天書夜讀:從組合語言到Windows核心程式設計筆記(2)

weixin_34391854發表於2011-07-11

核心執行緒

在驅動中生成的執行緒一般是系統執行緒。系統執行緒所在的程式名為“System”。

NTSTATUS

PsCreateSystemThread(

  OUT PHANDLE ThreadHandle,                          //用來返回控制程式碼,放入一個控制程式碼指標即可

  IN ULONG DesiredAccess,                               //一般總是填寫0

  IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,     //NULL

  IN HANDLE ProcessHandle OPTIONAL,             //NULL

  OUT PCLIENT_ID ClientId OPTIONAL,              //NULL

  IN PKSTART_ROUTINE StartRoutine,              //用於該執行緒啟動的時候執行的函式,傳入函式名即可

  IN PVOID StartContext);                               //用於傳入該函式的引數

執行緒的結束應該線上程中自己呼叫PsTerminateSystemThread來完成。此外得到的控制程式碼也必須要用ZwClose來關閉。關閉控制程式碼並不結束執行緒。

例子:

  VOID MyThreadProc(PVOID context)             //我的執行緒函式,傳入一個引數,這個引數是一個字串

  {  

      PUNICODE_STRING str = (PUNICODESTRING)context;

      KdPrint(("PrintInMyThread:%wZ\r\n",str));

      PsTerminateSystemThread(STATUS_SUCCESS);          //終止一個執行緒

  }

  VOID MyFunction()

  {

    UNICODE_STRING str = RTL_CONSTANT_STRING(L"Hello!");

    HANDLE thread = NULL;

    status = PsCreateSystemThread(&thread,0L,NULL,NULL,NULL,MyThreadProc,(PVOID)&str);         //開始一個執行緒

    if(!NT_SUCCESS(status))

    {

      //錯如處理

    }

    //如果成功了,可以繼續做自己的事,之後得到的控制程式碼要關閉

    ZwClose(thread);

  }

  注意  :MyThreadProc執行的時候,MyFunction可能已經執行完畢了,str就會無效,再執行K的Print去列印str一定會藍屏。解決方法是在隊中分配str的空間,或者str必須在全域性空間中。

睡眠

#define DELAY_ONE_MICROSECOND (-10)        //10個100納秒 = 1 微秒, -10表示相對時間

#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)

VOID MySleep(LONG msec)

{

  LARGE_INTEGER my_interval;

  my_interval.QuadPart = DELAY_ONE_MILLISECOND;

  my_interval.QuadPart *= msec;

  KeDelayExecutionThread(KernelMode,       //表示在核心程式設計中使用

                0,       //是否允許執行緒報警(用於重新喚醒)

                &my_interval); //表示要睡眠多久

}

事件

  核心中的事件是一個資料結構。這個結構的指標可以當作一個引數傳入一個等待函式中。如果這個事件不被“設定”,則這個等待函式不會返回,這個執行緒被阻塞。如果這個事件被“設定”,則等待結束,可以繼續下去。

  事件不需要銷燬。

  可以發現,關於事件的操作這一部分(如事件的重設,同步等),其原理和MFC中是很類似的,只是MFC中封封裝了更好處理的API函式而已。

  實際上等待執行緒結束並不一定要用事件。執行緒本身也可以當作一個事件來等待。

 例子:

  KEVENT event;                                                                  //定義一個事件

  KeInitializeEvent(&event,SyschronizationEvent,TRUE);            //事件初始化,SynchoronizationEvent為“自動重設”事件:只有一個執行緒的wait可以通過,通過之後被自動重設,其他的執行緒就只能繼續等待了;若為NotificationEvent,這個事件必須手動重設KeResetEvent(&event)之後才能使用,否則所有等待執行緒都通過了。

  KeWaitForSingleObject(&event,Executive,KernelMode,0,0);     //等待這個事件event,直到這個事件被人設定

  

  KeSetEvent(&event);                                                      //在另一個地方,設定這個事件,前面等待的地方將繼續執行

驅動與裝置和請求處理

#include <ntddk.h>

