Kernel synchronization object (below)

Source: Internet
Author: User
Tags apc readfile
Document directory
  • Cancels a periodic timer.
  • Example

Kernel mutex

Mutex is short for mutual exclusion. The kernel mutex provides a method (not necessarily the best) for serializing access to shared resources by multiple competing threads ). If the mutex object is not owned by a thread, it is a signal state, and vice versa. When the thread calls KeWait to obtain control of the mutex objectXxxDuring the routine, the kernel also does some work to help avoid possible deadlocks. Similarly, mutex objects also need additional actions similar to KeWaitForSingleObject. The kernel ensures that the thread is not swapped out and stops all APC submissions.IoCompleteRequestExcept for APC used to complete I/O requests.

Generally, we should use the fast mutex object output by the executive component instead of the kernel mutex object. The main difference between the two is that the kernel mutex can be obtained recursively, while the executive quick mutex cannot. That is, the owner of the kernel mutex can call KeWait.XxxAnd specify the mutex object so that the wait is met immediately. If a thread does this, it must release the mutex at the same time. Otherwise, the mutex object is not considered idle.

If you need to serialize an object for a long time, you should first consider using mutex (instead of relying on upgraded IRQL and spin locks ). Using mutex objects to control resource access allows other threads to run in other CPUs on the multi-processor platform, code that causes page faults can still lock resources without being accessed by other threads. Table 4-4 lists the service functions of mutex objects.

Table 4-4. Mutex object service functions

Service Functions Description
Keinitializemutex Initialize mutex object
Kereadstatemutex Obtains the current status of the mutex object.
Kereleasemutex Set mutex to signal state

To create a mutex object, you need to reserve a non-Paging memory for the KMUTEX object and initialize it as follows:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);KeInitializeMutex(mutex, level);

MutexIs the address of the KMUTEX object,LevelThe parameter was initially used to help avoid deadlocks caused by multiple mutex objects. But now, the kernel ignoresLevelParameters.

The initial state of the mutex object is the signal state, that is, it is not owned by any thread. KeWaitXxxThe call will allow the caller to take over the control of the mutex object and bring it into a non-signal state.

Use the following function to obtain the current status of the mutex object:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LONG signalled = KeReadStateMutex(mutex);

If the return value is 0, the mutex object is occupied. If the value is not 0, the object is not occupied.

The following function allows the owner to discard the mutex object it occupies and enable it to enter the signal state:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);LONG wassignalled = KeReleaseMutex(mutex, wait);

WaitThe parameters are the same as those in the KeSetEvent function. The return value of this function is always 0, indicating that the mutex object has been occupied. If this is not the case (the owner releases not its own object ),KeReleaseMutexA bug check is generated.

For the sake of integrity, I would like to mentionKeWaitForMutexObjectFunction, which is a macro In the DDK (see WDM. H ). It is defined as follows:

#define KeWaitForMutexObject KeWaitForSingleObject

Kernel Timer

 

The kernel also provides a timer object that automatically changes from a non-signal state to a signal state after the specified absolute time or interval. It can also periodically enter the signal state. We can use it to arrange a DPC callback function that is regularly executed. Table 4-5 lists the service functions used for timer objects.

Table 4-5. Service functions of kernel timer objects

Service Functions Description
Kecanceltimer Cancels an active timer.
Keinitializetimer Initialize a one-time notification Timer
Keinitializetimerex Initialize a one-time or repeated notification or synchronous Timer
Kereadstatetimer Obtains the current status of the timer.
Kesettimer Set the time for the notification Timer
KeSetTimerEx Set Time and other attributes for the timer

Notification timer usage event

 

In this section, we will create a notification timer object and wait until it reaches the specified time. First, we allocate a KTIMER object in non-Paging memory. Then, we initialize the timer object at a level lower than or equal to DISPATCH_LEVEL:

PKTIMER timer;      //  someone gives you thisASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);KeInitializeTimer(timer);

Here, the timer is in a non-signal state, and it has not started the countdown. The thread waiting for such a timer will never be awakened. To start the timer countdown, we callKeSetTimerFunction:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER duetime;BOOLEAN wascounting = KeSetTimer(timer, duetime, NULL);

DuetimeIs a 64-bit time value, measured in 100 nanoseconds. If the value is positive, it indicates an absolute time from January 1, January 1, 1601. If this value is negative, it is a period of time interval relative to the current time.

