Kernel synchronization object (on)

Source: Internet
Author: User

Windows NT provides five Kernel synchronization objects (Kernel Dispatcher Object). You can use these objects to control the processes of non-arbitrary threads (normal threads. Table 4-1 lists the types of these kernel synchronization objects and their usage. At any time, any object is in either of two States: signal or non-signal state. Sometimes, when the code runs in the context of a thread, it can block the execution of this thread and callKeWaitForSingleObjectOrKeWaitForMultipleObjectsThe function allows the Code (and background threads) to wait on one or more synchronization objects and wait for them to enter the signal state. The kernel provides routines for initializing and controlling the status of these objects.

Table 4-1. Kernel synchronization object

Object Data Type Description
Event) Kevent Blocks A thread until other threads detect an event.
Semaphore (signal light) Ksemaphore Similar to event objects, but can meet any number of waits
Mutex) Kmutex When a key code segment is executed, other threads are prohibited from executing the code segment.
Timer (timer) Ktimer Delay thread execution for a period of time
Thread) Kthread Blocking one thread until the end of another thread

In the following sections, I will describe how to use the kernel to synchronize objects. I will start from when I can call a thread that waits for the primitive to block, and then discuss the support routines for each object. Finally, we discuss the concepts related to thread vigilance and the submission of APC (asynchronous process call.

When and how to block a thread

To understand when the WDM driver program blocks a thread and how to use the kernel synchronization object, you must first have a basic understanding of the thread. Generally, if a software or hardware interrupt occurs during thread execution, the thread is still "current" during kernel processing interruption. The Context Environment in kernel mode code execution refers to the context of the "current" thread. To respond to various interruptions, the Windows NT scheduler may switch the thread, so that the other thread will become the new "current" thread.

The terms "arbitrary thread context" and "nonarbitrary thread context" are used to precisely describe the context type in which the driver routine is executed. If we know that the program is in the context of the initialization I/O Request thread, the context is not any context. However, for most of the time, the WDM driver cannot know this fact, because the chances of controlling which thread should be activated are usually when the interrupt occurs. When an application sends an I/O request, a conversion from user mode to kernel mode is generated, the I/O manager routine that creates and sends the IRP will continue to run in non-arbitrary thread context. We use the term "highest level driver" to describe the first driver that receives the IRP.

Generally, only the highest-level driver of a given device knows exactly that it is executed in a context of a non-arbitrary thread. This is because the driver dispatch routine usually puts requests in the queue and then immediately returns the caller. Then, through the callback function, the request is put forward to the queue and uploaded to the low-level driver. Once a dispatch routine suspends a request, all post-processing operations on the request must occur in any thread context.

After explaining the thread context, we can tell you a simple rule about thread blocking:

When processing a request, we can only block the thread that generates the request.

Generally, only the most advanced driver of the device can apply this rule. However, an important exception is the IRP_MN_START_DEVICE request. All drivers process this request synchronously. That is, the driver does not queue or suspend such requests. When you receive such a request, you can directly find the request initiator from the stack. As I mentioned in chapter 6, you must block that thread when processing such requests.

The following rules indicate that thread switching is not possible at the IRQL level:

The Code executed above or equal to the dispatch_level cannot block the thread.

This rule indicates that you can only block the current thread in the DriverEntry function, AddDevice function, or dispatch Letter of the driver. Because these functions are executed at the PASSIVE_LEVEL level. There is no need to block the current thread in the DriverEntry or AddDevice functions, because these functions only initialize some data structures.

Wait on a single synchronization object

You can call the KeWaitForSingleObject function as follows:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER timeout;NTSTATUS status = KeWaitForSingleObject(object, WaitReason, WaitMode, Alertable, &timeout);

The ASSERT statement indicates that the routine must be called at a level lower than or equal to DISPATCH_LEVEL.

In this call,ObjectPoint to the object you want to wait. Note that the type of this parameter is pvoid. It should point to the synchronization object listed in Table 4-1. This object must be in Non-Paging memory, for example, in a device extension or other data zone allocated from a non-Paging memory pool. In most cases, the execution stack can be considered non-paging.

WaitReasonIs a purely created token value, which is the kwait_reason Enumeration type. In fact, unless you specifyWrQueueParameter. Otherwise, no kernel code cares about this value. The cause of thread blocking is saved to an opaque data structure. If you understand this data structure, you may obtain some clues from the code for debugging a deadlock. Generally, the driver should specify this parameterExecutive.

WaitModeIt is a mode Enumeration type, which has only two values:KernelModeAndUserMode.

AlertableIs a Boolean value. Unlike waitreason, this parameter affects system behavior in another way and determines whether waiting can be terminated in advance to submit an APC. If waiting occurs in user mode, the memory manager can swap out the kernel mode stack of the thread. If the driver creates event objects in the form of automatic variables (in the stack), and a thread callsKeSetEventIn this case, the event object is replaced with the memory, and a bug check is generated. Therefore, we should always set the alertable parameter to false, that is, wait in kernel mode.

Last Parameter& TimeoutIs an address with a 64-bit timeout value, measured in 100 nanoseconds. A positive timeout indicates the absolute time from January 1, January 1, 1601. CallKeQuerySystemTimeFunction to obtain the current system time. A negative number indicates the time interval relative to the current time. If you specify an absolute timeout value, the system clock change will also affect your timeout value. If the system time exceeds the specified absolute time, it will never time out. On the contrary, if you specify a relative timeout value, the timeout value will not be affected by the system clock change.

Why is July January 1, 1601?

Many years ago, when I first learned Win32 APIs, I wondered why I chose January 1, 1601 as the start time of Windows NT. After I wrote a set of time conversion functions, I understood the cause of this problem. Everyone knows that the year divisible by four is a leap year. Many people also know that the Year of the century (such as 1900) should be an exception, although these years can be divided by four, but they are not a leap year. A few people also know that the years (such as 400 and 1600) that can be divisible by 2000 are exceptions, and they are also a leap year. In contrast, January 1, 1601 is the beginning of the 400 cycle. If you use it as the start point of the time information, you do not need to skip any operation to convert the NT time information into a regular date expression (or the opposite.

If 0 is specified, the KeWaitForSingleObject function will return immediately. The returned status code indicates whether the object is in the signal state. If your code is executed at the DISPATCH_LEVEL level, you must specify 0 timeout because blocking is not allowed on this IRQL. Each kernel synchronization object provides a setKeReadStateXxxService functions can be used to directly obtain the object state. However, the get object status is not exactly equivalent to the 0 timeout wait: When the KeWaitForSingleObject finds that the wait is met, it executes the additional action required by the special object. In contrast, the get object state does not execute any additional action, even if the object is already in the signal state.

The timeout parameter can also be specified as a NULL pointer, which indicates an indefinite wait.

The return value of this function indicates several possible results. The STATUS_SUCCESS result is what you want, indicating waiting to be satisfied. That is, when you call KeWaitForSingleObject, the object may have entered the signal state, or later enter the signal state. If the second condition is met, it is necessary to perform additional actions on the synchronization object. Of course, this additional action should also refer to the object type, which I will explain later when discussing the specific object type. (For example, a synchronization event needs to be reset after you wait for it to be met)

The returned value STATUS_TIMEOUT indicates that the object has not entered the signal State during the specified timeout period. If 0 times out, the function returns immediately. The return code is STATUS_TIMEOUT, indicating that the object is in a non-signal state. The return code is STATUS_SUCCESS, indicating that the object is in a signal state. If NULL times out, no return value is allowed.

The other two return values STATUS_ALERTED and STATUS_USER_APC indicate that the object is waiting for early termination and the object is not in the signal state. The reason is that the thread receives an alert (alert) or a user mode APC.

Wait on multiple synchronization objects

KeWaitForMultipleObjectsThe function is used to wait for one or more synchronization objects at the same time. The function is called as follows:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER timeout;NTSTATUS status = KeWaitForMultipleObjects(count,        objects,        WaitType,        WaitReason,        WaitMode,        Alertable,        &timeout,        waitblocks);

Here,ObjectsPoint to a pointer array, and each array element points to a synchronization object,CountIs the number of pointers in the array. Count must be smaller than or equal to the MAXIMUM_WAIT_OBJECTS value (currently 64 ). The array and all objects it points to must be in Non-Paging memory.WaitTypeIs an enumeration type, and its value can beWaitAllOrWaitAnyIt indicates whether you can wait until all objects enter the signal state, or if one object enters the signal state.

WaitblocksThe parameter points to a KWAIT_BLOCK structure array, which is used by the kernel to manage the wait operation. You do not need to initialize these structures. The kernel only needs to know where the structure array is, and the kernel uses it to record the status of each object in waiting state. If you only need to wait for a small number of objects (not exceeding THREAD_WAIT_OBJECTS, this value is currently 3), you can specify this parameter as NULL. If this parameter is NULL, KeWaitForMultipleObjects uses the pre-allocated waiting block array in the thread object. If the number of waiting objects exceeds THREAD_WAIT_OBJECTS, you must provide at least one piece of LengthCount * sizeof (KWAIT_BLOCK)Non-Paging memory.

Other parameters have the same function as the corresponding parameters in KeWaitForSingleObject, and most return codes have the same meaning.

If you specifyWaitAllThe return value STATUS_SUCCESS indicates that all the objects waiting for are in the signal state. If you specifyWaitAnyThe return value is equal to the value of the object that enters the signal state inObjectsIndex in the array. If multiple objects happen to enter the signal state, this value only represents one of them. It may be the first or other. You can think that this value is equal to STATUS_WAIT_0 plus an array index. You can first use NT_SUCCESS to test the return code, and then extract the array index from it:

NTSTATUS status = KeWaitForMultipleObjects(...);if (NT_SUCCESS(status)){  ULONG iSignalled = (ULONG) status - (ULONG) STATUS_WAIT_0;  ...}

If KeWaitForMultipleObjects returns the successful code, it will also execute the additional action waiting for the object to be satisfied. If multiple objects enter the signal state at the same time and the WaitType parameter you specify is WaitAny, this function only performs the additional action of the specified object returned values.

Kernel events

Table 4-2 lists the service functions used to process kernel events. To initialize an event object, we should first allocate non-Paging storage for it, and then callKeInitializeEvent:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);KeInitializeEvent(event, EventType, initialstate);

EventIs the address of the event object.EventTypeIs an enumerated value, which can beIcationicationeventOrSynchronizationEvent. A notification event has this feature. When it enters the signal state, it will remain in the signal State until you explicitly reset it to a non-signal state. In addition, when a notification event enters the signal state, all threads waiting for the event are released. This is similar to manual reset events in user mode. For a synchronization event, as long as one thread is released, the event is reset to a non-signal state. This is the same as the automatic reset event in user mode. While KeWaitXxxThe additional action that the function performs on the synchronization event object is to reset it to a non-signal state. Last ParameterInitialstateIt is a Boolean value. TRUE indicates that the initial state of the event is a signal state. FALSE indicates that the initial state of the event is not a signal state.

Table 4-2. Service functions used for Kernel event objects

Service Functions Description
Keclearevent Set the event to a non-signal State and do not report the previous state.
Keinitializeevent Initialize the event object
Kereadstateevent Obtains the current status of an event.
Keresetevent Set the event to a non-signal state and return the previous state.
Kesetevent Set the event to the signal state and return the previous state.
Note:

In these paragraphs about the synchronization primitive, I will also talk about the limits on the use of IRQL In the DDK document. In the current Windows 2000 release, DDK sometimes has more limits than the OS actually requires. For example, KeClearEvent can be called on any IRQL, but the DDK requires that the caller call at a level lower than or equal to DISPATCH_LEVEL. KeInitializeEvent can also be called on any IRQL, but the DDK requires that this function be called only at the PASSIVE_LEVEL level. However, you should respect the description in DDK. Maybe one day Microsoft will use these restrictions in the document.

CallKeSetEventThe function can set the event to the signal state:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LONG wassignalled = KeSetEvent(event, boost, wait);

In the above Code, the ASSERT statement forces you to call this function at a level lower than or equal to DISPATCH_LEVEL.EventThe parameter points to an event object,BoostValue is used to increase the priority of the waiting thread.WaitFor parameter explanations, see "The third parameter of KeSetEvent" in the text box. The WDM driver never sets the wait parameter to TRUE. If the event is already in the signal state, the function returns a non-0 value. If the event is in a non-signal state, the function returns 0.

The multi-task scheduler needs to artificially increase the priority of threads waiting for I/O operations or synchronization objects to avoid starving long-waiting threads. This is because the blocked thread often gives up its own time slice and no longer requires CPU acquisition, but as long as these threads get a higher priority than other threads, or other threads with the same priority can resume execution after they have used up their own time slices. Note: threads in their own time slices cannot be blocked.

The boost value used to increase the priority of the blocked thread is not a good choice. A better stupid way is to specify the IO_NO_INCREMENT value. Of course, if you have a better value, you can skip this value. If the event is awakened by a thread that processes time-sensitive data streams (such as a sound card driver), use the boost value (such as IO_SOUND_INCREMENT) suitable for that device ). It is important not to raise the priority of the waiting person for a silly reason. For example, if you want to synchronously process an IRP_MJ_PNP request, your completion routine should call KeSetEvent when you want to stop and wait for the low-level driver to finish processing the IRP. The PnP request has no special requirements on the processor and does not occur frequently. Therefore, even the sound card driver should specify the boost parameter as IO_NO_INCREMENT.

