分类: C/C++
2007-06-17 21:42:14
This paper details how the NT based API WaitForMultipleObjects()
was successfully ported to a
Solaris operating environment. A thread-safe emulation based on the POSIX thread library and its
basic synchronization primitives was used to accomplish this task.
This project was undertaken to assist an ISV with the port of a server
application from NT to Solaris, particularly the port of the
WaitForMultipleObjects()
API, which is not straightforward since
there is no concept of events and handles in Solaris. Our
implementation of this API is generic and can be applied to any
handle type on NT. This paper discusses the design issues we faced
and could be applicable to other developers facing similar port
issues. The Appendix includes the source code for
WaitforMultipleObject
s API implementation and is POSIX compliant.
The WaitFor
method is the basic thread synchronization mechanism for the NT operating system,
and its successful implementation is critical to any NT emulation effort.
WaitFor
allows NT threads to synchronize on the setting of one or more
varieties of NT objects. The thread issuing a WaitFor
is then blocked until either a
timeout occurs after a pre-specified time, or when one or all of the objects it is waiting for gets "set".
The WaifFor
is not blocking if one of the NT objects is already set when the
WaifFor
is first issued.
The WaitForSingleObject
function takes a handle object as the first parameter and will not return
before the object that is referenced by the handle attains the signaled state or the timeout value that is specified
as the second parameter expires. This is simple.
The WaitForMultipleObjects
call is similar, except that its main parameter (the second) is an array
of Windows NT handle objects. The first parameter passed to it specifies the number of handles in this array.
The third parameter is FALSE if waiting for ANY object is desired and TRUE if the function is to return only
when all objects in the array have been set; the wait for ALL objects is desired.
Note that the handle array passed to WaitForMultipleObject
s can be composed of an arbitrary mix
of Windows NT handle objects. This includes thread handles, handles to processes, mutexes, semaphores, and events.
Each handle type is a subclass of the base handle class.
A thread can wait for multiple objects to be set in one of two ways:
1. ALL
The issuing thread will unblock after ALL objects specified in the call have been set. This is fairly simple to emulate. Each object is waited for in turn until all have been set or until timeout occurs. At that point this function returns.
2. ANY
The issuing thread will unblock after ANY one specified object in the call has been set.
WaitForMultipleObjects
for the case ANY is the most difficult of all Win32 routines
to emulate since UNIX® (thus Solaris) does not support anything
like it. The problem is that the issuing thread has to actually be blocked on a single Solaris variable,
yet the setting of any one of a series of NT objects has to result in the signaling of that variable.
Following is a list of requirements for the solution:
The solution detailed in this paper makes use of the subscription model, that is, the interested thread subscribes itself to the interested wait object and goes to sleep until signaled.
The handle is a composite object containing a list of mutex_cond
objects. Each mutex_cond
object contains a condition variable and a mutex.
The handle structure will look like this:
Handle{
List mutex_cond;
Boolean is_signaled;
}
mutex_cond{
Mutex mutex;
Cond_var cond_var;
}
Instead of having two lists, one of mutex and the other of cond_var
, the mutex_cond
object is created so that the action of adding the pair of mutex and condition variables to the
handle is atomic.
The model proposes that each thread that issues or executes on a WaitFor
call on any one or more handles
subscribes itself to those handles by adding a pair of mutex/cond_var
to that handle list and then waits
on that mutex/cond_var
pair. When a handle is set, all threads waiting on that handle are signaled.
Thus, if two threads wait on a handle, there will be two elements in the list of that handle. And when the handle
is set, both of the threads are signaled.
ALL or logical AND
In the case of ALL or logical AND, the thread needs to wait for all handles to be set, or for timeout,
before exiting from the WaitFor
call. One mutex_cond
object containing a condition
variable and a mutex is created for every handle and added to that handle's list.
The code will look like this:
Boolean WaitForMultipleObjects(int Count, void
**Handle, boolean fWaitall, long msTimeOut)
{
mutex_cond_t mutex_cond[Count];
for(i=0; i < COUNT; I++){
INITIALIZE THE MUTEX AND CONDITION VARIABLE
CREATE A NEW MUTEX_COND OBJECT MUTEX_COND[I]
ADD THE MUTEX_COND OBJECT TO THE LIST OF THE
HANDLE[I]
}
FOR(I=0; I < COUNT; I++){
LOCK MUTEX_COND[I]->mutex
while (! Handle[i]->is_signaled) {
Wait until timeout on mutex_cond[i]->cond_var
}
UnLock mutex_cond[i]->mutex
}
for(i=0; i < COUNT; I++){
DELETE MUTEX_COND[I] FROM HANDLE LIST
}
}
ANY or logical OR
In the case of ANY or logical OR, the preceding example illustrates
that instead of the thread creating a new mutex_cond
object for each handle, there is a single common mutex_cond
object for all the handles specified, and the thread wait on this common
mutex_cond
object for all the handles. In other words, the
thread subscribes to each handle with the common mutex_cond
object, and waits on it. So, if any handle gets set, the thread returns
from the WaitFor
method as expected. This prevents the need
for any kind of polling, which can be highly CPU intensive.
The code will look like this:
Boolean WaitForMultipleObjects(int Count,
void **Handle,
boolean fWaitall, long TimeOut)
{
Create one common mutex_cond object:
common_mutex_cond
Initialize the mutex and condition variable
for(i=0; i < COUNT; I++){
ADD THE COMMON_MUTEX_COND OBJECT TO THE LIST OF THE
HANDLE[I]
}
LOCK COMMON_MUTEX_COND->mutex
for(i=0; i < COUNT; I++){
CHECK FOR EACH HANDLE IF IT HAS BEEN SET, IF
SET EXIT
}
IF (NONE OF THE HANDLES HAVE BEEN SET) {
WAIT UNTIL TIMEOUT ON COMMON_MUTEX_COND->cond_var,
common_mutex_cond->mutex
}
UnLock common_mutex_cond->mutex
for(i=0; i < COUNT; I++){
DELETE COMMON_MUTEX_COND ELEMENT FROM LIST OF
HANDLE[I]
}
}
SIGNALING
When a handle is set, a signal is sent to all threads waiting on that handle.
void HandleSet(void *Handle ){
Iterate through the Handle List {
Lock Handle->List_element->mutex
}
Iterate through the Handle List {
Signal Handle->List_element->cond_var
}
Iterate through the Handle List {
Unlock Handle->List_element->mutex
}
}
The complete code for the case of an event handle and a test driver is available in the Appendix. The design is generic and this code can be modified to use a mix of one or more handle types. The code uses the POSIX thread library.
The same subscription model can be used for the case of WaitForSingleObject
if the Count parameter in the
case of WaitForMultipleObjects
is set to 1.
For example:
Boolean WaitForSingleObjects(void **Handle, long
msTimeOut)
{
WaitForMultipleObjects(1, **Handle, AND,
msTimeOut);
}
LIMITATION
mmaped
on startup and shared by different processes.CONCLUSIONS
The port of WaitForMultipleObjects()
Win32 API has been successfully done and can be
used by other groups for porting Win32 applications from NT to the Solaris operating environment.
Much more than that, this implementation of the handle structure forms the basic building block,
making use of the Solaris synchronization variables, on top of which complex synchronization
objects can be built.
REFERENCES
|