If the return value is TRUE, it indicates that the timer has been started. (In this case, if we call the KeSetTimer function again, the timer will give up the original time and start the countdown again)

The following statement reads the current status of the Timer:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);BOOLEAN counting = KeReadStateTimer(timer);

KeInitializeTimerAnd KeSetTimer are actually old service functions, they have been replaced by new functions. We can call the initialization timer as follows:

ASSERT(KeGetCurrentIqrl() <= DISPATCH_LEVEL);KeInitializeTimerEx(timer, NotificationTimer);

The timer setting function also has an extended version,KeSetTimerEx:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER duetime;BOOLEAN wascounting = KeSetTimerEx(timer, duetime, 0, NULL);

I will explain the new parameters of this function extension later in this chapter.

Even if the timer starts counting down, it is still in a non-signal State until it reaches the specified time. At that time, the timer object automatically changes to the signal state, and all the waiting threads are released.

 

 

 

Synchronous Timer

 

 

Similar to the event object, the timer object has two forms: Notification mode and synchronization mode. The notification Timer allows any number of waiting threads. The synchronization timer is opposite. It only allows one waiting thread. Once a thread waits on such a timer, the timer automatically enters the non-signal state. To create a synchronization timer, you must use an extended form of initialization function:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);KeInitializeTimerEx(timer, SynchronizationTimer);

SynchronizationTimerIs an enumerated value of the enumeration type TIMER_TYPE. Another enumerated value isNotificationTimer.

If you use the DPC routine on the synchronization timer, You can regard the DPC queuing as an additional task that occurs when the timer expires. That is, when the timer expires, the system sets the timer to the signal state and inserts the DPC object into the DPC queue. When the timer enters the signal state, the blocked thread is released.

 

 

Periodic Timer

 

 

The timer we have discussed so far can only be timed once. By using the timer extension setting function, you can request a periodic Timeout:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER duetime;BOOLEAN wascounting = KeSetTimerEx(timer, duetime, period, dpc);

Here,PeriodIs a period timeout value, in milliseconds (MS ),DpcIs an optional pointer to a KDPC object. This timer is used in the first countdown.DuetimeTime, and then use the period value to repeat the countdown after expiration. To accurately complete periodic timing, you should specify the duetime as the relative time of the same period as the periodic interval parameter. The duetime parameter 0 enables the timer to immediately complete the first Countdown and then start the periodic countdown. Because you do not need to wait for timeout notifications repeatedly, periodic timers are often used with DPC objects.

Cancels a periodic timer.

Before the timer object is out of the defined range, you must callKeCancelTimerCancel any created periodic timers. If this periodic timer has a DPC, you also need to callKeRemoveQueueDpc. Even if you do these two tasks, there may be an unsolved problem. If you areDriverUnloadCanceling the timer in a routine may result in a rare situation: Your driver has been uninstalled, but the instance of the DPC routine is still running on another CPU. This problem can only be solved by the operating system of future versions. You can cancel the timer as early as possible to reduce the possibility of the problem, such as in the irp_mn_remove_device handler.

Example

A kernel timer is used to provide cyclic timing for system threads that regularly Detect Device activity. Few devices need follow-up services today, but you may encounter exceptions. I will discuss this topic in Chapter 9. This concept is demonstrated by an example (polling) on the CD. Part of the Code in this example follows the device inspection at a fixed interval. This loop can be broken by the configured kill event, so the program uses the kewaitformultipleobjects function. The actual code is more complex than the following example. The following code snippets mainly focus on timer usage:

VOID PollingThreadRoutine(PDEVICE_EXTENSION pdx){  NTSTATUS status;  KTIMER timer;  KeInitializeTimerEx(&timer, SynchronizationTimer);   <--1  PVOID pollevents[] = {      <--2    (PVOID) &pdx->evKill,    (PVOID) &timer,  };  ASSERT(arraysize(pollevents) <= THREAD_WAIT_OBJECTS);    LARGE_INTEGER duetime = {0};  #define POLLING_INTERVAL 500  KeSetTimerEx(&timer, duetime, POLLING_INTERVAL, NULL);  <--3  while (TRUE)  {    status = KeWaitForMultipleObjects(arraysize(pollevents),  <--4          pollevents,          WaitAny,          Executive,          KernelMode,          FALSE,          NULL,          NULL);    if (status == STATUS_WAIT_0)      break;    if (<device needs attention>)     <--5      <do something>;  }  KeCancelTimer(&timer);  PsTerminateSystemThread(STATUS_SUCCESS);}
  1. Here, we initialize a kernel timer as a synchronization method. It can only be used for one thread, this thread.
  2. We need to provide an array of synchronized object pointers for the KeWaitForMultipleObjects function. The first array element is the kill event object. The other part of the driver may set this object when the system thread needs to exit to terminate the loop. The second array element is the timer object.
  3. The Start cycle timer of the KeSetTimerEx statement. BecauseDuetimeThe parameter is 0, so the timer immediately enters the signal state. Then triggered every 500 milliseconds.
  4. During the inspection cycle, we wait for the timer to expire or the kill event to occur. If we wait for the kill event to end, we exit the loop, perform some cleanup work, and finally terminate the system thread. If the waiting period ends when the timer expires, we will proceed to the next step.
  5. Here, the device driver can perform hardware-related operations.

 

 

Timing Functions

 

 

In addition to kernel timer objects, you can also use two other timer functions, which may be more suitable for you. The first function isKeDelayExecutionThreadYou can call this function at passive_level and give a time interval. This function saves you trouble when using a timer, such as creating, initializing, setting, and waiting for operations.

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);LARGE_INTEGER duetime;NSTATUS status = KeDelayExecutionThread(WaitMode, Alertable, &duetime);

Here,WaitMode,Alertable, And function return code and KeWaitXxxThe corresponding part has the same meaning.DuetimeIt is also the same time expression type used in the kernel timer.

You can callKeStallExecutionProcessorAt any IRQL level:

KeStallExecutionProcessor(nMicroSeconds);

This delay aims to allow the hardware to prepare for the next operation before the program continues. The actual latency may be much longer than the request time, because KeStallExecutionProcessor can be preemptible by other activities running at a higher IRQL level, but cannot be preemptible by the same IRQL level.

 

 

Kernel Thread Synchronization

 

 

The Process Structure component of the operating system provides some routines that the WDM driver can use to create and control kernel threads. These routines can help drivers periodically review devices, I will discuss these routines in Chapter 9. For the sake of integrity, I would like to mention it here first. IfXxxIf a kernel thread object is specified in the call, your thread will be blocked until the kernel thread ends running. The kernel thread callsPsTerminateSystemThreadThe function terminates itself.

To wait for the end of a kernel thread, you should first obtain a KTHREAD object (opaque object) pointer. Internally, this object is used to represent the kernel thread, but there is still a problem here, when you are running in the context of a thread, you can easily obtain the KTHREAD pointer of the current thread:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);PKTHREAD thread = KeGetCurrentThread();

Unfortunately, when you callPsCreateSystemThreadWhen creating a new kernel thread, you can only obtain the opaque handle of the thread. To obtain the KTHREAD Object Pointer, you must use the Object Manager service function:

HANDLE hthread;PKTHREAD thread;PsCreateSystemThread(&hthread, ...);ObReferenceObjectByHandle(hthread,     THREAD_ALL_ACCESS,     NULL,     KernelMode,     (PVOID*) &thread,     NULL);ZwClose(hthread);

ObReferenceObjectByHandleThe function converts the handle you provide into a pointer pointing to the underlying kernel object. With this pointer, you can callZwCloseClose the handle. In some places, you still need to callObDereferenceObjectThe function releases a reference to this thread object.

ObDereferenceObject(thread);

Thread vigilance and APC

Internally, the Windows NT kernel sometimes uses thread alert (thread alert) to wake up a thread. This method uses APC (asynchronous process call) to wake the thread to execute some special routines. Support routines used to generate vigilance and APC are not output to WDM driver developers. However, since the DDK document and header file have many references to this concept, I would like to discuss it here.

When someone calls KeWaitXxxWhen a routine is blocking a thread, you must specify a Boolean parameter, which indicates whether the wait is vigilant ). A vigilant wait can be completed in advance, that is, it does not need to meet any waiting conditions or timeout, only because the thread is vigilant. Thread guard native API functions originating in user modeNtAlertThread. If the kernel returns a special status value STATUS_ALERTED because it is alert to wait for early termination.

