System interrupts load the processor: what to do. Deferred Procedure Call (DPC) Deferred Procedure Call


Common operating room problem Windows systems any edition is the loading of computer resources by “internal” processes. One such process is a system interrupt, which can seriously load the computer's resources, which will be displayed in the Task Manager. The most common situation is when a system interrupt loads the processor, causing the computer to seriously lose performance. In this article, we will look at why this happens and whether it is possible to disable system interrupts in Windows.

System interrupts: what is this process

The System Interrupts process is constantly running by default in the Windows operating system, but during normal operation it should not load system components by more than 5%. If this process has a more serious impact on computer resources, this indicates the presence of a hardware problem, namely a malfunction of one of the computer components.

When “System Interrupts” load the processor, this may indicate a problem with the video card, motherboard, RAM or other element of the system unit. The central processor tries to supplement the missing power resulting from the malfunction of the component using its own resources, as evidenced by the “System Interrupts” process. Most often, the problem of computer components not working correctly is associated with complete or partial incompatibility running program(or games) with computer component drivers.

How to disable system interrupts

As noted above, system interrupts are nothing more than an indicator that Windows is making additional access to resources central processor. It is not possible to disable system interrupts to improve computer performance, and you need to look for a problem in the operation of PC components. To do this, it is convenient to use the DPC Latency Checker application, which can be downloaded for free on the Internet from the developers’ website. The program allows you to identify faulty computer components.

To diagnose your system with the DPC Latency Checker application, launch it and wait. It will take some time to check the computer, after which the user will see on the graph if there are problems in the operation of system components. The application will also indicate possible errors and advise you to look for them by disconnecting the devices.

To do this, go to "Device Manager" by clicking right click mouse to “Start” and selecting the appropriate item, and start disconnecting devices one by one. After each shutdown, check in the Task Manager and the DPC Latency Checker application to see if the problems with CPU load due to system interrupts have been resolved. If the problem persists, turn the device back on and move on to the next one.

Important: While disabling components in Device Manager, do not disable Computer, Processor, and System Devices, otherwise this will cause the computer to reboot unexpectedly.

When a device is found that, when disconnected, will reduce the CPU load to normal condition, update the drivers for this component from the official developer website.

Note: If attempts have been made to disable all system components, but the System Interrupts process continues to load the system, try updating the processor drivers.

In a situation where the tips given above do not help to cope with the problem of processor loading by system interrupts, you can try the following methods to correct the situation:

It is worth noting that you should not disable system interrupts through the Task Manager; this will cause the system to crash, but will not solve the problem.

Control objects include primitive objects for threads, interrupts, timers, synchronization, profiling, and two special facility to implement DPC and APC. DPC (Deferred Procedure Call) objects are used to reduce the execution time of ISR (Interrupt Service Routines), which are triggered by an interrupt from a device. Limiting the time spent on ISR procedures reduces the chance of losing an interrupt.

The system hardware assigns interrupts a hardware priority level. The processor also associates the priority level with the work it is performing. The processor responds only to interrupts that have a higher priority than the one it is using. this moment. The normal priority level (including the priority level of all user mode) is 0. Device interrupts occur at level 3 or higher, and the ISR for a device interrupt typically runs at the same priority level as the interrupt (so that other less important interrupts do not occurred while processing a more important interrupt).

If the ISR takes too long to execute, servicing lower priority interrupts will be delayed, possibly causing data loss or slowing down system I/O. There may be multiple ISRs running at any given time, with each successive ISR originating from interrupts with increasingly higher priority levels.

To reduce ISR processing time, only critical operations such as writing the results of I/O operations and reinitializing the device are performed. Further interrupt processing is deferred until the processor's priority level drops and no longer blocks other interrupts from being serviced. The DPC object is used to represent the work to be done, and the ISR calls the kernel level to put the DPC in the DPC list of a particular processor. If DPC is first in the list, then the kernel registers a special hardware request to interrupt the processor at level 2 (at which NT calls the DISPATCH level). When the last existing ISR completes, the processor's interrupt level drops below 2 and this unlocks the interrupt for DPC processing. The DPC interrupt ISR will process each of the DPC objects (that the kernel has queued).

The technique of using software interrupts to delay interrupt processing is an established technique for reducing ISR latency. UNIX and other systems began using lazy processing in the 1970s (to cope with slow hardware and the limited size of serial terminal buffers). The ISR received characters from the equipment and queued them. After all high-level interrupt processing was completed, the software interrupt would trigger a low-priority ISR to process characters (for example, to implement a cursor move back one position by sending a control character to the terminal to erase the last displayed character, and the cursor would move back) .

A similar example in modern system Windows can serve as a keyboard. After a key is pressed, the keyboard ISR reads the key code from the register and then again enables the keyboard interrupt, but does not process the key immediately. Instead, it uses DPC to queue key code processing (until all device interrupts to be serviced have been processed).

Because DPCs operate at level 2, they do not interfere with the execution of ISRs for devices, but will prevent any threads from executing until all queued DPCs have completed and the processor priority level has dropped below 2. Device drivers and the system should not take too long to execute ISR or DPC. Because threads are not allowed to execute, ISR and DPC can make the system sluggish and cause problems when playing music (by slowing down those threads that write music from the buffer to the audio device). Another common use of DPC is to perform timer interrupt routines. To avoid blocking threads, timer events (which need to run for a long time) must queue requests to a pool of worker threads (which the kernel maintains for background work).

Send your good work in the knowledge base is simple. Use the form below

Students, graduate students, young scientists who use the knowledge base in their studies and work will be very grateful to you.

CALCULATION AND EXPLANATORY NOTE

for a course project on the topic:

Application Profiler

1. Introduction

2. Analytical section

2.1. Technical task

2.2. Windows NT 5.x Architecture Overview