The third parameter of KeSetEvent

WaitThe purpose of a parameter is to allow internal control to be quickly transferred from one thread to another. Apart from device drivers, most system components can create dual-event objects. For example, the client thread and server thread use double event objects to define their communication. When the server thread needs to wake up the corresponding client thread, it first calls the KeSetEvent function and specifiesWaitIf the parameter is TRUE, call KeWait immediately.XxxThe function enables you to sleep. Since both operations are performed in an atomic manner, no other threads are awakened during Control handover.

DDK always describes some internal details, but I find some descriptions confusing. I will explain these internal details in another way. After reading these details, you will understand why we always set this parameter to FALSE. Internally, the kernel uses a "dispatcher database lock" to protect threads from blocking, wakeup, and scheduling operations. The KeSetEvent function needs to obtain this lock, KeWaitXxxThe same is true for functions. If you set this parameter to TRUE, the KeSetEvent function will set a flag for KeWaitXxxThe function knows that you use the TRUE parameter, and then it returns, and does not release the lock. When you call KeWait later (you should call it immediately, because you are running on an IRQL higher than any hardware device, and you hold a spin lock that is frequently competed)XxxFunction, it does not have to obtain this lock. The effect is that you wake up the waiting thread and put yourself into the sleep state at the same time, without giving other threads any chance to run.

