驅動和應用層的三種通訊方式

whatday發表於2013-08-02

驅動程式和客戶應用程式經常需要進行資料交換,但我們知道驅動程式和客戶應用程式可能不在同一個地址空間,因此作業系統必須解決兩者之間的資料交換。
驅動層和應用層通訊,主要是靠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方式通訊,其他通訊方式類似,大家可以參考程式碼了,這裡就不列舉了,由於程式碼比較簡單,我就不多說了,大家還是看程式碼吧,很好明白。最後,給個測試圖:

應用層:

驅動層:


相關文章