NTSTATUS

  DriverEntry (

    IN PDRIVER_OBJECT DriverObject,

    IN PUNICODE_STRING RegistryPath

    )

{

  NTSTATUS status = STATUS_UNSUCCESSFUL;

  return status;

}

  函式DriverEntry是每個驅動程式中必須的。如同Win32應用程式裡的WinMain。

  DriverEntry的第一個引數就是一個 DRIVER_OBJECT的指標。這個DRIVER_OBJECT結構就對應當前編寫的驅動程式。其記憶體是Windows系統已經分配的。

  第二個引數RegistryPath是一個字串。代表一個登錄檔子鍵。這個子鍵是專門分配給這個驅動程式使用的。用於儲存驅動配置資訊到登錄檔中。

  

  DRIVER_OBJECT中含有分發函式指標,分發函式指標的個數為IRP_MJ_MAXIMUM_FUNCTION,儲存在一個陣列中。這些函式用來處理髮到這個驅動的各種請求。Windows總是自己呼叫DRIVER_OBJECT下的分發函式來處理這些請求。所以編寫一個驅動程式,本質就是自己編寫這些處理請求的分發函式。

NTSTATUS

    DriverEntry (

      IN PDRIVER_OBJECT DriverObject,

      IN PUNICODE_STRING RegistryPath

    )

{

  ULONG i;

  for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;++i)

  {

    DriverObject->MajorFunctions[i] = MyDispatchFunction;                //分發函式定義,所有的分發函式指標都指向一個分發函式MyDispatchFunction中

  }

}

NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp)               //處理髮送給device的irp請求

{

……

}

VOID MyDriverUnload(PDRIVER_OBJECT driver)

{

……

}

這個函式的地址設定到DriverObject->DriverUnload即可。

裝置與符號連結


如果驅動程式要和應用程式之間通訊,則應該生成裝置。此外還必須為裝置生成應用程式可以訪問的符號連結(就像檔案路徑一樣)

“\\.\”意味後面是一個符號連結名。 目前生成裝置,請總是生成在\Device\目錄下。

例子:

#include  <ntifs.h>        

NTSTATUS DriverEntry(

  PDRIVER_OBJECT driver,

  PUNICODE_STRING reg_path)

{

  NTSTATUS status;

  PDEVICE_OBJECT device;

  UNICODE_STRING device_name = RTL_CONSTANT_STRING("\\Device\\MyCDO");         //裝置名

  UNICODE_STRING symb_link = RTL_CONSTANT_STRING("\\DosDevices\\MyCDOSL");         //符號連結名

  status = IoCreateDevice(                                     //生成裝置,這個裝置必須用系統許可權才能訪問,IoCreateDeviceSecure可以產生使用者許可權可以訪問的裝置

    driver,                                                         //生成裝置的驅動物件

    0,      //當使用者需要在裝置上記錄一些額外的資訊,就要指定裝置擴充套件區記憶體的大小,以後就可以從DeviceObject->DeviceExtension中來獲取這些資訊了

    device_name,                                             //裝置名字

    FILE_DEVICE_UNKNOWN,                            //裝置型別

    0,

    FALSE,

    &device);

  if(!NT_SUCCESS(status))

    return status;

  status = IoCreateSymbolicLink(

    &symb_link,

    &device_name);

  if(!NT_SUCCESS(status))

  {

    IoDeleteDevice(device);

    return status;

  }

  device->Flags &= ~DO_DEVICE_INITIALIZENG;         //裝置生成之後,開啟初始化完成標記

  return status;

}

符號連結與使用者相關性

  某個使用者穿件的符號連結只能被該使用者訪問,系統建立的符號連結可以被所有使用者訪問。下面的例子生成的符號連結總是隨時可以使用:

UNICODE_STRING device_name;

UNICODE_STRING symbl_name;

if(IoIsWdmVersionAvailable(1,0x10))

{

  RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\Global\\SymbolicLinkName"); 

         //如果是支援符號連結使用者相關性的版本的系統,一個使用者產生的符號連結只能被這一個使用者訪問,其它使用者則不能訪問,所以必須產生全域性符號

}

