驅動和應用層的三種通訊方式
驅動程式和客戶應用程式經常需要進行資料交換,但我們知道驅動程式和客戶應用程式可能不在同一個地址空間,因此作業系統必須解決兩者之間的資料交換。
驅動層和應用層通訊,主要是靠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)
(因為METHOD_IN_DIRECT 和 METHOD_OUT_DIRECT 是類似的,所以也可以說是三種。)
為了對這些型別更詳細的描述,請看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;
}
程式碼比較簡單,都是取得輸入的資料,然後把資料直接拷貝到輸出,傳輸給應用層。
應用層的程式碼:
procedure TfrmMain.Send_Recv_Data(AInData: String; var AOutData:String;
IoctlCode: DWORD);
var
dwReturn: DWORD;
inData:array[0..1023] of char;
outData:array[0..1023] of char;
begin
StrPCopy(inData, AInData);
if m_hCommDevice <> 0 then
begin
DeviceIoControl(m_hCommDevice, IoctlCode, @inData, Length(inData), @outData, Length(outData), dwReturn, nil);
AOutData := StrPas(@outData);
end;
end;
上面是進行傳送和接受的過程。
需要通訊,只要如下做:
程式碼:
procedure TfrmMain. btnDirect_IN_IOClick (Sender: TObject);
var
outData:String;
begin
Send_Recv_Data(Trim(edtDirect_in_in.Text), outData, IOCTL_COMM_DIRECT_IN_IO);
edtDirect_in_out.Text := outData;
end;
這是 direct_in方式通訊,其他通訊方式類似,大家可以參考程式碼了,這裡就不列舉了,由於程式碼比較簡單,我就不多說了,大家還是看程式碼吧,很好明白。最後,給個測試圖:
應用層:
驅動層:
相關文章
- 使用者層與驅動層通訊
- 主機和虛擬機器的三種通訊方式虛擬機
- Vue元件之間通訊的三種方式Vue元件
- Activity與Service通訊的方式有三種:
- 應用層和驅動如何判斷當前的啟動模式模式
- SAP Fiori應用的三種部署方式
- 啟動另外的一個應用程式的Activity(三種方式)
- vue通訊的N種方式Vue
- Activity和Service跨程式通訊的兩種方式
- 驅動開發:透過應用堆實現多次通訊
- 驅動學習之驅動和應用的介面
- redis的php驅動兩種方式RedisPHP
- 如何解決資料驅動帶來強互動和深層次通訊的痛點
- 程式間的幾種通訊方式
- 幾種程式間的通訊方式
- 程式間的8種通訊方式
- 網路通訊與行動式應用驅動SRAM技術發展
- React中元件通訊的幾種方式React元件
- C#winform和php通訊的一種方式request payloadC#ORMPHP
- 移動應用開發者必讀:提升應用效能的13種方式
- Android 三種播放視訊的方式Android
- iOS之視訊的三種播放方式iOS
- React的6種通訊方式(附帶例子)React
- Vue3 的8種元件通訊方式Vue元件
- 程式間的五種通訊方式介紹
- iOS App間常用的五種通訊方式iOSAPP
- React中元件間通訊的幾種方式React元件
- 樂訊通雲通訊:物聯網路卡在花卉種植業的應用
- 載入驅動三種execute
- 盤點使 macOS 應用流量通過代理的多種方式Mac
- flowable 啟動流程的三種方式
- 三大硬核方式揭秘:Java如何與底層硬體和工業裝置輕鬆通訊!Java
- IPA加驅動的一種方式,未驗證
- nginx與php-fpm通訊的兩種方式NginxPHP
- vue3 常用的幾種元件通訊方式Vue元件
- NIO框架之MINA原始碼解析(三):底層通訊與責任鏈模式應用框架原始碼模式
- 【張龍】加快Flex應用啟動速度的5種方式Flex
- 資料驅動決策的13種思維方式