You should understand thatWaitIf the parameter is TRUE, the function that calls the KeSetEvent must exist in non-Paging memory because it runs on the upgraded IRQL for a certain period of time. It is hard to imagine that a general device driver needs this mechanism, because the driver never knows Thread Scheduling better than the kernel. Bottom line: Always use FALSE for this parameter. In fact, the reason why Microsoft exposes this parameter is still unclear.

CallKeReadStateEventFunctions (on any IRQL) can test the current state of an event:

LONG signalled = KeReadStateEvent(event);

If the returned value is not 0, the event is in the signal State. If the returned value is 0, the event is in the non-signal state.

Note:

Windows 98 does not support the KeReadStateEvent function, but supports other KeReadState described above. XxxFunction. To obtain the event status, we must use other synchronization primitives of Windows 98.

CallKeResetEventA function (at a level lower than or equal to DISPATCH_LEVEL) can immediately obtain the current state of the event object. However, this function resets the event object to a non-signal state.

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LONG signalled = KeResetEvent(event);

You can callKeClearEventFunction, as shown below:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);KeClearEvent(event);

The KeClearEvent function runs faster because it does not set the event to a non-signal state after reading the current state of the event.

Kernel signal lights

The kernel mode signal light is an integer counter with synchronous semantics. The signal state of the signal counter is positive, and the signal state of the zero time table is non-signal. The counter cannot be a negative value. Releasing a traffic signal will increase the traffic signal counter by 1. Waiting on a traffic signal will reduce the traffic signal counter by 1. If the counter value is reduced to 0, the signal enters the non-signal state, and other call KeWaitXxxThe function thread will be blocked. Note that if the number of waiting threads exceeds the counter value, not all waiting threads can resume operation.

