驅動程序和客戶應用程序經常需要進行數據交換,但我們知道驅動程序和客戶應用程序可能不在同一個地址空間,因此操作系統必須解決兩者之間的數據交換。
驅動層和應用層通信,主要是靠DeviceIoControl函數,下面是該函數的原型:
BOOL DeviceIoControl (
HANDLE hDevice, // 設備句柄
DWORD dwIoControlCode, // IOCTL請求操作代碼
LPVOID lpInBuffer, // 輸入緩沖區地址
DWORD nInBufferSize, // 輸入緩沖區大小
LPVOID lpOutBuffer, // 輸出緩沖區地址
DWORD nOutBufferSize, // 輸出緩沖區大小
LPDWORD lpBytesReturned, // 存放返回字節數的指針
LPOVERLAPPED lpOverlapped // 用於同步操作的Overlapped結構體指針
);
dwIoControlCode
要進行操作的控制碼。驅動程序可以通過CTL_CODE宏來組合定義一個控制碼,並在IRP_MJ_DEVICE_CONTROL的實現中進行控制碼的操作。在驅動層,irpStack->Parameters.DeviceIoControl.IoControlCode表示了這個控制碼。
IOCTL請求有四種緩沖策略,下面一一介紹。
1、 輸入輸出緩沖I/O(METHOD_BUFFERED)
2、 直接輸入緩沖輸出I/O(METHOD_IN_DIRECT)
3、 緩沖輸入直接輸出I/O(METHOD_OUT_DIRECT)
4、 上面三種方法都不是(METHOD_NEITHER)
為了對這些類型更詳細的描述,請看msdn上的解釋,我抄錄如下:
"緩沖"方法(METHOD_BUFFERED)
備注:在下面的討論中,"輸入"表示數據從用戶模式的應用程序到驅動程序,"輸出"表示數據從驅動程序到應用程序。
對於讀取請求,I/O 管理器分配一個與用戶模式的緩沖區大小相同的系統緩沖區。IRP 中的 SystemBuffer 字段包含系統地址。UserBuffer 字段包含初始的用戶緩沖區地址。當完成請求時,I/O 管理器將驅動程序已經提供的數據從系統緩沖區復制到用戶緩沖區。對於寫入請求,會分配一個系統緩沖區並將 SystemBuffer 設置為地址。用戶緩沖區的內容會被復制到系統緩沖區,但是不設置 UserBuffer。對於 IOCTL 請求,會分配一個容量大小足以包含輸入緩沖區或輸出緩沖區的系統緩沖區,並將 SystemBuffer 設置為分配的緩沖區地址。輸入緩沖區中的數據復制到系統緩沖區。UserBuffer 字段設置為用戶模式輸出緩沖區地址。內核模式驅動程序應當只使用系統緩沖區,且不應使用 UserBuffer 中存儲的地址。
對於 IOCTL,驅動程序應當從系統緩沖區獲取輸入並將輸出寫入到系統緩沖區。當完成請求時,I/O 系統將輸出數據從系統緩沖區復制到用戶緩沖區。
"直接"方法(METHOD_IN/OUT_DIRECT)
對於讀取和寫入請求,用戶模式緩沖區會被鎖定,並且會創建一個內存描述符列表 (MDL)。MDL 地址會存儲在 IRP 的 MdlAddress 字段中。SystemBuffer 和 UserBuffer 均沒有任何含義。但是,驅動程序不應當更改這些字段的值。
對於 IOCTL 請求,如果在 METHOD_IN_DIRECT 和 METHOD_OUT_DIRECT 中同時有一個輸出緩沖區,則分配一個系統緩沖區(SystemBuffer 又有了地址)並將輸入數據復制到其中。如果有一個輸出緩沖區,且它被鎖定,則會創建 MDL 並設置 MdlAddress。UserBuffer 字段沒有任何含義。
"兩者都不"方法(METHOD_NEITHER)
對於讀取和寫入請求,UserBuffer 字段被設置為指向初始的用戶緩沖區。不執行任何其他操作。SystemAddress 和 MdlAddress 沒有任何含義。對於 IOCTL 請求,I/O 管理器將 UserBuffer 設置為初始的用戶輸出緩沖區,而且,它將當前 I/O 棧位置的 Parameters.DeviceIoControl.Type3InputBuffer 設置為用戶輸入緩沖區。利用該 I/O 方法,由驅動程序來確定如何處理緩沖區:分配系統緩沖區或創建 MDL。
通常,驅動程序在訪問用戶數據時不應當將 UserBuffer 字段用作地址,即使當用戶緩沖區被鎖定時也是如此。這是由於在調用驅動程序時,在系統中可能看不到調用用戶的地址空間。(對於該規則的一個例外是,在最高層驅動程序將 IRP 向下傳遞到較低層的驅動程序之前,它可能需要使用 UserBuffer 來復制數據。)如果使用"直接"或"兩者都不"方法,在創建 MDL 之后,驅動程序可以使用 MmGetSystemAddressForMdl 函數來獲取有效的系統地址以訪問用戶緩沖區。
在驅動層,依傳輸類型的不同,輸入緩沖區的位置亦不同,見下表。
傳輸類型 位置
METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer
在驅動層,依傳輸類型的不同,輸出緩沖區的位置亦不同,見下表。
傳輸類型 位置
METHOD_IN_DIRECT irp->MdlAddress
METHOD_OUT_DIRECT irp->MdlAddress
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irp->UserBuffer
所以只要確定了傳輸方式后,就可以根據各自的位置來讀取和寫入數據,從而實現應用層和驅動的通信。
下面看驅動層對ioctl控制碼的處理代碼:
代碼:
驅動層和應用層通信,主要是靠DeviceIoControl函數,下面是該函數的原型:
BOOL DeviceIoControl (
HANDLE hDevice, // 設備句柄
DWORD dwIoControlCode, // IOCTL請求操作代碼
LPVOID lpInBuffer, // 輸入緩沖區地址
DWORD nInBufferSize, // 輸入緩沖區大小
LPVOID lpOutBuffer, // 輸出緩沖區地址
DWORD nOutBufferSize, // 輸出緩沖區大小
LPDWORD lpBytesReturned, // 存放返回字節數的指針
LPOVERLAPPED lpOverlapped // 用於同步操作的Overlapped結構體指針
);
dwIoControlCode
要進行操作的控制碼。驅動程序可以通過CTL_CODE宏來組合定義一個控制碼,並在IRP_MJ_DEVICE_CONTROL的實現中進行控制碼的操作。在驅動層,irpStack->Parameters.DeviceIoControl.IoControlCode表示了這個控制碼。
IOCTL請求有四種緩沖策略,下面一一介紹。
1、 輸入輸出緩沖I/O(METHOD_BUFFERED)
2、 直接輸入緩沖輸出I/O(METHOD_IN_DIRECT)
3、 緩沖輸入直接輸出I/O(METHOD_OUT_DIRECT)
4、 上面三種方法都不是(METHOD_NEITHER)
為了對這些類型更詳細的描述,請看msdn上的解釋,我抄錄如下:
"緩沖"方法(METHOD_BUFFERED)
備注:在下面的討論中,"輸入"表示數據從用戶模式的應用程序到驅動程序,"輸出"表示數據從驅動程序到應用程序。
對於讀取請求,I/O 管理器分配一個與用戶模式的緩沖區大小相同的系統緩沖區。IRP 中的 SystemBuffer 字段包含系統地址。UserBuffer 字段包含初始的用戶緩沖區地址。當完成請求時,I/O 管理器將驅動程序已經提供的數據從系統緩沖區復制到用戶緩沖區。對於寫入請求,會分配一個系統緩沖區並將 SystemBuffer 設置為地址。用戶緩沖區的內容會被復制到系統緩沖區,但是不設置 UserBuffer。對於 IOCTL 請求,會分配一個容量大小足以包含輸入緩沖區或輸出緩沖區的系統緩沖區,並將 SystemBuffer 設置為分配的緩沖區地址。輸入緩沖區中的數據復制到系統緩沖區。UserBuffer 字段設置為用戶模式輸出緩沖區地址。內核模式驅動程序應當只使用系統緩沖區,且不應使用 UserBuffer 中存儲的地址。
對於 IOCTL,驅動程序應當從系統緩沖區獲取輸入並將輸出寫入到系統緩沖區。當完成請求時,I/O 系統將輸出數據從系統緩沖區復制到用戶緩沖區。
"直接"方法(METHOD_IN/OUT_DIRECT)
對於讀取和寫入請求,用戶模式緩沖區會被鎖定,並且會創建一個內存描述符列表 (MDL)。MDL 地址會存儲在 IRP 的 MdlAddress 字段中。SystemBuffer 和 UserBuffer 均沒有任何含義。但是,驅動程序不應當更改這些字段的值。
對於 IOCTL 請求,如果在 METHOD_IN_DIRECT 和 METHOD_OUT_DIRECT 中同時有一個輸出緩沖區,則分配一個系統緩沖區(SystemBuffer 又有了地址)並將輸入數據復制到其中。如果有一個輸出緩沖區,且它被鎖定,則會創建 MDL 並設置 MdlAddress。UserBuffer 字段沒有任何含義。
"兩者都不"方法(METHOD_NEITHER)
對於讀取和寫入請求,UserBuffer 字段被設置為指向初始的用戶緩沖區。不執行任何其他操作。SystemAddress 和 MdlAddress 沒有任何含義。對於 IOCTL 請求,I/O 管理器將 UserBuffer 設置為初始的用戶輸出緩沖區,而且,它將當前 I/O 棧位置的 Parameters.DeviceIoControl.Type3InputBuffer 設置為用戶輸入緩沖區。利用該 I/O 方法,由驅動程序來確定如何處理緩沖區:分配系統緩沖區或創建 MDL。
通常,驅動程序在訪問用戶數據時不應當將 UserBuffer 字段用作地址,即使當用戶緩沖區被鎖定時也是如此。這是由於在調用驅動程序時,在系統中可能看不到調用用戶的地址空間。(對於該規則的一個例外是,在最高層驅動程序將 IRP 向下傳遞到較低層的驅動程序之前,它可能需要使用 UserBuffer 來復制數據。)如果使用"直接"或"兩者都不"方法,在創建 MDL 之后,驅動程序可以使用 MmGetSystemAddressForMdl 函數來獲取有效的系統地址以訪問用戶緩沖區。
在驅動層,依傳輸類型的不同,輸入緩沖區的位置亦不同,見下表。
傳輸類型 位置
METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer
在驅動層,依傳輸類型的不同,輸出緩沖區的位置亦不同,見下表。
傳輸類型 位置
METHOD_IN_DIRECT irp->MdlAddress
METHOD_OUT_DIRECT irp->MdlAddress
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irp->UserBuffer
所以只要確定了傳輸方式后,就可以根據各自的位置來讀取和寫入數據,從而實現應用層和驅動的通信。
下面看驅動層對ioctl控制碼的處理代碼:
代碼:
//METHOD_OUT_DIREC方式 NTSTATUS COMM_DirectOutIo(PIRP Irp,
PIO_STACK_LOCATION pIoStackIrp,
UINT *sizeofWrite) {
NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID pInputBuffer, pOutputBuffer;
ULONG outputLength, inputLength;
DbgPrint("COMM_DirectOutIo\r\n");
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
inputLength = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
pInputBuffer = Irp->AssociatedIrp.SystemBuffer; pOutputBuffer = NULL;
if(Irp->MdlAddress)
pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if(pInputBuffer && pOutputBuffer) { DbgPrint("COMM_DirectOutIo UserModeMessage = '%s'", pInputBuffer);
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
*sizeofWrite = outputLength;
status = STATUS_SUCCESS;
}
return status;
}
// METHOD_IN_DIRECT
NTSTATUS COMM_DirectInIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite) {
NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID pInputBuffer, pOutputBuffer;
ULONG outputLength, inputLength;
DbgPrint("COMM_DirectInIo\r\n");
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
inputLength = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
pOutputBuffer = NULL;
if(Irp->MdlAddress) pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if(pInputBuffer && pOutputBuffer) { DbgPrint("COMM_DirectInIo UserModeMessage = '%s'", pInputBuffer);
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
*sizeofWrite = outputLength;
status = STATUS_SUCCESS;
}
return status;
}
// METHOD_BUFFERED
NTSTATUS COMM_BufferedIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite) {
NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID pInputBuffer, pOutputBuffer;
ULONG outputLength, inputLength;
DbgPrint("COMM_BufferedIo\r\n");
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
inputLength = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;
if(pInputBuffer && pOutputBuffer)
{
DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
*sizeofWrite = outputLength;
status = STATUS_SUCCESS;
}
return status;
}
// METHOD_NEITHER
NTSTATUS COMM_NeitherIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite) {
NTSTATUS status = STATUS_UNSUCCESSFUL;
PVOID pInputBuffer, pOutputBuffer;
ULONG outputLength, inputLength;
DbgPrint("COMM_NeitherIo\r\n");
outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
inputLength = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
pInputBuffer = pIoStackIrp->Parameters.DeviceIoControl.Type3InputBuffer;
pOutputBuffer = Irp->UserBuffer;
if(pInputBuffer && pOutputBuffer) {
DbgPrint("COMM_NeitherIo UserModeMessage = '%s'", pInputBuffer);
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
*sizeofWrite = outputLength;
status = STATUS_SUCCESS;
}
return status;
}