2.3. Driver classification

2.4. General structure of the Legacy driver

2.4.1. DriverEntry procedure

2.4.2. DriverUnload procedure

2.4.3. Workflows for processing IRP packets

2.4.3.1. IRP packet header

2.4.3.2. IRP stack

2.4.3.3. Packet processing function IRP_MJ_CREATE

2.4.3.4. Packet processing function IRP_MJ_CLOSE

2.4.3.5. Packet processing function IRP_MJ_DEVICE_CONTROL

2.4.4. ISR - Interrupt Service Routine

2.4.5. DPC - deferred call procedure

3. Design section

3.1. Legacy driver

3.1.1. DriverEntry procedure

3.1.2. DriverUnload

3.1.3. DispatchCreate and DispatchClose

3.1.4. DispatchDeviceControl

3.2. Custom Application

4. Technical section

4.1. Selecting an operating system and programming environment.

4.2. Interface

4.3. System requirements

5. Conclusion.

6. List of used literature.

1. Introduction

Very often, when developing software, there is a need to monitor its operation: how long its threads are running in kernel mode, how long in user mode, how much time they spend waiting, as well as the number of context switches from one mode to another. All this is important, since each mode has its own characteristics. In kernel mode, code runs faster, but there is a potential for data/system code corruption. Unlike kernel mode, user mode is limited in the services it can provide so that its code cannot crash the system. For the same purpose, additional checks are performed in user mode to prevent the execution of malicious instructions. Therefore, the execution speed of user-mode code is significantly lower. The number of context switches also affects the speed of code execution, since this operation is quite expensive (about 2000 clock cycles). This was clearly noticeable during development laboratory work and a course project in computer graphics: when drawing an image pixel by pixel using the SetPixel function, the drawing speed was disproportionately slower than when using a user mode buffer, into which information about the color of the pixels corresponding to the buffer elements was gradually entered. This was due to the fact that when using the SetPixel function, two context switches occurred (from user mode to kernel mode and back) per pixel, and when using a buffer storing a context-independent representation of color, the same two switches occurred, but once for drawing the whole frame.

Thus, the ability to find out the above statistical information about the target software will allow you to promptly notice the so-called “bottlenecks” in the program that interfere with improving the performance of the application as a whole.

2. Analytical section

2.1 Technical task

The goal of the presented course project is to develop a simple application profiler, which includes:

Legacy driver, which should:

Periodically update information about processes and their flows;

Provide basic information about processes and their threads;

Provide hardware context for the selected thread;

Ensure secure access to this information from multiple user client applications.

A custom application that allows you to:

Correctly install and remove the driver without the need to reboot the system;

Contact the driver with requests to obtain various information.

2.2 Architecture overviewWindows NT 5.x

On Windows, applications are separate from the operating system itself. Its kernel code runs in privileged processor mode ( kernel mode), which provides access to system data and hardware. Application code runs in unprivileged processor mode ( custom mode) with an incomplete set of interfaces, limited access to system data and without direct access to the equipment. When a user-mode program calls a system service, the processor intercepts the call and switches the calling thread to kernel mode. At the end system service the operating system switches the thread's context back to user mode and continues executing it.

The following schematically shows the part of the Windows architecture that is affected by this course project. Components such as system support processes, service processes, environment subsystems, hardware abstraction level, and support for windows and graphics are not specified here.

User applications are a type of user process that runs in user mode, where they are limited to using unprivileged instructions.

DLL subsystems - in Windows custom Applications cannot call operating system services directly; instead, they work with one or more DLL subsystems whose purpose is to translate documented functions into corresponding internal (and usually undocumented) calls to system services.

The executive system contains the core operating system services that provide memory, process and thread management, security, I/O, and inter-process communication.

The kernel contains low-level operating system functions that support, for example, thread scheduling, interrupt and exception dispatching. It also provides a set of procedures and basic objects that the executive system uses to implement higher-level structures.

Drivers -- used for expansion functionality kernels.

2.3 Driver classification

Unlike a user application, a driver is not a process and has no thread of execution. Instead, control is transferred to the driver as a result of an I/O request from a user application or driver, or as a result of an interrupt. In the first case, the driver execution context is precisely known - it is an application program. In the second case, the execution context can be either known or random - it depends on the execution context of the calling driver function. In the third case, the execution context is random, since an interruption (and, accordingly, the execution of driver code) can occur during the execution of any application program.

By location in the driver stack:

Higher-level drivers -- receive requests from the user application and interact with lower-level drivers;

Intermediate drivers -- receive requests from upstream drivers and interact with downstream drivers;

Low-level drivers receive requests from higher-level drivers and carry out final processing of request packets.

Also highlight the concept monolithic driver- a top-level driver that does not interact with any other drivers.

Due to the improvement of the model Windows drivers(WDM - Windows Driver Model), which added support for Plug and Play and Energy Saving Technologies, drivers began to be divided into:

Legacy drivers (Legacy drivers, NT-style drivers) - drivers written in the old style, without support for innovations;

WDM drivers - drivers that satisfy all the requirements of the extended WDM model.

2.4 General structure of the Legacy driver

The Legacy driver has the following main entry points:

DriverEntry - driver loading procedure;

DriverUnload - driver unloading procedure;

Operating procedures for processing IRP packets;

ISR procedure (Interrupt Service Routine) - interrupt processing procedure;

DPC procedure (Deferred Procedure Call) - a deferred call procedure.

2.4.1 DriverEntry procedure

This procedure is present in any driver and is called by the I/O manager when the driver is loaded.

Legacy drivers perform significantly more work in it than WDM drivers, since they are forced to perform the work of the AddDevice procedure, which is mandatory for WDM drivers. In addition to solving initialization tasks and registering entry points for working procedures for processing supported IRP packets and driver unloading procedures, here:

The hardware that the driver will control is determined;

Device objects are created (IoCreateDevice function) for each physical or logical device controlled by this driver;

For devices that should be visible to user applications, symbolic links are created (IoCreateSymbolicLink function);

If necessary, the device is connected to the interrupt object. If the ISR procedure requires the use of a DPC procedure, then the corresponding object is created and initialized at this stage;

Allocating memory required for driver operation.

2.4.2 DriverUnload procedure

The I/O manager calls this procedure when dynamically unloading a driver. This procedure does the opposite of what is done in the DriverEntry procedure.

The following steps are typical for Legacy drivers:

For some types of hardware, it is necessary to save its state in the system registry, because when loading the driver later, this data can be used;

If interrupts are enabled for the device being served, then the unloading procedure must disable them and disconnect from the interrupt object. A situation where a device generates interrupts for a non-existent interrupt object will inevitably lead to a system crash;

Delete a device object (IoDeleteDevice);

Freeing up memory allocated to the driver during operation.

2.4.3 Workflows for processing IRP packets

All functions registered in the DriverEntry procedure by populating the MajorFunction array are called by the I/O Manager to handle appropriate requests from the driver's clients. These requests are always formatted in the form of special data structures - IRP packets, memory for which is allocated by the I/O Manager in the non-paged system pool. The structure of an IRP packet is such that it consists of a header of a fixed size and an IRP stack, the size of which depends on the number of device objects in the stack.

2.4.3.1 IRP packet header. The IRP packet header structure has the following fields:

The IoStatus field of type IO_STATUS_BLOCK contains two subfields:

Status contains the value that the driver sets after processing the packet;

Information most often contains the number of bytes transmitted or received.

The AssociatedIrp.SystemBuffer field of type PVOID contains a pointer to the system buffer in case the device supports buffered I/O;

The MdlAddress field of the PMDL type contains a pointer to the MDL list if the device supports direct input/output;

The UserBuffer field of type PVOID contains the address of the user buffer for I/O;

The Cancel BOOLEAN field is an indicator that the IRP should be canceled.

2.4.3.2 IRP packet stack. The primary purpose of IRP stack cells is to store function code and I/O request parameters. For a request that is addressed to the lowest level driver, the corresponding IRP packet has only one stack location. For a request that is sent to an upper-level driver, the I/O Manager creates an IRP with multiple stack cells, one for each device object.

Each IRP stack cell contains:

MajorFunction of type UCHAR is code that describes the purpose of the operation;

MinorFunction of type UCHAR is a code that describes a sub-code of the operation;

DeviceObject of type PDEVICE_OBJECT is a pointer to the device object to which it was addressed this request IRP;

FileObject of type PFILE_OBJECT - file object for this request;

Parameters of union type - application depends on the MajorFunction value.

The I/O manager uses the MajorFunction field to retrieve the procedure needed to process the request from the MajorFunction array.

Each procedure for processing IRP packets must take as parameters:

Pointer to the device object for which the IRP request is intended;

A pointer to an IRP describing this request;

2.4.3.3 Packet processing function IRP_MJ_CREATE. This function is designed to handle requests for a driver descriptor from user applications or upstream drivers. Typically, this function simply marks the IRP packet as completed.

2.4.3.4 Packet processing function IRP_MJ_CLOSE. This function is designed to handle requests to close a driver handle from user applications or upstream drivers. Typically, this function simply marks the IRP packet as completed.

2.4.3.5 Packet processing function IRP_MJ_DEVICE_CONTROL. This function allows you to process advanced requests from user-mode clients, most often used to exchange data between the application and the driver. Such a request can be generated by calling the DeviceIoControl function from user mode.

IOCTL codes (I/O Control codes) are used here, some of which are predefined by the operating system, and some of which can be created by the driver developer. This code is specified in the request by the I/O Manager when generating the IRP packet.

Driver operations that operate on IOCTL requests often require a buffer area to be specified to accommodate input or output data. A situation is possible when both buffers are used in one request.

The data access method provided by the I/O Manager is defined in the IOCTL code. Such methods could be:

METHOD_BUFFERED: The input user buffer is copied to the system buffer, and when processing is complete, the system buffer is copied to the output user buffer.

METHOD_IN_DIRECT: Required user buffer pages are loaded from disk to RAM and are blocked. Next, using DMA, data is transferred between the device and the user.

METHOD_OUT_DIRECT: The required user buffer pages are loaded from disk into RAM and locked. Next, using DMA, data is transferred between the device and the user.

METHOD_NEITHER: when this method transfer does not check memory availability, does not allocate intermediate buffers, and does not create MDLs. The IRP packet carries the virtual addresses of the buffers in the address space of the I/O request initiator.

In this case, the flags that determine the type of buffering in the device object have no meaning when working with IOCTL requests. The buffered exchange mechanism is defined whenever the IOCTL value is specified in a dedicated portion of this data structure. This approach provides maximum flexibility when working with the DeviceIoControl custom mode call.

From the driver's point of view, buffer areas containing or intended for data are accessed using the following structure fields:

METHOD_IN_DIRECT or METHOD_OUT_DIRECT

Buffer with data

The buffer address in the system address space is specified in pIrp->AssociatedIrp.SystemBuffer

Client virtual address in Parameters. DeviceIoControl. Type3InputBuffer

The length is specified in Parameters.DeviceIoControl.InputBufferLength

Buffer for data

Uses buffering (system buffer)

The address of the buffer in the system address space is specified in pIrp->AssociatedIrp. SystemBuffer

Uses direct access, the client buffer is converted to an MDL list, the pointer to which is located in pIrp->MdlAddress

Client virtual address in pIrp->UserBuffer

The length is specified in Parameters.DeviceIoControl.OutputBufferLength

