Novell is now a part of Micro Focus

The NetWare API: Using Semaphores to Implement Concurrent-User Licensing

Articles and Tips: article

MORGAN B. ADAIR
Senior Research Engineer
Novell Systems Research Department

01 Feb 1996


NetWare provides a simple semaphore facility that you can use to synchronize user access to network resources. This DevNote describes NetWare's semaphore facility, suggests some possible applications, and illustrates how semaphores can be used to create a program that limits the number of users who can run it concurrently.

Semaphores

If you've had a few computer science classes, you've probably encountered semaphores. But unless you've written an operating system or a distributed application, chances are you've never used them. NetWare provides a simple implementation of semaphores that you can use to synchronize processes across the network or enforce exclusive access to a shared resource.

This DevNote gives a brief introduction to semaphores (just in case you were sleeping in CS232), describes how semaphores are implemented in NetWare, then gives an example program that uses semaphores to implement concurrent-user licensing.

In 1965, Edsger W. Dijkstra proposed semaphores as a solution to the mutual exclusion problem. The mutual exclusion problem results when two processes share a resource.

For example, suppose your program has two processes (A and B), each of which is to increment a shared variable (X). The desired order of operations is:


Operation
Valueof X

InitializeX.

5

A retrievesthe value of X.

5

A incrementsX.

5

A storesthe new value in X.

6

B retrievesthe value of X.

6

B incrementsX.

6

B storesthe new value in X.

7

Because the two processes run independently, the operations may occur in the following order:


Operation
Valueof X

InitializeX.

5

A retrievesthe value of X.

5

A incrementsX.

5

B retrievesthe value of X.

5

A storesthe new value in X.

6

B incrementsX.

6

B storesthe new value in X.

6

Dijkstra's solution is to use a single variable, a semaphore, to control access to the shared resource. If the semaphore has a value greater than 0, the resource is available. If the semaphore has a value less than 0, processes are waiting for the shared resource. The two operations that operate on semaphores are called P (or wait) and V (or signal).

The operations get their name from the Dutch proberen (to test) and verhogen (to increment). Dijkstra defined the P operation on semaphore S as:

if S > 0 then S = S - 1 else (wait on S)

The V operation is:

if (processes are waiting on S) then (let one of the processes proceed) else S = S + 1

The P and V operations must be implemented atomically, or the semaphore becomes just another shared resource with its own mutual exclusion problem.

Event Synchronization

One typical application of semaphores is in processing I/O requests. For example, a process may request data to be read from a file, then waits on a semaphore while another process services the request. The file read process signals the semaphore when the read is completed, and the first process can continue.

