The NetWare API: Using Semaphores to Implement Concurrent-User Licensing
Articles and Tips: article
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.