2.4.4 ISR - Interrupt Service Routine

The driver registers this function so that it receives control at the moment when the hardware served by the driver sends an interrupt signal. The purpose of this function is to do minimal work and register a deferred call procedure (DPC) to service the interrupt. A kernel interrupt manager call can occur in any context: both the kernel and the user process.

2.4.5 DPC - deferred call procedure

Such routines run at a lower interrupt request level (IRQL) than the ISR, so they don't block other interrupts. They can perform all or complete interrupt servicing work started in the ISR.

3. Design section

This is how the interaction between a user application and the driver through system components looks like:

3.1 Legacy-driver

The Legacy driver of this course project implements the following procedures:

DispatchCreate (processing IRP_MJ_CREATE packet);

DispatchClose (processing IRP_MJ_CLOSE packet);

DispatchDeviceControl (processing IRP_MJ_DEVICE_CONTROL packet).

3.1.1 DriverEntry procedure

Here, typical actions for initializing a driver driver are performed.

Driver entry points are registered:

pDriverObject->DriverUnload = SpectatorDriverUnload;

PDRIVER_DISPATCH * majorFunction = pDriverObject->MajorFunction;

majorFunction[ IRP_MJ_CREATE ] = SpectatorDispatchCreate;

majorFunction[ IRP_MJ_CLOSE ] = SpectatorDispatchClose;

majorFunction[ IRP_MJ_DEVICE_CONTROL ] = SpectatorDispatchDeviceControl;

A device object named DEVICE_NAME is created:

#define DEVICE_NAME L"\\Device\\Spectator"

RtlInitUnicodeString(&deviceName, DEVICE_NAME);

status = IoCreateDevice

sizeof(DEVICE_EXTENSION),

FILE_DEVICE_SPECTATOR,

FILE_DEVICE_SECURE_OPEN,

&pDeviceObject);

A symbolic link SYMBOLIC_LINK is registered for the created device object:

#define SYMBOLIC_LINK L"\\DosDevices\\Spectator"

RtlInitUnicodeString(&symbolicLink, SYMBOLIC_LINK);

status = IoCreateSymbolicLink(&symbolicLink, &deviceName);

A mutex kernel object is created:

NTSTATUS CreateMutex()

(BEGIN_FUNC(CreateMutex);

NTSTATUS status = STATUS_SUCCESS;

status = _ExAllocatePool(g_pMutex, NonPagedPool, sizeof(KMUTEX));

if (NT_SUCCESS(status))

(KeInitializeMutex(g_pMutex, 0);

status = STATUS_SUCCESS;)

END_FUNC(CreateMutex);

return (status);)

For the first time, information about processes and their threads is loaded:

if (LockInfo() == STATUS_SUCCESS)

The LockInfo() and UnlockInfo() functions are simply wrapper functions for the LockMutex() and UnlockMutex() functions, respectively. The first of the last two functions waits on a mutex kernel object.

Kernel objects called mutexes guarantee that threads have mutually exclusive access to a single resource. This is where the name of these objects comes from (mutual exclusion, mutex). They contain a user counter, a recursion counter, and a variable that stores the thread ID. Mutexes behave exactly the same as critical sections. However, while the latter are user-mode objects, mutexes are kernel objects. Additionally, a single mew tex object allows multiple threads to synchronize access to a resource. different processes; In this case, you can set the maximum waiting time for access to the resource.

It is thanks to this mutex that the security requirement is ensured when accessing stored information.

The timer operation is initialized:

A timer is necessary to update the stored information at a certain interval.

To do this, a kernel object “timer” is created:

status = _ExAllocatePool(g_pTimer, NonPagedPool, sizeof(KTIMER));

KeInitializeTimerEx(g_pTimer, SynchronizationTimer);

Comment: memory for kernel objects should be allocated exclusively in the non-page pool ( keyword NonPagedPool).

Timers can be of two types:

SynchronizationTimer -- after a specified time interval or period has passed, it is put into a signal state until one of the threads waiting for it is awakened. Then the timer is switched to a non-signal state.

NotificationTimer -- after the specified time interval or period has expired, it is put into a signal state, and all threads waiting on it are awakened. Such a timer remains in the signaled state until it is explicitly set to non-signaled.

In order to do any useful work on the timer, you need to register the OnTimer() DPC procedure. For it, you need to create your own DPC object, which will be periodically placed in the system-wide queue:

status = _ExAllocatePool(g_pTimerDpc, NonPagedPool, sizeof(KDPC));

KeInitializeDpc(g_pTimerDpc, OnTime, NULL);

Further, due to the fact that in this timer driver actions that require a user context must be performed, they must be taken out of the OnTimer() function, which is a DPC procedure, and therefore, only the system context is available during its execution. However, you must ensure that the required work is done in reasonable synchrony with the time the DPC function object is removed from the queue for processing. To do this, we will create a thread that will be dedicated to waiting for some event:

OBJECT_ATTRIBUTES objectAttributes;

InitializeObjectAttributes(&objectAttributes, NULL, OBJ_KERNEL_HANDLE,

status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, &objectAttributes,

NULL, NULL, UpdateThreadFunc, NULL);

KeInitializeEvent(g_pUpdateEvent, SynchronizationEvent, FALSE);

Comment: Event kernel objects are identical in type to timer kernel objects.

When this event is received, the thread will update system information about the processes and their threads. We will transfer the object of this event to the signal state in the OnTimer() function. This synchronization method made it possible to ensure the implementation necessary actions at a specified interval with an accuracy of up to a millisecond, as follows from the messages below, intercepted by the DebugView program from the debug version of the driver:

0.00075233 ^^^^^^^^ OnTime ^^^^^^^^

0.00116579 ======== LockInfo ========

0.00118814 ======== ReloadInfo ========