The APC mechanism enables the operating system to execute a function in the context of a specific thread. The asynchronous mechanism of APC means that the system can effectively interrupt the target thread to execute an external routine. The APC action is a bit like a hardware interrupt that makes the processor jump suddenly from any current code to ISR, Which is unpredictable.

APC comes from three sources: user mode, kernel mode, and special kernel mode. The user mode code calls the Win32 API function.QueueUserAPCRequest a user mode APC. The kernel mode code requests an APC by calling an undisclosed function, and the function has no prototype in the DDK header file. Some reverse engineers may already know the name of the routine and how to call it, but this function is indeed only used internally, so I will not discuss it here. The system puts APC into a special thread until the execution conditions are met. The applicable execution conditions depend on the APC type, as shown below:

  • The special kernel APC is executed as quickly as possible, as long as there are schedulable activities at the APC_LEVEL. In many cases, special kernel APC can even wake up blocked threads.
  • The common kernel APC is executed only when all the special APC programs are executed and the target thread is still running, and no other Kernel Mode APC is executed in this thread.
  • User Mode APC is executed only after all kernel mode APC is executed, and only when the target thread has an alert attribute.

If the system wakes up the thread to submit an APC, the wait primitive function that causes the thread to block will return a special status value STATUS_KERNEL_APC or STATUS_USER_APC.

 

 

APC and I/O requests

 

 

The kernel uses the APC concept for multiple purposes. Since this book only discusses writing drivers, I only explain the relationship between APC and I/O operations. In some cases, when a user mode program performs synchronization on a handleReadFileDuring the operation, the Win32 subsystem callsNtReadFile(Although not public, but widely known) Kernel Mode routines. This function creates and submits an IRP to the appropriate device driver, while the driver usually returns STATUS_PENDING to indicate that the operation is not complete. NtReadFile then returns this status code to ReadFile, so ReadFile callsNtWaitForSingleObjectFunction, which causes the application to wait on the file object to which the user mode handle points. NtWaitForSingleObject then calls KeWaitForSingleObject to execute an unguarded user mode wait and wait on an event object within the file object.

When the device driver completes the read operation, it calls the IoCompleteRequest function, which then queues A special Kernel Mode APC. The APC routine then calls the KeSetEvent function to enable the file object to enter the signal State, so that the application is released and can continue to be executed. Sometimes, after an I/O request is completed, other tasks, such as buffer replication, must be executed in the address context of the request thread, therefore, other types of APC are required. If the request thread is not in the prepare wait state, the kernel mode APC is required. If the thread is not suitable for running when the APC is submitted, a special APC is required. In fact, APC routines are used to wake up threads.

The kernel mode routine can also call the NtReadFile function. But the driver should callZwReadFileFunction substitution, which uses the same System Service Interface as the user mode program to reach NtReadFile (note that the NtReadFile function is not disclosed to the device driver ). If you follow the limits of DDK to call the ZwReadFile function, your call to NtReadFile is almost no different from the call in user mode. There are only two differences. First, the ZwReadFile function is smaller, and any wait will be completed in the kernel. Another difference is that if you callZwcreatefileIf the synchronization operation is specified for the function, the I/O manager automatically waits for your read operation to complete. This wait can be either vigilant or not, depending on the actual options you specified in the ZwCreateFile call.

 

 

How to specify Alertable and WaitMode Parameters

 

 

Now you have enough background information to learn aboutAlertableAndWaitmodeParameters. As a general rule, you must never write code that synchronously responds to user mode requests. This can only be done for identified I/O control requests. Generally, it is better to suspend long-time operations (return the STATUS_PENDING code from the dispatch routine) and complete them asynchronously. In addition, you should not call the wait primitive as soon as it comes up. Thread blocking is only applicable to some parts of the device driver. The following sections describe these areas.

Kernel threadSometimes, when your device needs periodic check, you need to create your own kernel mode thread.

Process PNP requestsI will discuss in Chapter 6 how to handle the I/O requests sent to you by the PnP Manager. There are several PnP requests that need to be processed synchronously on the driver side. In other words, you pass these requests to low-level drivers and wait for them to complete. You will call the KeWaitForSingleObject function and wait in kernel mode, because the PnP Manager calls your driver in kernel mode thread context. In addition, if you need to execute a secondary request as part of a processing PnP request, such as communicating with a USB device, you should wait in kernel mode.