else

{

  RtlInitUnicodeString(&symbl_name,L"\\DosDevices\\SymbolicLinkName");    //如果系統不支援符號連結的使用者相關性,則直接建立符號連結即可,所有使用者都可以訪問

}

IoCreateSymbolicLink(&symbl_name,&device_name);

請求處理

  應用程式為了和驅動通訊,首先必須開啟裝置。然後傳送或者接收資訊。最後關閉它。這至少需要三個IRP:第一個是開啟請求。第二個傳送或者接收資訊。第三個是關閉請求。

  IRP的種類取決於主功能號。

  應用層呼叫的API 驅動層收到的IRP主功能號-----即DRIVER_OBJECT中分發函式指標陣列中的索引。

  IRP的主功能號在IRP的當前棧空間中;IRP總是傳送給一個裝置棧,到每個裝置上的時候擁有一個“當前棧空間”來儲存在這個裝置上的請求資訊。

  這些功能都有應用層API引發,對應關係如下:

    CreateFile         IRP_MJ_CREATE

    CloseHandle         IRP_MJ_CLOSE

    DeviceIoControl  IRP_MJ_DEVICE_CONTROL

    ReadFile      IRP_MJ_READ

    WriteFile      IRP_MJ_WRITE

返回時一個IRP成功是一個三部曲

  (1)設定irp->IoStatus.Information為0;

  (2)設定irp->IoStatus.Status的狀態;成功或者失敗

  (3)呼叫IoCompleteRequest(irp,IO_NO_INCREMENT),這個函式完成IRP;

  最後返回irp->IoStatus.Status即可。

開啟和關閉請求

  下面的函式能夠實現開啟和關閉請求:

  NTSTATUS

  MyCreateClose(

    IN PDEVICE_OBJECT device,

    IN PIRP irp)

  {

    irp->IoStatus.Information = 0;

    irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(irp,IO_NO_INCREMENT);

    return irp->IoStatus.Status;

  }

  驅動的分發函式設定如下:

    DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose;

    DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;

應用層資訊的傳入

  可以使用WriteFile或者DeviceIoControl(雙向的);

  DeviceIoControl:裝置控制介面,可以傳送一個帶有特定控制碼的IRP,同時提供輸入和輸出緩衝區;應用程式可以定義一個控制碼,然後把相應的引數填寫在輸入緩衝區中,同時可以從輸出緩衝區得到返回的更多資訊。

  驅動獲得一個DeviceIoControl產生的IRP的時候,需要獲得當前的控制碼、輸入輸出緩衝區的位置和長度;控制碼必須預先用一個巨集定義:

  #define MY_DVC_IN_CODE \

    (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, \

      0xa01,  \             //自定義的值,其它照抄即可

      METHOD_BUFFERED,  \

      FILE_READ_DATA|FILE_WRITE_DATA)

  可以通過裝置的棧空間獲得三個要素。

驅動層資訊的傳出   

  應用程式開啟一個執行緒,通過呼叫DeviceIoControl(或者ReadFile)實現該功能,驅動沒有訊息的時候,則阻塞這個IRP的處理,等待有訊息的時候返回。

  具體實現見《天書夜讀:從組合語言到windows核心開發》第92-93頁。

關於上述幾項內容的專題論述,請參見相關文件。[5,6]

參考
[1] http://www.cnblogs.com/phinecos/archive/2009/02/19/1393803.html
[2] http://www.cnblogs.com/qsilence/archive/2009/06/11/1501511.html
[3 http://msdn.microsoft.com/en-us/library/ff557565%28VS.85%29.aspx
[4] http://www.cnblogs.com/wanghao111/archive/2009/05/25/1489041.html
[5] Windows驅動程式設計基礎教程.doc
[6] 天書夜讀-從組合語言到windows核心程式設計(改)
[7] Windows DDK

[8] 天書夜讀——從組合語言到Windows核心程式設計
http://download.csdn.net/source/2754275
http://msdn.microsoft.com/en-us/library/ff557573%28VS.85%29.aspx

相關文章