Using functions from the NetWare API, an NLM can control the termination process. An NLM should include mechanisms to handle the following:
For information about terminating an NLM gracefully see Section 4.2, Terminating an NLM.
The NLM unload process, as shown in the following figure, occurs only when an NLM is unloaded from the system console command line. The NLM unload process is a sequence of steps, some performed by the NLM, and others performed by the NetWare OS or by the NetWare API.
Figure 2-2 NLM Unload Process
The NLM self-termination process, as shown in the following figure, occurs when an NLM calls exit or ExitThread or when all threads in an NLM terminate. The NLM self-termination process consists of two phases, with the first phase performed by the thread that caused the termination and the second phase performed by an OS thread.
Figure 2-3 NLM Self-Termination Process
The NLM abnormal exit process, as shown in the following figure, occurs only when the NLM calls the _exit or abort function. The NLM abnormal exit process is a sequence of steps, some performed by the NLM, and others performed by the NetWare OS or by the NetWare API.
Figure 2-4 NLM Abnormal Abort Process
The registered function calls and signal raising shown in the figures that show exit steps occur only if the NLM has specified them, using signal, AtUnload, and atexit (NDK: NLM Threads Management). If no registered functions or signal handling exists in the NLM, the associated step is skipped.
If a signal handler is implemented properly, use of AtUnload and atexit might not be needed at all (see Section 4.2, Terminating an NLM).
In the first step of the unload process, the NLM calls the CHECK function, but only if it has been specified. NLMs specify CHECK functions in the linking phase using directives. For example, if CheckFunction were the name of the function to be called as the CHECK function, the WLINK directive would be as follows:
option check = CheckFunction
The purpose of the CHECK function is to determine if the NLM is in a state in which it can unload safely. If so, the function should return zero, indicating that the unload process can continue normally.
If the function determines that the NLM cannot be unloaded safely, it should display a warning message on the system console screen and return a nonzero value. If the NetWare operating system receives a nonzero return value from the CHECK function, it issues the following message:
Unload module anyway? n
If the above message appears, the system console operator can abort the termination process and allow the NLM to continue its normal operation.
NLM termination code should not be placed in the CHECK function.
IMPORTANT:The CHECK function is run by an operating system thread that by default does not have CLIB context in any NetWare version. If your CHECK function calls any NetWare API functions that need CLIB context, you must give the calling thread CLIB context by calling SetThreadGroupID (NDK: NLM Threads Management).
The following is a sample CHECK function:
int CheckFunction()
{
/* If you need context information, put it here */
if( NLMIsBusyRightNow )
{
ConsolePrintf(
"That NLM is currently in use.\r\n");
return 1;
}
return 0;
}
Given the sample CHECK function above, if the operator attempted to unload HELLO.NLM while it is busy, the following would be the command and output on the console:
:unload hello That NLM is currently in use. Unload module anyway? n :
This method does not prevent an operator from continuing with the unload process. It merely provides a meaningful message that the operator can use in deciding whether to continue the unload.
In most situations, you would want to allow the NLM to be unloaded. However, there might be a reason that you do not want anyone to unload the NLM without shutting the server down. In this case, you need to ungetch an `n’ to the system console. This can be done with the following code:
#include <stdio.h>
#include <conio.h>
#include <process.h>
int sysThreadGroupID
main()
{
sysThreadGroupID = GetThreadGroupID();
...
}
int NWNoUnload()
{
LONG OldScrID;
LONG NewScrID;
int TGID;
// give the OS thread CLIB context
TGID = SetThreadGroupID(sysThreadGroupID);
OldScrID = GetCurrentSceen
NewScrID = CreateScreen("System Console",0);
If(OldScrID != NewScrID)
SetCurrentScreen(NewScrId);
ungetch(’n’);
if(OldScrID != NewScrID)
{
SetCurrentScreen(OldScrID);
DestroyScreen(NewScrID);
}
SetThreadGroupID(TGID)
return -1;
}
You can use SetNLMDontUnloadFlag to set an NLM so it cannot be unloaded even if the console operator says it is OK to unload the NLM. Use ClearNLMDontUnloadFlag to allow the NLM to be unloaded after its don’t unload flag has been set (NDK: NLM Threads Management).
In general, a signal is raised by the NetWare API when a particular program condition occurs. However, the NLM itself can raise a signal by calling raise (NDK: NLM Threads Management).
Signal handling allows previously registered functions to gain control of critical shutdown processes. The following are typical signals your NLM might handle:
This ANSI standard signal is the most frequently used signal, and is raised when the NLM is unloaded from the console command line. Because the NetWare API raises the SIGTERM signal only when the NLM is unloaded, you must raise the signal yourself when exiting with ExitThread or exit. You can raise the signal using raise (NDK: NLM Threads Management).
This signal is raised only during abnormal exit of the NLM, such as stack overflow. In abnormal exit, the NLM calls abort, which raises the SIGABRT signal (NDK: NLM Threads Management).
This signal is raised when the operator presses Ctrl+C during screen output and if the NLM screen’s CtrlCharCheckMode is set to TRUE (default). This does not affect an NLM if the current screen is the System Console screen.
In all cases of the termination process, threads are ended before the functions registered with AtUnload and atexit are called. Without signal handling, resources allocated on a local stack, such as local semiphores, cannot be released because thread stacks are freed before the functions registered with AtUnload and atexit are called. (If you want to keep track of these resources after the treads are terminated, you could store them in global or static variables.)
NLMs usually define signal handlers during initialization by calling a function such as the following:
signal( SIGTERM,MySignalHandler );
The following is a sample signal handler:
int ThreadCounter;/* counts the number of threads running */
int ShutDownFlag; /* the signal handler sets this to TRUE */
#pragma off(unreferenced);
void MySignalHandler(int sigtype) /* sigtype is SIGTERM */
#pragma on(unreferenced);
{
ShutDownFlag = TRUE;
printf("Inside signal handler.
Waiting for threads to stop ...\n");
while( ThreadCounter > 0 )
{
delay( 500 ); /* wait half a second */
}
printf("Inside signal handler. Threads have
stopped.\n");
}
By writing a signal handler function that defines a global flag, you can manage resources that are allocated from a local stack. When the signal is raised, it can set a global flag that each thread in the NLM reads. Those threads then can return their own resources and exit.
Whether you require the use of a signal handler depends primarily on whether you have local resources that can be freed only by the thread of execution that allocated them. If your NLM uses any stack-based resources, a signal handler is necessary for proper NLM shutdown.
IMPORTANT:By the time the AtUnload and atexit functions are called, all NLM thread groups have been destroyed. These threads therefore cannot be given thread group contex. You cannot use any of the NetWare API functions that need context (for example, printf in Single and Intra-File Services) while in the AtUnload and atexit routines. If you do, the server abends.
To ensure that resources owned by NLM threads are properly freed, we highly recommend that you implement a SIGTERM signal handler as explained in Section 4.2, Terminating an NLM.
During unloading, the AtUnload and the ANSI standard atexit functions are executed if they have been defined. During self-termination, only the atexit functions are executed if they have been defined.
The AtUnload and atexit functions can perform resource cleanup such as freeing memory, closing semaphores, and so on. Each NLM can have a single AtUnload function and up to 32 atexit functions.
NLMs can define the AtUnload function with a call such as the following:
AtUnload( NLMUnloadFunction );
The following example uses the AtUnload function:
char *myMemPtr; /* the pointer for this NLM’s memory */
main()
{
AtUnload( NLMUnloadFunction );
/* other NLM code would go here */
printf("You may unload this NLM.\n");
}
void NLMUnloadFunction()
{
if( myMemPtr != NULL ) free ( myMemPtr );
}
The AtUnload function calls one function only. Therefore, the called function should perform all the necessary functions you want implemented at unload time.
NLMs usually specify their atexit functions with calls such as the following:
atexit( CloseMyFile ); atexit( CloseMySemaphore ); atexit( FreeMyMemory );
Successive calls to the atexit function create a list of functions to be executed on a last-in, first-out basis.
The following example uses the atexit function:
FILE *myOpenFile; /* the file this NLM will open */
LONG mySemaphore; /* the semaphore this NLM will use */
char *myMemPtr; /* memory this NLM will allocate */
main()
{
atexit( CloseMyFile );
atexit( CloseMySemaphore );
atexit( FreeMyMemory );
/* other NLM code would go here */
printf("You may unload this NLM.\n");
}
void CloseMyFile()
{
// still have NLM level context
if( myOpenFile != NULL)
fclose ( myOpenFile );
}
void CloseMySemaphore()
{
if( mySemaphore != NULL )
CloseLocalSemaphore( mySemaphore );
}
void FreeMyMemory()
{
if( myMemPtr != NULL )
free( myMemPtr );
}
If you do not handle cleanup through signal handling, you can use the atexit functions to clean up if any of the following conditions are met:
The NLM calls exit.
The last thread in the NLM returns from its original function.
The NLM calls ExitThread with EXIT_NLM as the action code parameter, which causes NetWare to unload the NLM. (If only one thread is running, calling ExitThread with EXIT_THREAD as the action code parameter also terminates the NLM.)
The NLM is unloaded with the UNLOAD command.
If the NLM never self-terminates, then the only function that needs to be defined is AtUnload or a SIGTERM signal handler. These functions gain control when the operator issues the UNLOAD command.
NLMs are responsible for freeing the resources they allocate, such as memory, sockets, screens, devices, semaphores, and so on. NLMs should return all allocated resources to the OS during the termination process. If an NLM has not freed all its resources upon program termination, NetWare issues a warning message such as the following:
5/24/93 3:30pm: Module did not release 500 resources. Module: Hello Resource: Small memory allocations Description: Alloc Short Term Memory
During NLM termination, NetWare and the NetWare API attempt to free all resources that the NLM allocated. Local semaphores are the only resource that cannot be freed; they must be freed with calls to CloseLocalSemaphore. If an NLM does not close all allocated local semaphores upon termination, the server abends.
To avoid these problems, we strongly suggest that you use a signal handler. Most frequently, the NLM is unloaded from the console command line with the unload command, and the SIGTERM signal handler is appropriate (see Section 4.2, Terminating an NLM).