Process other I/O requestsWhen you are processing other types of I/O requests, and you know that they are running in a non-arbitrary thread context, you must carefully consider before action, if you are sure that the thread can be blocked, you should wait in the handler mode where the caller is. In most cases, you can useRequestormodeDomain. You can also callExgetpreviusmodeTo determine the previous processor mode. If you are waiting in user mode and allow the user program to call QueueUserAPC to terminate the waiting in advance, you should execute a blocking wait.

The last thing I want to mention is to wait in user mode and allow APC interruption in user mode. You should use disruptive wait.

The bottom line is: use non-disruptive wait unless you know the reason for not doing so.

The following is the synchronization object in MFC and its usage. It is for reference only.

ClassCsemaphoreThe object represents a "signal light": A synchronization object that allows a limited number of threads in one or more processes to access a certain resource. A CSemaphore object maintains the number of processes currently accessing a resource. Traffic signals are usually used to control access to shared resources that only support a limited number of users. The current value of the CSemaphore object counter indicates the number of users allowed to use the shared resources it protects. When this number reaches 0, all operations that attempt to access protected resources are put into a system queue waiting until the Count value rises above 0 or the wait times out.

ClassCeventAn object represents an "Event": A synchronization object used to notify another thread of an event. Events are usually used by threads to know when to execute their tasks. For example, the thread that copies data to a file needs to be notified when the data is ready. The CEvent object can be used to notify the replication thread that the data has been valid, so that the thread can execute its tasks as soon as possible. There are two types of CEvent objects: manual and automatic. The manual CEvent object will stay in the SetEvent or ResetEvent setting State unless you set it again. The automatic CEvent object automatically returns to the non-signal state (invalid) after a thread is released ).

ClassCmutexAn object represents a "mutex": A synchronization object that allows a thread to access a resource in an exclusive manner. Mutex can be used for resources that only one thread can access at a time. For example, the process of adding a contact to a linked list is to allow only one thread to execute at a time. By using CMutex objects to control the linked list, only one thread can access the linked list at a time.

ClassCcriticalsectionThe object represents a "key segment": A synchronization object, representing a resource or code segment that only one thread can access at a time. For example, add a node to the linked list. The linked list controlled by the CCriticalSection object allows only one thread to access the linked list at a time. If you focus on execution speed and protected resources are not used across processes, you can also use key segments instead of mutex.

In this section, we want the timer to trigger a DPC routine. With this method, No matter what priority your thread has, it will respond to the timeout event. (Because the thread can only wait at the PASSIVE_LEVEL level, and after the timer reaches the time, the thread that obtains CPU control is random. However, DPC routines are executed at the IRQL level, which can effectively lead all threads)

We use the same method to initialize the timer object. In addition, we initialize another KDPC object, which should be allocated in non-Paging memory. The following code is used:

PKDPC dpc;  //  points to KDPC you've allocatedASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);KeInitializeTimer(timer);KeInitializeDpc(dpc, DpcRoutine, context);

Use KeInitializeTimer or KeInitializeTimerEx to initialize the timer object.DpcroutineIs the address of a DPC (deferred process call) routine, which must exist in non-Paging memory.ContextA parameter is an arbitrary 32-bit value (type: PVOID) that is passed as a parameter to the DPC routine.DPCThe parameter is a pointer to a KDPC object (the object must be in Non-Paging memory. For example, in your device extension ).

When the timer countdown starts, we specify the DPC object as a parameter of the KeSetTimer or KeSetTimerEx function:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);LARGE_INTEGER duetime;BOOLEAN wascounting = KeSetTimer(timer, duetime, dpc);

The difference between this KeSetTimer call and the previous call is that we specify a DPC object address in the last parameter. When the timer time is reached, the system will queue the DPC and execute it immediately as long as the conditions permit. In the worst case, it is as fast as waking up a thread at PASSIVE_LEVEL. DPC functions are defined as follows:

VOID DpcRoutine(PKDPC dpc, PVOID context, PVOID junk1, PVOID junk2){  ...}

You can call KeWait even if you provide dpc parameters for the KeSetTimer or KeSetTimerExXxxThe function allows you to wait at PASSIVE_LEVEL. In a single CPU system, the DPC will be executed before completion because it runs on a higher IRQL.

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.