0.99727142 ^^^^^^^^ OnTime ^^^^^^^^

1.00966775 ======== LockInfo ========

1.00968981 ======== ReloadInfo ========

1.99729049 ^^^^^^^^ OnTime ^^^^^^^^

2.05610037 ======== LockInfo ========

2.05632067 ======== ReloadInfo ========

2.99727035 ^^^^^^^^ OnTime ^^^^^^^^

2.99741030 ======== LockInfo ========

2.99743295 ======== ReloadInfo ========

3.99727631 ^^^^^^^^ OnTime ^^^^^^^^

3.99739385 ======== LockInfo ========

3.99741673 ======== ReloadInfo ========

4.99728107 ^^^^^^^^ OnTime ^^^^^^^^

4.99742365 ======== LockInfo ========

4.99744749 ======== ReloadInfo ========

5.99728870 ^^^^^^^^ OnTime ^^^^^^^^

5.99742651 ======== LockInfo ========

5.99744844 ======== ReloadInfo ========

Here OnTime is the moment when the OnTimer timer procedure was entered, LockInfo is the moment when the thread responsible for updating the information woke up, ReloadInfo is the moment when the information was actually updated.

As can be seen from the interception, in the first two seconds the periodicity is not at a high level, but then the situation stabilizes and the accuracy improves, as stated, to one millisecond.

After all these actions, the timer finally starts:

LARGE_INTEGER dueTime = RtlConvertLongToLargeInteger(0);

BOOLEAN existed = KeSetTimerEx(g_pTimer, dueTime, g_timerPeriod, g_pTimerDpc);

Here dueTime is the time before the first call to the OnTime() procedure, and g_timerPeriod is the period of further calls.

Finally, in the DriverEntry procedure, the counter of user client applications that received the descriptor of this driver will be reset: pDeviceExtension->clientCount = 0;

Thanks to this one variable, it becomes possible to simultaneously access the driver of several user applications at once. The only limitation for them is exclusivity of access to information about processes and their threads.

3.1.2 DriverUnload

In this procedure, if the number of driver clients is zero, all objects created to organize the operation of the timer are deleted, the mitex, the device object and its symbolic link are deleted. If the number of clients is different from zero, then the driver is not unloaded, since otherwise it will disrupt the normal operation of other user client applications.

3.1.3 DispatchCreate and DispatchClose

These functions take into account the number of open descriptors for a given driver received using the CreateFile() API call. As many handles have been opened, the same number must be closed with the CloseHandle() API call. Otherwise, the driver will remain in the operating system after the user application has finished running, which, of course, is highly undesirable.

3.1.4 DispatchDeviceControl

This procedure serves IOCTL requests from user applications sent by the DeviceIoControl() API call. In this course project, interaction with the driver is mostly based on their use; the main functionality of the driver is implemented here: what it was intended for. That's why this procedure most voluminous.

First, depending on the specific IOCTL request, a pointer to the IRP stack cell of the IRP packet intended for the driver device object is obtained:

PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

In the driver under consideration, all IOCTL requests use a buffered data transfer method, since in all requests their volume is really small. This data transfer approach allocates enough memory in the system nonpage pool to accommodate the larger of the input and output buffers. Before the request begins processing, the contents of the input user buffer are copied to the system buffer, and upon completion from the system buffer - to the output user buffer. Since only one system buffer is used for both user buffers, you need to be careful when processing data, since there is a possibility that when writing, it will damage unread input data and then it will be lost forever.

The lengths (in bytes) of the user buffers, input and output, are retrieved from the Parameters field of the IRP stack cell: Parameters.DeviceIoControl.InputBufferLength and Parameters.DeviceIoControl.OutputBufferLength, respectively. And the system buffer address is extracted from the IRP packet header: AssociatedIrp.SystemBuffer.

Weekendedata: [No]

This IOCTL request is used to contact the driver so that it answers the question whether the request initiator is the only client working with the driver at the moment. This request is sent to the driver by every user application when it is about to exit. If the answer is yes, then the application attempts to terminate the driver, otherwise it simply terminates, confident that there are other clients running on the driver and that the application that terminates last will take care of unloading the driver.

Weekendsedata: [No]

The first IOCTL request from this is used to capture the user application system information for exclusive use. The other is, accordingly, to free up this resource. They simply call the LockInfo() and UnlockInfo() functions of the same name, which were described earlier when talking about the DriverEntry procedure in this section.

Weekendsedata: structure with basic information about the process.

This pair of IOCTL requests allows their initiator to sequentially view structures that describe running processes in the system. Each of them calls the ProcessFirst() and ProcessNext() functions of the same name, respectively. The first function sets the pointer to the first record, and the second moves the pointer to the next one, if there is one. The result of executing each of these functions is a completed structure with information about the process, if the end of the list is not reached. In the event that the end of the list is nevertheless reached, the IRP packet is nevertheless marked as successfully processed, but the value of the number of bytes transferred is set to zero, which allows the user application to correctly recognize this situation and promptly stop sending further IOCTL_PROCESS_NEXT to the driver -requests.

Weekendsedata: a structure with basic information about the thread.

As in the previous paragraph, this pair of IOCTL requests allows their initiator to sequentially view the structures describing the threads of the selected process. The logic for processing these requests is similar to obtaining information about processes.

3.1.4.6 IOCTL_OPEN_THREAD. Inputdata: access rights, unique identifier of the target thread.

Weekendsedata: A handle to the target stream.

When processing this IOCTL request, an attempt is made to open a handle to a thread that has the specified identifier with the rights that were requested by the user client application.

Weekendsedata: [No].

During processing of this IOCTL request, an attempt is made to close a thread handle that was previously opened with an IOCTL_OPEN_THREAD request.

3.1.4.7 IOCTL_GET_THREAD_CONTEXT. Inputdata: hardware context structure, target thread descriptor.

Weekendsedata: hardware context structure.

This IOCTL request takes full advantage of the DeviceIoControl API call, since both input and output buffers are involved here. The input is a structure for the hardware context with an initialized CONTEXT::ContextFlags field indicating which groups of hardware context registers should be returned in this structure upon successful completion of the request. This project queries the entire hardware context.

3.2 Custom Application

The user application includes two classes: CDialog and CDriver. As the names suggest, these classes are respectively responsible for interaction with the user through the application dialog box and interaction with the driver primarily through IOCTL requests.

When a user application instance starts, the first thing it does is try to install the driver, if this has not been done previously by another instance. If the installation caused an error, the user is given a corresponding message, which indicates in text form the reason for its occurrence, if it was provided for, otherwise, its code is simply indicated. The user can request the driver installation again by giving a positive response to the corresponding program offer. This procedure will be repeated until the driver installation is successful or the user refuses to try again.

After this the drop down list is loaded running processes, sorted into alphabetical order by their names, the first process from the list is selected, and its threads are displayed in the second drop-down list. These lists are updated every time the user wants to select a different process or thread because they need the latest information to do so.

This information is obtained through the drivers, as already mentioned, using the DeviceIoControl API call:

BOOL DeviceIoControl

(HANDLE hDevice,

DWORD dwIoControlCode,

LPVOID lpInBuffer, DWORD nInBufferSize,

LPVOID lpOutBuffer, DWORD nOutBufferSize,

LPDWORD lpBytesReturned,

LPOVERLAPPED lpOverlapped);

HANDLE hDevice - a descriptor of the device to which the request is sent;

DWORD dwIoControlCode - IOCTL request code;

LPVOID lpInBuffer - input buffer address;

DWORD nInBufferSize - input buffer length;

LPVOID lpOutBuffer - output buffer address;

DWORD nOutBufferSize - output buffer length;

LPDWORD lpBytesReturned - number of bytes transferred;

LPOVERLAPPED lpOverlapped is a structure required when using asynchronous query execution, which is not present in this application.

The use of this API call is completely encapsulated in the CDriver class, which implements a separate method for each request with a name similar to the name of the IOCTL request, which provides an intuitive understanding of the interface of this class.

This class also encapsulates the use of the Service Control Manager (SCM), which is used to dynamically install, start, stop, and remove the driver.

4. Technical section

4.1 Selecting an operating system and programming environment

Windows was chosen as the operating system. This is due to the fact that the DOS operating system is already outdated for many reasons (we have already moved away from OSes that work in single-tasking mode), and other operating systems for personal machines with good interface There are no truly user-friendly ones yet. Windows is still the most common OS for PCs. In addition, various software development environments are designed specifically for Windows:

Visual C++, Visual Basic, Borland C++ Builder, Delphi and others.

Writing language user program C++ was chosen. The C++ language provides very rich opportunities for programmers and is perhaps the most widespread in their environment. It is a very powerful operator language. In addition, it provides sufficient freedom in writing programs, while Pascal sets very narrow limits, in particular, in the description of variables and does not allow the construction of complex operator expressions. C was chosen as the language for writing the driver. The use of this language ensures portability between systems: the maximum that will have to be done is to rebuild the driver. Microsoft was chosen as the development environment Visual Studio.Net, because it provides powerful and convenient tools not only for visual interface development software product, but also project settings, which allows you to effectively organize your workplace.

4.2 Interface

This is what the window of a custom Profiler application instance looks like:

At the top of the dialog there are two drop-down lists, the top of which displays a list of running processes in the system, and the bottom - a list of threads of this process. These controls can be used to tell an application which process and which thread of that process to monitor.

There are three groups in the dialogue:

Group “Process information”:

ProcessID - process identifier;

ParentID - identifier of the parent process;

BasePriority - default base priority for process threads;

ThreadCount - number of process threads;

KernelTime - total time spent in kernel mode by process threads, 1 unit equals 100 ns;

UserTime - the total time spent in user mode by process threads, 1 unit is equal to 100 ns.

“Stream Information” group:

ThreadID - thread identifier;

BasePriority - base priority of the thread;

Priority - thread priority;

ContextSwitches - the number of context switches performed by the thread;

KernelTime - time spent in kernel mode (1 unit equals 100 ns);

UserTime - time spent in user mode (1 unit equals 100 ns).

WaitTime - the moment in time when the thread entered the waiting state (counting from the moment the system starts).

Thread Context group:

This represents the thread's hardware context. Most applications expect input from the user. When monitoring the threads of such a process, you may not see any changes at all. Therefore, for a more visual overview, it is worth running tasks that require large computational costs. For example, WinAmp, with which you can play music - the thread that is responsible for this is immediately visible by changing general-purpose registers. But the most frequent changes in registers for various purposes occur in truly “heavy” tasks, for example, you can take a course project on Computer Graphics.

4.3 System requirements

The driver is written for Windows NT version 5.x.

Processing of requests from several user client applications has been tested only on Windows XP Service Pack 2.

Conclusion

As a result of work on the project, a custom application was implemented that interacts with the Legacy driver. With its help, it obtains the basic information about the selected process, the basic information and the hardware context of the selected thread of the specified process. This application is the basis for implementing full-fledged application profilers to trace target applications and detect bottlenecks in them, which can significantly increase the efficiency of the programmer and the software he develops.

List of used literature

1. V.P. Soldatov “Windows driver programming.” Ed. 3rd, revised and additional - M.: Binom-Press LLC, 2006 - 576 p.: ill.

2. M. Russinovich, D. Solomon " Internal organization Microsoft Windows: Windows Server 2003, Windows XP and Windows 2000", 4th edition.

3. J. Richter “Windows for professionals: creating effective Win32 applications taking into account the specifics of the 64-bit version of Windows” / Translated, English - 4th ed. - St. Petersburg; Peter; M.: Publishing and trading house "Russian Edition", 2001.

4. Schreiber, Sven B., 1958-Undocumented Windows 2000 secrets: a programmer's cookbook.

5. Garry Nebbett, Windows NT/2000 Native API.

Similar documents

    The main advantages of modular programming. Selection of the procedure: inputting an array from the console, displaying the array, information about the author and the condition of the solved problem before processing and after processing. Hierarchy of procedures, characteristics of the purpose of modules.

    abstract, added 01/29/2016

    Study of the concept, vectors and mechanisms of interrupt handling; their classification depending on the source of origin. Features of the response of the hardware and software parts of the operating system to signals about the occurrence of some event in the computer.

    abstract, added 06/22/2011

    Analysis existing technologies creating web applications. Development network technology publishing and processing information about children in kindergarten No. 176 "Squirrel" using JSP pages and servlets using a JDBC driver to access the database.

    course work, added 12/18/2011

    Development of a data storage and processing system, interface. Using Xamarin.Forms technology to organize filling out waybills. Selecting an operating system, language and programming environment. Hardware integration of the information system.

    thesis, added 07/09/2017

    Principles and algorithms of interrupt handling. A set of actions to implement microprocessor interrupt processing stages. Development of the structure and algorithm of the resident program. Implementation of a program in Assembly language, methods for its debugging and testing.

    course work, added 12/22/2014

    An example of building a program using arithmetic operators. Basic tools for creating a calculator. Procedure for entering numbers. Changed procedure for processing pressing the "+" button. The procedure for opening the Help form, the final result.

    presentation, added 03/02/2012

    Designing an interrupt handling mechanism. Intel 82C59A interrupt controller. Interrupt I/O. Intel 82C55A programmable interface controller. The role of the processor in handling I/O interrupts. Overview of the interrupt handling algorithm.

    test, added 05/19/2010

    Comparison of the results of simulation modeling and analytical calculation of characteristics. Study of the data packet switching node, packet processing in the processor, buffering and transmission along the output line. Determination of processor load factor.

    course work, added 06/29/2011

    Requirements and structure of processing systems economic information. Information processing technology and system maintenance, information protection. The process of creating queries, forms, reports, macros and modules. Tools for organizing databases and working with them.

    course work, added 04/25/2012

    Basic techniques for working in the Delphi programming environment. Features of the technology for creating simple applications. Working with application development environment components. Input, editing, selection and output of information. Aspects of using branching structure.

An important feature of routines executed on interrupt requests is that they perform work that most often has nothing to do with the current process.

For example, a disk driver may receive control after the disk controller has written information received from process A to the appropriate sectors, but this point in time does not coincide with the period of the next iteration of process A or its thread.

In the most typical case, process A will be waiting for the I/O operation to complete (if the operation is synchronous) and the disk driver will interrupt some other process.

In some cases, it is generally difficult to unambiguously determine for which process one or another is performing work. software module OS, such as thread scheduler. Therefore, restrictions are introduced for this kind of procedure - they do not have the right to use resources (memory, open files, etc.) with which the current process is working.

Interrupt routines work with the resources that were allocated to them when the corresponding driver was initialized or the operating system itself was initialized. These resources belong to the OS, not to a specific process. This is how memory is allocated to drivers from the system area. Therefore, it is common to say that interrupt routines operate outside the context of a process.

Interrupt dispatching is an important OS feature, and this feature is implemented in almost all multiprogramming OSes. As a rule, the OS implements a two-level work scheduling mechanism. Top level scheduling is performed by the interrupt manager, which distributes processor time among the stream of incoming interrupt requests various types- external, internal and software. The remaining processor time is allocated by another dispatcher - the thread dispatcher, based on the quantization disciplines and others that we have considered.

System calls

A system call allows an application to contact the OS with a request to perform a particular action, formatted as a procedure (or set of procedures) of the OS code segment.

For an application programmer, the OS looks like a certain library that implements useful features, making it easier to control an application task or perform actions that are prohibited in user mode, such as communicating with an I/O device.

Implementation system calls must meet the following requirements:

· provide switching to privileged mode;

· have a high speed of calling OS procedures;

· provide uniform access to system calls for all hardware platforms on which the OS runs;

· allow easy expansion of the set of system calls;

· provide OS control over the correct use of system calls

In most operating systems, system calls are handled in a centralized manner based on the existence of a system call manager.

For any system call, the application executes a software interrupt with a specific and unique vector number.

Before executing a software interrupt, the application passes the system call number to the OS. The method of transmission is implementation dependent. For example, the number can be placed in a specific processor register or passed through the stack. Also, system call arguments are passed in some way; they can be transferred either to general-purpose registers or passed through a stack or array of RAM.

Procedure address 21h
Procedure address 22h
Procedure address 23h
System call manager address
System Call Manager
Procedure for handling System call 21h
Procedure for processing System call 22h
Procedure for processing System call 23h

After the system call completes, control is returned to the dispatcher, and he also receives the completion code for this call. The dispatcher restores the processor's registers, places a return code in a specific register, and executes a return instruction from an interrupt, which restores the processor's unprivileged mode.

The described tabular method of organizing system calls is accepted in almost all operating systems. It allows you to easily modify the composition of system calls by simply adding to the table new address and expanding the range of acceptable call numbers.

The OS can execute system calls in synchronous or asynchronous modes.

Synchronous system call means that the process that made such a call is suspended until the system call has completed all the required work . After this, the scheduler puts the process in the ready state.

Asynchronous system call does not cause the process to go into sleep mode after completing some initial systemic actions, such as starting an I/O operation, control returns to the application process.

Most system calls in the OS are synchronous.

