What is a dispatch function:
Dispatch functions are an important concept in WIndows drivers. The main function of the driver is to handle I/O requests, where most of the I/O requests are handled in the dispatch function. In other words, the dispatch function is used to process the I/O request submitted by the driver.
So what is an I/O request?
When communication occurs between the upper program and the driver, the upper layer issues an I/O request (input/output request package)
The I/O requests between the user mode (upper layer) and all drivers are converted from the operating system to a data structure called an IRP, and different IRP are dispatched to different dispatch functions (Dispatch function).
What is an IRP:
In the WIndows kernel, there is a data structure called an IRP (I/O Request package). It is an important data structure related to input and output. When the upper-level application communicates with the underlying driver, that is, the communication between the. SYS and the, the application issues an I/O request. The operating system translates I/O requests into corresponding IRP data structures, and different types of IRP are passed to different dispatch functions depending on the type.
The two basic properties of the IRP, MajorFunction and Minorfunction, record the main type and subtype of the IRP, respectively. The operating system dispatches the IRP to a different dispatch function according to MajorFunction, and in the dispatch function you can continue to judge what kind of minorfunction the IRP belongs to.
Before entering the DriverEntry (drive entry function), the operating system fills the entire MajorFunction array with the address of the _iopinvaliddevicerequest (IOP Invalid device request).
Therefore, when using the dispatch function, the operation type of the dispatch function must be registered in the driver's entry function DriverEntry function.
Such as:
#definePagedcode cod_seg ("page")//Note that it is Pagedcode cod_seg, the name is wrong to write blue screen#pragmaPagedcodeVOID Unload (Pdriver_object pdriverobject);extern "C"NTSTATUS DriverEntry (pdriver_object pdriverobject, punicode_string punicodestring) { /////////////////////////////Register dispatch functions and Types////////////////////////////Pdriverobject->majorfunction[irp_mj_create] =ddk_dispatchfunction; Pdriverobject->majorfunction[irp_mj_read] =ddk_dispatchfunction; Pdriverobject->majorfunction[irp_mj_write] =ddk_dispatchfunction; Pdriverobject->majorfunction[irp_mj_close] =ddk_dispatchfunction; Pdriverobject->majorfunction[irp_mj_device_control] =ddk_dispatchfunction; /////////////////////////////Register dispatch functions and Types//////////////////////////// returnstatus_success;}
1. The relationship between IRP and dispatch function
The processing mechanism of the IRP is similar to the "message processing" mechanism in WIndows applications, and after the driver receives different types of IRP, it enters into a different dispatch function, in which the IRP is processed.
1.1. IRP Type
Related functions of file I/O, such as CreateFile, ReadFile, WriteFile, CloseHandle, etc., will cause the operating system to produce
Irp_mj_create 0x00//createfile;
Irp_mj_close 0x02//closehandle;
Irp_mj_read 0x03//readfile;
Irp_mj_write 0x04//writefile;
Irp_mj_device_control 0x0e//deviceiocontrol
The IRP is transmitted to the driver's dispatch function, for different IRP.
Some of the IRP is created by a component of the system, such as Irp_mj_shutdown, which is issued when the Plug and Play component of Windows is about to shut down the system.
1.2. Simple handling of dispatch functions
Most of the IRP originates from the file's I/O processing Win32 API, such as CreateFile, ReadFile, and so on. The simplest way to handle these IRP is to set the state of the IRP to success in the corresponding dispatch function, then end the request for the IRP and let the dispatch function return successfully. The request to end the IRP uses IoCompleteRequest.
VOID IoCompleteRequest (
In PIRP IRP,//IRP required to be ended
In CChar Priorityboost//priority level for thread recovery
);
To explain the concept of precedence, you need to understand the internal operating procedures of the WIn32 API associated with file I/O. Here, for example, the internal operation of the ReadFile is generally ReadFile:
A), ReadFile calls the Ntreadfile in Ntdll. Where the ReadFile function of the Win32 API, and the Ntreadfile function is Native API
b), ntreadfile in Ntdll enters kernel mode and calls the Ntreadfile function in the system service.
c), the system service function Ntreadfile creates an IRP of the Irp_mj_write type, and then it sends the IRP to a driver's dispatch function. Ntreadfile then waits for an event, when the current thread enters the "sleep" state, or the current thread is blocked or the thread is in the "Pending" state.
d), in the dispatch function generally will end the IRP request, the end of the IRP is through the IoCompleteRequest function. The inside of the function sets the event that was just waiting, and the "sleep" thread resumes running.
When reading a large file or device, ReadFile does not return immediately, but waits for some time. This is the time that the current thread "sleeps". The IRP request is ended, marking the end of the operating system, when the "sleep" thread is awakened.
The Priorityboost parameter of the IoCompleteRequest function represents a priority that refers to the priority level at which the blocked thread resumes running. In general, the priority is set to Io_no_increment (INCREMENT increment). For some special cases, the blocked thread needs to be resumed as a "priority".
1.3. Open the device via device connection
To open a device, you must pass the device's name to get a handle to the device. The alias cannot be queried by the application in user mode, and the device name intelligence is queried by the program in kernel mode. In the application, the device can be accessed through symbolic links, and the driver creates symbolic links through the Iocreatesymbliclink function.
1.4. Write a more general dispatch function
The IRP is sent to the top level of the device stack by the operating system, and if the dispatch function of the top-level device object ends the request for the IRP, the IO request ends and if the request for the IRP is not terminated, then the operating system forwards the IRP to the next layer of equipment on the device stack for processing. If the dispatch function for this device still cannot end the IRP request, continue forwarding to the underlying device.
Therefore, an IRP may be forwarded multiple times. In order to record the operation of an IRP in each layer of equipment, the IRP will have an array of io_stack_location (stacks, positions). The number of elements in the array should be greater than the number of devices traversed by the IRP. Each io_stack_location element records the actions that are made in the corresponding device. For the corresponding io_stack_location of this layer device, it can be obtained by iogetcurrentirpstacklocation (get the position of the current IRP in the stack) function. The type of IRP is recorded in the io_stack_location structure, which is the majorfunction subdomain in io_stack_location.
2, buffer mode read and write operation
Drivers created by the device generally have three ways to read and write, one is the buffer mode, one is the direct way, one is the other way. The following describes the buffer read and write:
2.1. Buffer Device
When the driver creates the device object, you need to consider what kind of reading and writing the device is using. When IoCreateDevice is finished creating the device, the flags (flags) subdomain of the device object needs to be set. Setting different flags causes the device to be manipulated in different ways. The three different values for Flags are: do_buffered_io (buffered), Do_direct_io (direct), and 0.
Read and write operations are generally caused by ReadFile or WriteFile functions, which are described in the WriteFile function as an example: WriteFile asks the user to provide a buffer, declares the buffer size, and then WriteFile The data for this memory is passed into the driver.
This buffer memory is a user-mode memory address, and it is dangerous for the driver to consume the memory directly, because the Windows operating system is multitasking, and it may switch to another process at any time. If the driver needs to access this memory, then the operating system may have switched to another process. If this is the case, the memory address that the driver accesses must be wrong, and this error can cause the system to crash.
One workaround is for the operating system to replicate data from the application-supplied buffers to addresses in kernel mode. This way, the kernel-mode address does not change regardless of how the operating system switches processes. The dispatch function of the IRP will be the buffer operation in kernel mode, not the user-mode address. However, it is necessary to replicate data between user mode and kernel mode, which affects the efficiency of operation.
2.2. Buffer device reading and writing
When a device is written in "buffer" mode, the operating system copies the buffer provided by the user mode to the kernel-mode address WriteFile. This address is created by WriteFile for the IRP AssociatedIrp.SystemBuffer (Federated IRP. System buffer) subdomain record.
When a device is read in "buffer" mode, the operating system allocates memory for a period of kernel mode. This memory size is equal to the number of bytes specified by ReadFile or WriteFile, and the associatedirp.systembuffer subdomain of ReadFile or WriteFile created IRP records the memory address. When the IRP request ends, the memory address is copied to the buffer provided by the ReadFile.
Data replication of the user-mode address and the kernel-mode address occurs in the buffer mode whether the device is read or written. The process of copying is the responsibility of the operating system. The user-mode address is provided by ReadFile or WriteFile, and the kernel-mode address is assigned and recycled by the operating system. The Parameters.Read.Length subdomain in io_stack_location knows how many bytes ReadFile request, and is known by the Parameters.Write.Length subdomain in io_stack_location WriteFile the number of bytes requested.
However, WriteFile and ReadFile specify how many bytes to operate on the device and do not really mean to manipulate so many bytes. In the dispatch function, the subdomain iostatus.information (IO return value) of the IRP should be set. This subdomain records how many bytes the device actually operates.
NTSTATUS Helloddkread (in Pdevice_object pdevobj,in pirp pirp)
{
NTSTATUS status = Status_success;
Get the current stack
Pio_stack_location STACK = iogetcurrentirpstacklocation (PIRP);
Get the number of bytes needed to read the device
ULONG ulreadlength = stack->parameters.read.length;
Complete IRP
Pirp->iostatus.status = Status;
Set how many bytes the IRP operates
Pirp->iostatus.information = Ulreadlength;
Set the buffer in kernel mode
memset (pirp->associatedirp.systembuffer,0xaa,ulreadlength);
Processing IRP
IoCompleteRequest (pirp,io_no_increment);
return status;
}
The application uses ReadFile to read and write to the device:
#include <windows.h>
#include <stdio.h>
int main ()
{
Open the device handle
HANDLE hdevice = CreateFile ("\\\\.\\HELLODDK", generic_read| Generic_write,0,null,open_existing,file_attribute_normal. NULL);
Determine if Open is successful
if (Hdevice = = INVALID_HANDLE_VALUE)
{
Open failed
return 1;
}
UCHAR BUFFER[10];
ULONG Ulread;
Read and write to device
BOOL BRet = ReadFile (hdevice,buffer,10,&ulread,null);
if (BRet)
{
printf ("Read%d bytes:", ulread);
for (int i=0;i< (int) ulread;i++)
{
printf ("%02x", Buffer[i]);
}
}
Close the device handle
CloseHandle (Hdevice);
return 0;
}
2.3, buffer device simulation file read and write
A), write operation. In the application, the device is written using the WriteFile function.
The write operation of the application.
UCHAR BUFFER[10];
memset (buffer,0xbb,10);
ULONG Ulread;
ULONG Ulwrite;
BOOL BRet;
Write operations to devices
BRet = WriteFile (hdevice,buffer,10,&ulwrite,null);
if (BRet)
{
Successfully writes Ulwrite to the device
}
The WriteFile internally creates an IRP of the Irp_mj_write type, which the operating system passes to the driver. The dispatch function of the irp_mj_write needs to save the transmitted data in order to read the device. In this case, the data is stored in a buffer, and the address of the buffer is recorded in the device extension. The driver is responsible for allocating this buffer when the device is started, and the driver reclaims the buffer when the device is unloaded.
For Irp_mj_write dispatch functions, the primary task is to store the written data in this buffer. If the number of bytes written is too large to exceed the size of the buffer, the dispatch function sets the state of the IRP to an error state. In addition, there is a variable in the device extension that records the file length of the virtual file device. Writing to the device changes the variable.
Write operations in the driver
NTSTATUS helloddkwrite (in Pdevice_object pdevobj,in pirp pirp)
{
NTSTATUS status = Status_success;
Pdevice_extension Pdevext = (pdevice_extension) pdevobj->deviceextension;
Pio_stack_location STACK = iogetcurrentirpstacklocation (PIRP);
Gets the length of the store
ULONG ulwritelength = stack->parameters.write.length;
Gets the offset of the store
ULONG Ulwriteoffset = (ULONG) stack->parameters.write.byteoffset.quadpart;
if (ulwriteoffset+ulwritelength>max_file_length)
{
If the storage length + offset is greater than the buffer length, the returned value is invalid
status = Status_file_invalid;
ulwritelength = 0;
}
Else
{
Data to be written, stored in buffer
memcpy (pdevext->buffer+ulwriteoffset,pirp->associatedirp.systembuffer,ulwritelength);
status = Status_success;
Set a new file length
if (ulwritelength+ulwriteoffset>pdevext->file_length)
{
Pdevext->file_length = Ulwritelength+ulwriteoffset;
}
}
Pirp->iostatus.status = status;//Setting the completion state of the IRP
pirp->iostatus.information = ulwritelength;//actual operation how many bytes
IoCompleteRequest (pirp,io_no_increment);//End IRP Request
return status;
}
b), read operation. To read data from the device through ReadFile in the application:
BRet = ReadFile (hdevice,buffer,10,&ulread,null);//read 10 bytes from the device
if (BRet)
{
displaying the data read
for (int i=0;i< (int) ulread;i++)
{
printf ("%02x", Buffer[i]);
}
}
The ReadFile internally creates an IRP of the Irp_mj_read type, which the operating system passes the IRP to the dispatch function in the driver irp_mj_read. The primary task of the Irp_mj_read dispatch function is to copy the recorded data into the AssociatedIrp.SystemBuffer.
NTSTATUS Helloddkread (in Pdevice_object pdevobj,in pirp pirp)
{
Pdevice_extension Pdevext = (pdevice_extension) pdevobj->deviceextension;
NTSTATUS status = Status_success;
Pio_stack_location STACK = iogetcurrentirpstacklocation (PIRP);
ULONG ulreadlength = stack->parameters.read.length;
ULONG Ulreadoffset = (ULONG) stack->parameters.read.byteoffset.quadpart;
if (ulreadoffset+ulreadlength>max_file_length)
{
status = Status_file_invalid;
ulreadlength = 0;
}
Else
{
Store data in AssociatedIrp.SystemBuffer so that applications can use
memcpy (pirp->associatedirp.systembuffer,pdevext->buffer+ulreadoffset,ulreadlength);
status = Status_success;
}
Setting the IRP completion status
Pirp->iostatus.status = Status;
Sets the number of IRP operations bytes
Pirp->iostatus.information = Ulreadlength;
End IRP
IoCompleteRequest (pirp,io_no_increment);
return status;
}
c), read the file length. The read file length depends on the GetFileSize Win32 API. An IRP of type irp_mj_query_information is created inside the getfilesize. The purpose of this IRP request is to query the device for some information, including the length of the query file, the time the device was created, the device properties, and so on. In this example, the primary task of the Irp_mj_query_information dispatch function is to tell the application the length of the device.
3. Direct mode reading and writing operation
3.1, direct reading equipment
and the buffer mode read and write device, the direct way to read and write the device, the operating system will lock the buffer in user mode, and then operate the operating system to map this buffer in the kernel mode address again. In this way, user-mode buffers and kernel-mode buffers point to the physical memory of the same region. The kernel-mode address remains the same regardless of how the operating system switches processes.
When the operating system first locks the address of the user mode, the operating system records the memory with the Memory Description table (MDL data structure). This buffer of user mode is contiguous in virtual memory, but the physical memory may be discrete.
MDL records This virtual memory, the size of this virtual memory is stored in the Mdl->bytecount, the virtual memory of the first page address Mdl->startva, the initial address of this virtual memory for the first page address offset is mdl-> Byteoffset. Therefore, the first address of this virtual memory should be mdl->startva+mdl->byteoffset.
3.2. Read and write the device directly
The application calls the Win32 API ReadFile, and the operating system forwards irp_mj_read to the appropriate dispatch function. The dispatch function obtains the length of this read by reading the stack->parameters.read.length of the IO stack. This length is the third parameter of the ReadFile function nnumberofbytetoread
Dispatch function Shengze Pirp->iostatus.information tells ReadFile how many bytes actually read, this number corresponds to the fourth parameter of ReadFile
BOOL ReadFile (
HANDLE hfile,//File Handle
Lpvoce lpbuffer,//Buffer
DWORD nnumberofbytestoread,//the number of bytes to read
Lpdword lpnumberofbytesread,//number of bytes actually read
lpoverlapped LPOVERLAPPED//OVERLAP Data Structure address
);
4. Other ways to read and write operations
4.1, other means of equipment
The dispatch function directly reads and writes the buffer address provided by the application when using other methods to read and write the device. This is only possible if the driver is running in the same thread context as the application.
When read and write in other ways, the buffer memory address provided by ReadFile or WriteFile can be obtained through the Pirp->userbuffer field of the IRP in the dispatch function. The number of bytes read can be obtained from the Stack->parameters.read.length field in the I/O stack. Use user-mode memory with extreme caution, because ReadFile may pass null pointer addresses or illegal addresses to the driver. Therefore, before the driver uses the user-mode address, it needs to detect if the memory is readable or writable, and the Probeforwrite function and try block should be used when probing.
5. IO Device Control operation
In addition to using ReadFile and WriteFile, applications can also operate devices through another Win32 API DeviceIoControl. DeviceIoControl internally causes the operating system to create an IRP of the Irp_mj_device_control type, and the operating system forwards the IRP to the dispatch function.
Programmers can use DeviceIoControl to define operations other than read and write, which allows applications and drivers to communicate. For example, to initialize a device, the programmer customizes an I/O control code and then passes the control code and the request to the driver with DeviceIoControl. In the dispatch function, the different I/O control codes are processed separately.
5.1, DeviceIoControl and drive interaction
BOOL DeviceIoControl (
HANDLE hdevice,//already open device
DWORD dwiocontrolcode,//Control Code
LPVOID lpinbuffer,//Input Buffer
DWORD ninbuffersize,//Input buffer size
LPVOID lpoutbuffer,//Output Buffer
DWORD noutbuffersize,//Output Buffer size
Lpdword lpbytesreturned,//Actual number of bytes returned
lpoverlapped lpoverlapped//whether overlap operation
);
The second parameter of the DeviceIoControl is the I/O control code, which is also called the IOCTL value, which is a 32-bit unsigned integer. The IOCTL needs to comply with DDK regulations:
DDK intentionally provides a macro ctl_code:
Ctl_code (devicetype,function,method,access)
DeviceType: The type of device object that matches the type that was created when the device was iocreatedevice. A macro that is generally shaped like a file_device_xx
Function: This is the driver-defined IOCTL code. which
0x0000 to 0X7FFF: reserved for Microsoft
0x800 to 0XFFF: defined by the programmer himself
Method: This is the operating mode, can be one of the following four modes:
Method_buffered: operation with buffer mode.
Method_in_direct: Use direct mode operation.
Method_out_direct: Use direct mode operation. Read-only opening will fail
Method_neither: Use other methods.
5.2. Buffer Memory Mode IOCTL
When the Ctl_code macro defines this ioctl, the method parameter should be set to method_buffered. As mentioned many times before, it is best not to directly access the memory address in user mode in the driver, the buffer mode can avoid the programmer accessing memory address in memory mode.
The dispatch function first obtains the current I/O stack (io_stack_location) through the Iogetcurrentirpstacklocation function. The dispatch function obtains the output buffer size through stack->parameters.deviceiocontrol.outputbufferlength. Finally, the IOCTL is obtained through Stack->parameters.deviceiocontrol.iocontrolcode. The different ioctl are handled separately in the dispatch function through the switch statements in the C language.
5.3. Direct Memory Mode IOCTL
When defining this IOCTL with the Ctl_code macro, you should specify that the method parameter is Method_out_direct or Method_in_direct. The direct mode IOCTL also avoids the driver's access to the memory address of the user mode.
When DeviceIoControl is called, the contents of the input buffer are copied to the Pirp->associatedirp.systembuffer memory address in the IRP, and the number of bytes copied is specified as the number of bytes entered in DeviceIoControl. However, for the processing of the output buffers specified by DeviceIoControl, the IOCTL of the direct mode IOCTL and the buffer pattern are handled differently. The operating system locks out the output buffers established by the DeviceIoControl and then re-maps a section of addresses under the kernel-mode address.
The pirp->mdladdress record in the IRP structure in the dispatch function DeviceIoControl the specified output buffer. The dispatch function should use Mmgetsystemaddressformdlsafe to map this memory to the memory address in kernel mode.
6. Summary
This chapter highlights the dispatch function in the driver that handles IRP requests. All operations on the device will eventually be converted to an IRP request, which will be routed to the dispatch function for processing. This chapter mainly introduces the dispatching functions of Irp_mj_read, Irp_mj_write and Irp_mj_device_control. These IRP requests have the buffer mode, the direct way, and the other way of operation respectively. Among them, the buffer mode and the direct way is often used in driver development.
IRP and Dispatch function