The kernel provides three service functions to control the state of the traffic light object. (See Table 4-3) the traffic light object should be initialized at PASSIVE_LEVEL:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);KeInitializeSemaphore(semaphore, count, limit);

In this call,SemaphoreThe parameter points to a KSEMAPHORE object in non-Paging memory.CountIs the initial value of the traffic signal counter,LimitIs the maximum value that the counter can reach. It must be the same as the initial value of the signal light counter.

Table 4-3. Kernel signal light object service functions

Service Functions Description
Keinitializesemaphore Initialize a traffic signal object
KeReadStateSemaphore Obtains the current status of the traffic signal.
KeReleaseSemaphore Set the signal object to the signal state

If the limit parameter is set to 1 when you create a traffic signal, the object is similar to a mutex object with only one thread. However, the kernel mutex has some features that do not have signal lights. These features are used to prevent deadlocks. Therefore, there is no need to create a traffic signal with a limit of 1.

If you create a traffic signal with a limit value greater than 1, the traffic signal allows multiple threads to access some resources at the same time. In the queue theory, we will find that a single queue can be used by multiple service programs. Multiple service programs use one queue more reasonably than each service program has its own queue. The average waiting time of these two forms is the same, but the former has fewer waiting times. With traffic signals, you can organize a group of software or hardware service programs according to the queue principle.

The owner of the signal lamp can callKeReleaseSemaphoreFunction release signal:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LONG wassignalled = KeReleaseSemaphore(semaphore, boost, delta, wait);

ADeltaParameter, which must be a positive number.DeltaAdd ValueSemaphoreThis will bring the signal to the signal state and cause the waiting thread to release. Generally, this parameter should be set to 1, indicating that an owner has released its rights.BoostAndWaitParameters play the same role as the KeSetEvent function. If the return value is 0, the previous state of the signal lamp is not a signal State. If the return value is not 0, the previous state of the signal lamp is a signal state.

KeReleaseSemaphore does not allow you to increase the counter value to a value that exceeds the limit value. If you do this, the function does not adjust the counter value at all. It will generate an exception code STATUS_SEMAPHORE_LIMIT_EXCEEDED. Unless there is a handler that captures this exception in the system, it will lead to a bug check.

The following call reads the current status of the traffic signal:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LONG signalled = KeReadStateSemaphore(semaphore);

If the return value is not 0, the signal lamp is in the signal State. If the return value is 0, the signal lamp is not in the signal state. Do not assume that the returned value is the current value of the counter.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.