While kernel mode code is running, which has priority highest value, no other code can run on this processor. Of course, if the volumes are too large program code will be executed at too high IRQL values, this will inevitably lead to general degradation of the system.

To resolve this type of problem, code intended to run in kernel mode must be designed to avoid running at elevated IRQL levels for extended periods of time. One of the most important components of this strategy is Deferred Procedure Calls (DPC) - deferred procedural calls.

Functioning of the DPC

The scheme for using deferred procedural calls allows you to build the execution process in such a way that the task can be planned code running at a high IRQL level, but it has not yet performed . This deferral of execution is useful if an interrupt is being serviced in the driver and there is no reason to block other program code from executing for more than low level IRQL. In other words, when the processing of a given situation can be painlessly postponed to a later time.

To record requests for calling DPC procedures, the operating system maintains a queue of DPC objects.

To begin with, let us limit ourselves to considering more simple case working with DPC procedures intended for use in conjunction with interrupt handling procedures. This type DPC procedures received the special name DpcForIsr in the literature.

A DPC object for use in interrupt routines is created by calling IoInitializeDpcRequest, usually performed in the driver startup procedures. This call registers the procedure offered by the DpcForIsr driver and associates it with the object being created - a fairly common technique in Windows. It should be especially noted that the DPC object created by this call will remain in the depths of the operating system, inaccessible to the driver developer. (The difference between DpcForIsr and other DPC procedures is only that the latter are worked with using calls Ke...Dpc, and the DPC objects created for them are available to the driver developer.)

If the driver has registered its DpcForIsr procedure, then while the ISR interrupt is being processed by the procedure, the corresponding DPC object can be placed in the system DPC queue (in fact, a request to call this DpcForIsr procedure later) - using the call IoRequestDpc. The DpcForIsr procedure will later complete the processing of the received ISR by the request procedure, which will be performed under less critical conditions and at a low IRQL level.

IN general outline, the functioning of DPC procedures (in this case, DpcForIsr) consists of the following operations:

  • When a piece of code running at a high (hardware) IRQL wants to schedule part of its work to be done at a low IRQL, it adds a DPC object to the system's deferred procedural call queue.
  • Sooner or later, the processor's IRQL value drops below DISPATCH_LEVEL, and the work that was delayed by the interrupt is serviced by the DPC function. The DPC manager retrieves each DPC object from the queue and calls the corresponding function, a pointer to which is stored in that object. This function is called while the processor is running at DISPATCH_LEVEL.

Features of the DPC mechanism

Typically, working with deferred procedure calls is not difficult because OS Windows 2000/XP/Server 2003 offer a large set of system calls that hide most of the details of this process. However, two of the most deceptive aspects of working with DPC should be highlighted.

First, Windows NT 5 imposes a limitation that one instance of a DPC object can be placed on the system DPC queue at a given time. Attempts to put an object into the DPC queue that exactly matches one already there are rejected. As a result, only one DPC procedure call occurs, even if the driver is waiting for two calls to be made. This can happen if two interrupts were caused by a serviced device and processing of the first deferred procedure call has not yet begun. The first instance of the DPC object is still in the queue, while the driver has already started processing the second interrupt.

The driver design must provide for such a course of operation of the DPC mechanism. Perhaps an additional DPC request counter should be provided, or the driver could implement its own request queue implementation. While the actual deferred procedure is executing, you can check the counter and its own request queue to determine what specific work should be done.

Secondly, there are significant synchronization problems when running on multiprocessor platforms. Suppose code running on a single processor performs interrupt service and schedules a DPC procedure call (placing a DPC object on the system queue). However, before the interrupt is fully processed, another processor may begin DPC processing an object placed in the system queue. Thus, a situation arises in which the interrupt service code is executed in parallel and simultaneously with the DPC procedure code. For this reason, it is necessary to provide measures to reliably synchronize the access of the DPC procedure program code to resources shared with the driver interrupt service routine.

If you take a closer look at the list of call parameters IoInitializeDpcRequest And IoRequestDpc(intended to work with DpcForIsr procedures), then it is easy to notice that the DPC object is “tied” to the device object. When placing this object in the DPC queue at the time the ISR procedure is running, the device object is also specified. This ensures certainty as to which specific DPC procedure call is “ordered” by the ISR procedure (correlation by device object). This also means that a driver that has created several device objects (a fairly rare case) can also use several DpcForIsr procedures - one for each device object.

The system DPC mechanism prevents simultaneous processing of DPC objects from the system queue, even in a multiprocessor configuration. Thus, if resources are shared by several deferred procedures, then there is no need to worry about synchronizing access to them.

Above we discussed the use of DPC procedures to complete interrupt processing, that is, DpcForIsr. However, DPC procedures can be used in other ways, for example, in conjunction with timers to organize waiting. To do this, create a DPC object by calling KeInitializeDPC, which associates this object with the DPC procedure included in the driver. You can then set the timeout in the pre-initialized (using KeInitializeTimer or KeInitializeEx) timer object. Call is used to set the waiting interval KeSetTimer, which must be passed a pointer to an initialized DPC object as one of its parameters. When the DPC timeout expires, the object will be added to the system DPC queue and the DPC procedure associated with it will be called as soon as possible. DPC procedures of this second type are designated in the DDK documentation by the term “Custom DPC”. (This use of DPC procedures makes them closely resemble user-mode APC calls.)

To place objects corresponding to the second type of DPC procedures (not related to interrupts) in the system DPC queue, use the call KeInsertQueueDpc. Accordingly, the call initiating code must operate at an IRQL level not lower than DISPATCH_LEVEL.

To clear the system DPC queue from Custom DPC procedures, for example, if the driver must urgently shut down, call KeRemoveQueueDpc, which can be called from code at any IRQL level.







2024 gtavrl.ru.