This interaction is called a block/wakeup protocol. Most implementations of semaphores (including NetWare's) allow for timeout after a specified period.

A similar type of process interaction is called a producer-consumer relationship. One process generates data and stores it in a shared buffer. The other process reads from the buffer. If the producer process operates faster than the consumer process, data will be lost.

If the consumer process runs faster than the producer process, the same data will be processed more than once. You can use semaphores to assure that the processes remain synchronized.

Counting Semaphores

Counting semaphores only take non-negative integer values, and are used to control access to a pool of identical resources. The example program at the end of this DevNote, SYNC, uses counting semaphores to assure that no more than five users are allowed to run an application concurrently.

NetWare only has one type of semaphore, but they can operate as counting semaphores. See the description of SYNC for more information.

NetWare's Synchronization APIs

NetWare has five API (application programming interface) functions that operate on semaphores:

  • NWOpenSemaphore

  • NWCloseSemaphore

  • NWWaitOnSemaphore

  • NWSignalSemaphore

  • NWExamineSemaphore

A brief description of each API follows.

NWOpenSemaphore

NWOpenSemaphore creates and initializes a named semaphore to a specified value. NWOpenSemaphore is declared as follows:

NWCCODE NWAPI NWOpenSemaphore(NWCONN_HANDLE  conn,     //NetWare server connection handle

char NWFAR   *semaphoreName,                           //Pointer to the name of the semaphore

                                                      //to be opened.

NWSEM_INT   initSemaphoreHandle,                       //Initial value of the semaphore being

                                                      //opened.  Must be greater than or

                                                      //equal to 0.

NWSEM_HANDLE NWFAR *semaphoreHandle,                   //Pointer to the NetWare semaphore

                                                      //handle

NWNUMBER NWFAR  *semaphoreOpenCount                    //Pointer to the number of stations

                                                      //having this semaphore open

                                                      //(optional).

);

NWOpenSemaphore increments semaphoreOpenCount. If another process (on the calling workstation or another workstation) has already created a semaphore with the specified name, NWOpenSemaphore ignores initSemaphoreHandle. Subsequent calls to synchronization functions reference the semaphore using the semaphoreHandle.

SemaphoreOpenCount indicates how many clients have the semaphore open. SemaphoreOpenCount is optional. If you do not need the value of semaphoreOpenCount, pass a NULL in this.

A semaphore value greater than or equal to 0 indicates the application can access the associated network resource.

A negative value indicates the number of processes waiting to use the semaphore. If the semaphore value is negative, the application must either enter a waiting queue by calling NWWaitOnSemaphore or temporarily abandon its attempt to access the network resource.

SemaphoreOpenCount indicates the number of processes holding the semaphore open. NWOpenSemaphore increments this value; NWCloseSemaphore decrements this value.

Example Program: SYNC

SYNC uses counting semaphores to limit the application to five concurrent users. It implements counting semaphores by comparing the semaphoreOpenCount with the initial value of the semaphore.

SYNC sets the initial semaphore value when the first user runs it. SYNC never calls NWWaitOnSemaphore or NWSignalSemaphore, so the value of the semaphore never changes. It is just the maximum number of concurrent licenses allowed by the application.

As each copy of SYNC is executed, NetWare increments the semaphore's semaphoreOpenCount. SYNC compares the semaphoreOpenCount with the semaphore value to determine if the maximum number of concurrent-user licenses has been exceeded.

You could modify SYNC to allow the program to wait until an application license becomes available. One way to do this would be to add a second semaphore that implements a queue of waiting users.

If the license count semaphore is equal to the maximum number of concurrent licenses, the application would wait on the second semaphore. Each copy of the application would signal the queue semaphore as it exits, allowing another copy of the application to continue running.

SYNC was written using Microsoft Visual C++ with the Microsoft Foundation Classes (MFC).

SYNC.H

// sync.h : Declares the class interfaces for the application.

class CMainWindow : public CFrameWnd

{

public:

   CMainWindow();

   //{{AFX_MSG( CMainWindow )

   afx_msg void OnPaint();

   afx_msg void OnAbout();

   afx_msg void OnTimer(UINT nIDEvent);

   afx_msg void OnClose();

   //}}AFX_MSG

   DECLARE_MESSAGE_MAP()

};

class CTheApp : public CWinApp

{

public:

   BOOL InitInstance();

};

SYNC.CPP

// sync.cpp :

//

#include "stdafx.h""
#include "resource.h""
#include "sync.h""
#include <nwcalls.h<<
#include <stdlib.h<<
// theApp:

// Just creating this application object runs the whole application.

CTheApp NEAR theApp;

NWCONN_HANDLE conn;

NWSEM_HANDLE semHandle;

CMainWindowCMainWindow()

{

   NWCCODE  ccode;  

   NWNUMBER openCount;

   int      semValue;

   Crect   rect;

   //size program window

   SetRect(rect, 50, 50, 600, 300);

   //load accellerator keys

   LoadAccelTable("MainAccelTable");"
   //create program window

   Create( NULL, "Concurrent Application License Example", WS_OVERLAPPEDWINDOW, rect, NULL, "MainMenu");"
   if (!SetTimer(1, 10000, NULL))    //Fire timer every 10 seconds

   {

       MessageBox(_T("Not enough timers available for this window."),"
       _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
       exit(1);

   }

   ccode = NWGetDefaultConnectionID(&conn);&
   if (ccode)

   {

       MessageBox(_T("Unable to get default connection ID."),"
       _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
       exit(1);

   }

   ccode = NWOpenSemaphore(conn, "LICENSE", 5, "semHandle, "openCount);"
   if (ccode)

   {

       MessageBox(_T("Unable to get open semaphore."),"
       _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
       exit(1);

   } else {

       ccode = NWExamineSemaphore(conn, semHandle, &semValue, &openCount);&
       if (ccode)

       {

           MessageBox(_T("Unable to examine semaphore."),"
           _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
           exit(1);

       } else if (openCount > (NWOPEN_COUNT)semValue) {>
           MessageBox(_T("Too many copies of this application running concurrently."),"
           _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
           exit(1);

       }

   }

}

void CMainWindowOnPaint()

{

   NWCCODE      ccode;

   int          semValue;

   NWOPEN_COUNT openCount;

   char         countStr[3];

   char         valStr[3];

   ccode = NWExamineSemaphore(conn, semHandle, &semValue, &openCount);&
   if (ccode)

   {

       MessageBox(_T("Unable to examine semaphore."),"
       _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
       exit(1);

   }

   itoa(openCount, countStr, 10);

   itoa(semValue, valStr, 10);

   CString s = "Concurrently running copies of this app: ";"
   s += countStr;

   s += "   Semaphore value: ";"
   s += valStr;

   CPaintDC dc( this );

   CRect rect;

   GetClientRect( rect );

   dc.SetTextAlign( TA_BASELINE | TA_CENTER );

   dc.SetTextColor( GetSysColor(COLOR_WINDOWTEXT ) );

   dc.SetBkMode(TRANSPARENT);

   dc.TextOut( ( rect.right / 2 ), ( rect.bottom / 2 ), s, s.GetLength() );

}

void CMainWindowOnAbout()

{

   CDialog about( "AboutBox", this );"
   about.DoModal();

}

BEGIN_MESSAGE_MAP( CMainWindow, CFrameWnd )

   //{{AFX_MSG_MAP( CMainWindow)

   ON_WM_PAINT()

   ON_COMMAND( IDM_ABOUT, OnAbout )

   ON_WM_TIMER()

   ON_WM_CLOSE()

   //}}AFX_MSG_MAP

END_MESSAGE_MAP()

BOOL CTheAppInitInstance()

{

   SetDialogBkColor();

   m_pMainWnd = new CMainWindow();

   m_pMainWnd  ShowWindow(m_nCmdShow ); 
   m_pMainWnd  UpdateWindow(); 
   return TRUE;

}

void CMainWindowOnTimer(UINT nIDEvent)

{

   CFrameWnd OnTimer(nIDEvent);

   InvalidateRect(NULL, TRUE);

}

void CMainWindowOnClose()

{

   NWCCODE ccode;

   CFrameWnd OnClose();

   ccode = NWCloseSemaphore(conn, semHandle);

   if (ccode)

   {

       MessageBox(_T("Unable to close semaphore."),"
       _T("SYNC"), MB_ICONEXCLAMATION | MB_OK);"
       exit(1);

   }

}

Bibliography

Deitel, Harvey M., An Introduction to Operating Systems, 2nd ed., Addison-Wesley Publishing Company, Inc., 1990.

Dijkstra, E. W., "Cooperating Sequential Processes," Technical Report EWD-123, Technological University, Eindhoven, Netherlands, 1965.

Schank, Jeffrey D., Novell's Guide to Client-Server Applications and Architecture, SYBEX Inc./Novell Press, 1994.

Silberschatz, Abraham and Peterson, James L., Operating Systems Concepts, Addison-Wesley Publishing Company, Inc., 1989.

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates