Wednesday, September 26, 2007

學編程的人不能不看的好文章

http://blog.csdn.net/dljhf/archive/2005/06/28/406271.aspx

看完只是覺得, 小子你還太嫩了. 看來是沒學過演算法吧, 連基本的演算法都不知道就開始寫程式, 難怪被定的滿頭包.

而且第一題是很基本的歸納法啊, 高中就學過了, 題目沒看仔細就開始作答了, 難怪不會上. 如果每個大陸人都是這樣, 那我應該還有幾年的飯可以吃.....

Thursday, September 20, 2007

Windows Driver Programming[8]

Synchronous and Asynchronous IRP

IRP 只有一種,就是 IRP。會有 Synchronous 跟 Asynchronous 之分,是源自於 IRP 傳送的方式。如果你寫過 Socket 程式,那你應該會知道 Asynchronous I/O 跟 Synchronous I/O 的概念。 簡言之,便是 Synchronous IRP 一傳送,Driver 就得等這個 IRP 完成才能夠繼續執行,而 Asynchronous IRP 會繼續執行下去。 這個考量在 User mode Application 當然沒問題(其實還是會有啦,不然不會有一堆 Asynchronous API 出來),只要把這些東西放到 Thread 內,然後再用一些同步機制就可以搞定。 但是這一招在 Driver 裡面是不行的,Windows Driver 裡有定義每個函式能夠執行的 IRQL,而跑在 Dispatch Level 之上的 thread,是不能夠使用 Synchronous IRP 的, 因為它們不可以睡覺,而 Synchronous IRP 會讓該 thread 去睡覺。

使用 Asynchronous IRP 也有很大的困擾:同步的問題。在 Driver 裡面同步可不比 Application mode,一不小心就 BSOD 了,麻煩。 另外,Asynchronous IRP 必須在 Driver 內部配置跟釋放,而 Synchronous IRP 則是 Driver 配置,系統(I/O manager)釋放。

在這邊以 USB 當例子。

Allocate IRP

  • Synchronous IRP
  • PIRP IoBuildDeviceIoControlRequest(
     IN ULONG IoControlCode,
     IN PDEVICE_OBJECT DeviceObject,
     IN PVOID InputBuffer OPTIONAL,
     IN ULONG InputBufferLength,
     OUT PVOID OutputBuffer OPTIONAL,
     IN ULONG OutputBufferLength,
     IN BOOLEAN InternalDeviceIoControl,
     IN PKEVENT Event,
     OUT PIO_STATUS_BLOCK IoStatusBlock
    );
    

    IoControlCode:這個 IRP 所代表的操作。如果是 USB 的話,請設定為 IOCTL_INTERNAL_USB_SUBMIT_URB。
    DeviceObject:NdisMGetDeviceProperty() 的 NextDeviceObject 參數
    InputBuffer:Device 要輸出的記憶體位置。
    OutputBuffer:要輸入到 Device 的記憶體位置。
    Event:IRP 完成/取消/錯誤之後要設定的 Event。
    IoStatusBlock:Specifies an I/O status block to be set when the request is completed by lower drivers.

    通常這樣就可以了。如果是 USB 的話,Driver 還需要先產生一個 URB:

    VOID UsbBuildInterruptOrBulkTransferRequest(
     IN OUT PURB  Urb,
     IN USHORT  Length,
     IN USBD_PIPE_HANDLE  PipeHandle,
     IN PVOID  TransferBuffer OPTIONAL,
     IN PMDL  TransferBufferMDL OPTIONAL,
     IN ULONG  TransferBufferLength,
     IN ULONG  TransferFlags,
     IN PURB  Link
    );
    

    如果 Driver 要建立的是一個 USB Bulk 或 Interrupt Transfer 的話,請呼叫 UsbBuildInterruptOrBulkTransferRequest()。
    然後再設定 IRP 的參數:

    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->Parameters.Others.Argument1 = pUrb;
    

    至於為甚麼要這樣?因為當初根本沒想到會有這種東西出現吧,所以只好用很奇怪的欄位塞入 URB。
  • Asynchronous IRP


  • PIRP IoAllocateIrp(
     IN CCHAR StackSize,
     IN BOOLEAN ChargeQuota
    );
    

    StackSize:NdisMGetDeviceProperty() 的 NextDeviceObject ->StackSize + 1

    然後再設定 IRP 的參數:

    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; // Internal Device Control for USB
    nextStack->Parameters.Others.Argument1 = pUrb; // for USB
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; // for USB submition
    

    最後再設定 IoCompleteRoutine:

    VOID IoSetCompletionRoutine(
     IN PIRP Irp,
     IN PIO_COMPLETION_ROUTINE CompletionRoutine,
     IN PVOID Context,
     IN BOOLEAN InvokeOnSuccess,
     IN BOOLEAN InvokeOnError,
     IN BOOLEAN InvokeOnCancel
    );
    

    NTSTATUS IoCompletion(
     IN PDEVICE_OBJECT DeviceObject,
     IN PIRP Irp,
     IN PVOID Context
    );
    

Send IRP

丟出 IRP 倒是很容易:

NTSTATUS IoCallDriver(
 IN PDEVICE_OBJECT DeviceObject,
 IN OUT PIRP Irp
);

DeviceObject:NdisMGetDeviceProperty() 的 NextDeviceObject 參數
  • Synchronous IRP

  • 呼叫完 IoCallDriver() 之後,如果回傳值是 STATUS_PENDING 的話,還得呼叫 KeWaitForSingleObject() 等待 IRP 完成。

    NTSTATUS KeWaitForSingleObject(
     IN PVOID Object,
     IN KWAIT_REASON WaitReason,
     IN KPROCESSOR_MODE WaitMode,
     IN BOOLEAN Alertable,
     IN PLARGE_INTEGER Timeout OPTIONAL
    );
    

    Object:IoBuildDeviceIoControlRequest() 所傳入的 Event,也就是 Driver 所配置的 Event Object。
  • Asynchronous IRP
  • 不需額外處理。

Cancel IRP

  • Synchronous IRP/Asynchronous IRP
  • BOOLEAN IoCancelIrp(IN PIRP Irp);
    

    一般而言,Calcel IRP 的動作幾乎只會在 Asynchronous IRP 內進行,這是因為 Synchronous IRP 一定會呼叫 KeWaitForSingleObject(),並且指定其 Timeout 為 INFINITE。 但是如果 Timeout 不是 INFINITE 呢?那可能得先為這個 IRP 安裝一個 Complete Routine,如果 KeWaitForSingleObject() 失敗的話, 再呼叫 IoCancelIrp(),然後再呼叫 KeWaitForSingleObject(),等待這個 IRP 被 Cancel。

    為 Synchronous IRP 呼叫 IoCancelIrp() 的機會很少,只是我很倒楣,常常會遇到,因為硬體不穩啊,常常一個簡單的 IO command 就死掉了, 所以 KeWaitForSingleObject() 就得設 Timeout,理論上,Timeout 之後的 Synchronous IRP 是不需要去作特別處理的, 不過如果不取消它,Windows 會有機會 BSOD,所以還是乖乖呼叫 IoCancelIrp() 吧。

Release IRP

  • Asynchronous IRP
  • VOID IoFreeIrp(IN PIRP Irp);
    

    Callers of IoFreeIrp must be running at IRQL <= DISPATCH_LEVEL.
  • Synchronous IRP
  • 不需要呼叫 IoFreeIrp()。

Conclusion

IRP 不困難,困難的是 IRP 的流程。

舉例,如果你的 Driver 發了一堆 Asynchronous IRP,結果在這些 IRP 還沒完成之前,OS 要結束你的 Driver 了, 你得先確保這些 IRP 都已經被正確的完成或取消,才能呼叫 IoFreeIrp(),然後再離開。

又,如果你的 Driver 收到一堆 IRP,結果又把他們放到旁邊不管,在 Driver 結束之前,得先統統完成他們才行。

Wednesday, September 05, 2007

Windows Driver Programming[7]

Relationship between IRP and IO_STACK_LOCATION

通常,Driver programmer 會被IoSkipCurrentIrpStackLocation()、 IoGetCurrentIrpStackLocation() 以及 IoCopyCurrentIrpStackLocationToNext() 給搞得很頭大。 其實,這三個函式都是 macro,把 macro 給展開,秘密大概就揭開了一半。

但是,首先來看看這張圖:



這是 IRP traverse 的時候,資料結構的型態。請注意一點,stack 增加的方向跟一般記憶體不同, + 表示是往前一個 stack,而 - 是往下一個 stack。

先來看 IoGetCurrentIrpStackLocation() 吧:

#define IoGetCurrentIrpStackLocation( Irp ) {
 (Irp)->Tail.Overlay.CurrentStackLocation;
}

它只是取得 IRP.Tail.Overlay.CurrentStackLocation。注意,它的型別為 struct _IO_STACK_LOCATION *。 再來看 IoGetNextIrpStackLocation():

#define IoGetNextIrpStackLocation( Irp ) {
 (Irp)->Tail.Overlay.CurrentStackLocation - 1 )
}

它只是回傳 IRP.Tail.Overlay.CurrentStackLocation - 1。 那 IoSetNextIrpStackLocation() 呢?

#define IoSetNextIrpStackLocation( Irp ) { 
 (Irp)->CurrentLocation--;
 (Irp)->Tail.Overlay.CurrentStackLocation--;
}

它也只是把 IRP.Tail.Overlay.CurrentStackLocation - 1 跟把 IRP.CurrentLocation - 1。

而 IoCallDriver() 做了什麼事呢(由書提供)?

NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
 IoSetNextIrpStackLocation(Irp);
 PIO_STACK_LOCATION stack = IoGetCurrentStackLocation(Irp);
 stack->DeviceObject = DeviceObject;
 ULONG fcn = stack->MajorFunction;
 PDRIVER_OBJECT driver = DeviceObject->DriverObject;
 return (*driver->MajorFunction[func])(DeviceObject, Irp);
}

Forwarding IRPs

要丟一個 IRP 的話,得呼叫 IoCallDriver(),而 IoCallDriver() 第一件事情就是先往下一個 stack location 走,然後再設定這個 stack location 的 DeviceObject。 最後,再根據這個 stack location 的 major function,呼叫這個 Device Object 的 Driver Object 所對應的函式。 注意喔,DeviceObject 是指 next DeviceObject,所以根據上圖,如果是 filter driver 要丟 IRP 的話,DeviceObject 得是 functional device object。

當 Driver 收到一個 IRP 時,它最常做的選擇便是,把它往下丟。往下丟有兩種,射後不理跟射後管理。先說射後管理,Driver 得先呼叫 IoCopyCurrentIrpStackLocationToNext(),然後再呼叫 IoCallDriver()。

#define IoCopyCurrentIrpStackLocationToNext( Irp ) {
 PIO_STACK_LOCATION __irpSp;
 PIO_STACK_LOCATION __nextIrpSp;
 __irpSp = IoGetCurrentIrpStackLocation( (Irp) );
 __nextIrpSp = IoGetNextIrpStackLocation( (Irp) );
 RtlCopyMemory( __nextIrpSp, __irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
 __nextIrpSp->Control = 0;
}

只是把現在的 stack location 的內容複製到下一個 stack location(除了 CompletionRoutine 跟 Context 之外)。 注意,IoCopyCurrentStackLocationToNext() 會把 stack location 的 DeviceObject 也一起複製,不過沒關係,IoCallDriver() 會寫入正確的值。

如果是射後不理的方式呢?Driver 得先呼叫 IoSkipCurrentStackLocation(),然後再呼叫 IoCallDriver()。

#define IoSkipCurrentIrpStackLocation( Irp ) { 
 (Irp)->CurrentLocation++;
 (Irp)->Tail.Overlay.CurrentStackLocation++;
}

IoSkipCurrentStackLocation() 只是把 IRP.Tail.Overlay.CurrentStackLocation + 1 跟 IRP.CurrentLocation + 1,也就是往前一個 stack location。 而因為 IoCallDriver 會往下一個 stack location 走,所以結果就是 stack location 並沒有改變,但是 stack location 的 DeviceObject 會變成 lower Device Object。 而造成的後果是,你的 Driver Object 在 stack location 內消失了,而這表示你跟這個 IRP 會變成一點關係都沒有。

通常,Driver 會想知道這個 IRP 完成了沒,所以 Driver 可以呼叫 IoSetCompletionRoutine() 為這個 IRP 設定你專屬的 CompletionRoutine。 注意,IoSetCompletionRoutine() 會把你的 CompletionRoutine 設定在下個 stack location 內,而非現在的 stack location 內。

而當 physical device driver 最後完成這個 IRP,並呼叫 IoCompleteRequest()。

typedef struct _IRP {
 CSHORT Type;
 USHORT Size;

 // Define the common fields used to control the IRP.

 // Define a pointer to the Memory Descriptor List (MDL) for this I/O
 // request.  This field is only used if the I/O is "direct I/O".

 PMDL MdlAddress;

 // Flags word - used to remember various flags.

 ULONG Flags;

 // The following union is used for one of three purposes:
 //    1. This IRP is an associated IRP.  The field is a pointer to a master IRP.
 //
 //    2. This is the master IRP.  The field is the count of the number of
 //       IRPs which must complete (associated IRPs) before the master can
 //       complete.
 //
 //    3. This operation is being buffered and the field is the address of
 //       the system space buffer.
 union {
  struct _IRP *MasterIrp;
  __volatile LONG IrpCount;
  PVOID SystemBuffer;
 } AssociatedIrp;

 // Thread list entry - allows queueing the IRP to the thread pending I/O
 // request packet list.
 LIST_ENTRY ThreadListEntry;

 // I/O status - final status of operation.
 IO_STATUS_BLOCK IoStatus;

 // Requestor mode - mode of the original requestor of this operation.
 KPROCESSOR_MODE RequestorMode;

 // Pending returned - TRUE if pending was initially returned as the
 // status for this packet.
 BOOLEAN PendingReturned;

 // Stack state information.
 CHAR StackCount;
 CHAR CurrentLocation;

 // Cancel - packet has been canceled.
 BOOLEAN Cancel;

 // Cancel Irql - Irql at which the cancel spinlock was acquired.
 KIRQL CancelIrql;

 // ApcEnvironment - Used to save the APC environment at the time that the
 // packet was initialized.
 CCHAR ApcEnvironment;

 // Allocation control flags.
 UCHAR AllocationFlags;

 // User parameters.
 PIO_STATUS_BLOCK UserIosb;
 PKEVENT UserEvent;

 union {
  struct {

   union {
    PIO_APC_ROUTINE UserApcRoutine;
    PVOID IssuingProcess;
   };

   PVOID UserApcContext;

  } AsynchronousParameters;

  LARGE_INTEGER AllocationSize;
 } Overlay;

 // CancelRoutine - Used to contain the address of a cancel routine supplied
 // by a device driver when the IRP is in a cancelable state.
 __volatile PDRIVER_CANCEL CancelRoutine;

 // Note that the UserBuffer parameter is outside of the stack so that I/O
 // completion can copy data back into the user's address space without
 // having to know exactly which service was being invoked.  The length
 // of the copy is stored in the second half of the I/O status block. If
 // the UserBuffer field is NULL, then no copy is performed.
 PVOID UserBuffer;

 // Kernel structures
 //
 // The following section contains kernel structures which the IRP needs
 // in order to place various work information in kernel controller system
 // queues.  Because the size and alignment cannot be controlled, they are
 // placed here at the end so they just hang off and do not affect the
 // alignment of other fields in the IRP.

 union {
  struct {
   union {

    // DeviceQueueEntry - The device queue entry field is used to
    // queue the IRP to the device driver device queue.
    KDEVICE_QUEUE_ENTRY DeviceQueueEntry;

    struct {
     // The following are available to the driver to use in
     // whatever manner is desired, while the driver owns the
     // packet.
     PVOID DriverContext[4];
    } ;
   } ;

   // Thread - pointer to caller's Thread Control Block.
   PETHREAD Thread;

   // Auxiliary buffer - pointer to any auxiliary buffer that is
   // required to pass information to a driver that is not contained
   // in a normal buffer.
   PCHAR AuxiliaryBuffer;

   // The following unnamed structure must be exactly identical
   // to the unnamed structure used in the minipacket header used
   // for completion queue entries.
   struct {

    // List entry - used to queue the packet to completion queue, among others.
    LIST_ENTRY ListEntry;

    union {

     // Current stack location - contains a pointer to the current
     // IO_STACK_LOCATION structure in the IRP stack.  This field
     // should never be directly accessed by drivers.  They should
     // use the standard functions.
     struct _IO_STACK_LOCATION *CurrentStackLocation;

     // Minipacket type.
     ULONG PacketType;
    };
   };

   // Original file object - pointer to the original file object
   // that was used to open the file.  This field is owned by the
   // I/O system and should not be used by any other drivers.
   PFILE_OBJECT OriginalFileObject;

  } Overlay;

  // APC - This APC control block is used for the special kernel APC as
  // well as for the caller's APC, if one was specified in the original
  // argument list.  If so, then the APC is reused for the normal APC for
  // whatever mode the caller was in and the "special" routine that is
  // invoked before the APC gets control simply deallocates the IRP.
  KAPC Apc;

  // CompletionKey - This is the key that is used to distinguish
  // individual I/O operations initiated on a single file handle.
  PVOID CompletionKey;

 } Tail;

} IRP, *PIRP;

typedef struct _IO_STATUS_BLOCK {
 union {
  NTSTATUS Status;
  PVOID Pointer;
 };

 // A common use of the Information field is to hold the total number of bytes transferred
 // by an operation such as IRP_MJ_READ that transfers data.

 ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef struct _IO_STACK_LOCATION {
 UCHAR MajorFunction;
 UCHAR MinorFunction;
 UCHAR Flags;
 UCHAR Control;

 // The following user parameters are based on the service that is being
 // invoked.  Drivers and file systems can determine which set to use based
 // on the above major and minor function codes.

 union {

  // System service parameters for:  NtCreateFile
  struct {
   PIO_SECURITY_CONTEXT SecurityContext;
   ULONG Options;
   USHORT FileAttributes;
   USHORT ShareAccess;
   ULONG EaLength;
  } Create;

  // System service parameters for:  NtReadFile
  struct {
   ULONG Length;
   ULONG Key;
   LARGE_INTEGER ByteOffset;
  } Read;

  // System service parameters for:  NtWriteFile
  struct {
   ULONG Length;
   ULONG Key;
   LARGE_INTEGER ByteOffset;
  } Write;

  // System service parameters for:  NtQueryDirectoryFile
  struct {
   ULONG Length;
   PUNICODE_STRING FileName;
   FILE_INFORMATION_CLASS FileInformationClass;
   ULONG FileIndex;
  } QueryDirectory;

  // System service parameters for:  NtNotifyChangeDirectoryFile
  struct {
   ULONG Length;
   ULONG CompletionFilter;
  } NotifyDirectory;

  // System service parameters for:  NtQueryInformationFile
  struct {
   ULONG Length;
   FILE_INFORMATION_CLASS FileInformationClass;
  } QueryFile;

  // System service parameters for:  NtSetInformationFile
  struct {

   ULONG Length;
   FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;
   PFILE_OBJECT FileObject;

   union {

    struct {
     BOOLEAN ReplaceIfExists;
     BOOLEAN AdvanceOnly;
    };

    ULONG ClusterCount;
    HANDLE DeleteHandle;
   };

  } SetFile;

  // System service parameters for:  NtQueryEaFile
  struct {
   ULONG Length;
   PVOID EaList;
   ULONG EaListLength;
   ULONG EaIndex;
  } QueryEa;

  // System service parameters for:  NtSetEaFile
  struct {
   ULONG Length;
  } SetEa;

  // System service parameters for:  NtQueryVolumeInformationFile
  struct {
   ULONG Length;
   FS_INFORMATION_CLASS FsInformationClass;
  } QueryVolume;

  // System service parameters for:  NtSetVolumeInformationFile
  struct {
   ULONG Length;
   FS_INFORMATION_CLASS FsInformationClass;
  } SetVolume;

  // System service parameters for:  NtFsControlFile
  //
  // Note that the user's output buffer is stored in the UserBuffer field
  // and the user's input buffer is stored in the SystemBuffer field.
  struct {
   ULONG OutputBufferLength;
   ULONG InputBufferLength;
   ULONG FsControlCode;
   PVOID Type3InputBuffer;
  } FileSystemControl;

  // System service parameters for:  NtLockFile/NtUnlockFile
  struct {
   PLARGE_INTEGER Length;
   ULONG POINTER_ALIGNMENT Key;
   LARGE_INTEGER ByteOffset;
  } LockControl;

  // System service parameters for:  NtFlushBuffersFile
  //
  // No extra user-supplied parameters.

  // System service parameters for:  NtCancelIoFile
  // No extra user-supplied parameters.

  // System service parameters for:  NtDeviceIoControlFile
  // Note that the user's output buffer is stored in the UserBuffer field
  // and the user's input buffer is stored in the SystemBuffer field.
  struct {
   ULONG OutputBufferLength;
   ULONG InputBufferLength;
   ULONG IoControlCode;
   PVOID Type3InputBuffer;
  } DeviceIoControl;

  // System service parameters for:  NtQuerySecurityObject
  struct {
   SECURITY_INFORMATION SecurityInformation;
   ULONG Length;
  } QuerySecurity;

  // System service parameters for:  NtSetSecurityObject
  struct {
   SECURITY_INFORMATION SecurityInformation;
   PSECURITY_DESCRIPTOR SecurityDescriptor;
  } SetSecurity;

  // Non-system service parameters.
  //
  // Parameters for MountVolume
  struct {
   PVPB Vpb;
   PDEVICE_OBJECT DeviceObject;
  } MountVolume;

  // Parameters for VerifyVolume
  struct {
   PVPB Vpb;
   PDEVICE_OBJECT DeviceObject;
  } VerifyVolume;

  // Parameters for Scsi with internal device contorl.
  struct {
   struct _SCSI_REQUEST_BLOCK *Srb;
  } Scsi;

  // System service parameters for:  NtQueryQuotaInformationFile
  struct {
   ULONG Length;
   PSID StartSid;
   PFILE_GET_QUOTA_INFORMATION SidList;
   ULONG SidListLength;
  } QueryQuota;

  // System service parameters for:  NtSetQuotaInformationFile
  struct {
   ULONG Length;
  } SetQuota;

  // Parameters for IRP_MN_QUERY_DEVICE_RELATIONS
  struct {
   DEVICE_RELATION_TYPE Type;
  } QueryDeviceRelations;

  // Parameters for IRP_MN_QUERY_INTERFACE
  struct {
   CONST GUID *InterfaceType;
   USHORT Size;
   USHORT Version;
   PINTERFACE Interface;
   PVOID InterfaceSpecificData;
  } QueryInterface;


  // Parameters for IRP_MN_QUERY_CAPABILITIES
  struct {
   PDEVICE_CAPABILITIES Capabilities;
  } DeviceCapabilities;

  // Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS
  struct {
   PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;
  } FilterResourceRequirements;

  // Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG
  struct {
   ULONG WhichSpace;
   PVOID Buffer;
   ULONG Offset;
   ULONG Length;
  } ReadWriteConfig;

  // Parameters for IRP_MN_SET_LOCK
  struct {
   BOOLEAN Lock;
  } SetLock;

  // Parameters for IRP_MN_QUERY_ID
  struct {
   BUS_QUERY_ID_TYPE IdType;
  } QueryId;

  // Parameters for IRP_MN_QUERY_DEVICE_TEXT
  struct {
   DEVICE_TEXT_TYPE DeviceTextType;
   LCID LocaleId;
  } QueryDeviceText;

  // Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION
  struct {
   BOOLEAN InPath;
   BOOLEAN Reserved[3];
   DEVICE_USAGE_NOTIFICATION_TYPE Type;
  } UsageNotification;

  // Parameters for IRP_MN_WAIT_WAKE
  struct {
   SYSTEM_POWER_STATE PowerState;
  } WaitWake;

  // Parameter for IRP_MN_POWER_SEQUENCE
  struct {
   PPOWER_SEQUENCE PowerSequence;
  } PowerSequence;

  // Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER
  struct {
   union {
    ULONG SystemContext;
    SYSTEM_POWER_STATE_CONTEXT SystemPowerStateContext;
   };

   POWER_STATE_TYPE Type;
   POWER_STATE State;
   POWER_ACTION ShutdownType;
  } Power;

  struct {
   ULONG SystemContext;
   POWER_STATE_TYPE Type;
   POWER_STATE State;
   POWER_ACTION ShutdownType;
  } Power;

  // Parameters for StartDevice
  struct {
   PCM_RESOURCE_LIST AllocatedResources;
   PCM_RESOURCE_LIST AllocatedResourcesTranslated;
  } StartDevice;

  // Parameters for Cleanup
  //
  // No extra parameters supplied

  // WMI Irps
  struct {
   ULONG_PTR ProviderId;
   PVOID DataPath;
   ULONG BufferSize;
   PVOID Buffer;
  } WMI;

  // Others - driver-specific
  struct {
   PVOID Argument1;
   PVOID Argument2;
   PVOID Argument3;
   PVOID Argument4;
  } Others;

 } Parameters;

 // Save a pointer to this device driver's device object for this request
 // so it can be passed to the completion routine if needed.
 // IoCallDriver() fills this field.

 PDEVICE_OBJECT DeviceObject;

 // The following location contains a pointer to the file object for this
 // request.

 PFILE_OBJECT FileObject;

 // The following routine is invoked depending on the flags in the above flags field.
 // Use IoSetCompletionRoutine() to set this field.

 PIO_COMPLETION_ROUTINE CompletionRoutine;

 // The following is used to store the address of the context parameter
 // that should be passed to the CompletionRoutine.
 // Use IoSetCompletionRoutine() to set this field.

 PVOID Context;

} IO_STACK_LOCATION, *PIO_STACK_LOCATION;

Windows Driver programming[6]

前面提到,DRIVER_OBJECT 的 MajorFunction function pointer array。這玩意兒要怎麼填?先來看看 IRP_MJ_MAXIMUM_FUNCTION 及其相關的定義吧:

#define IRP_MJ_CREATE                             0x00
#define IRP_MJ_CREATE_NAMED_PIPE                  0x01
#define IRP_MJ_CLOSE                              0x02
#define IRP_MJ_READ                               0x03
#define IRP_MJ_WRITE                              0x04
#define IRP_MJ_QUERY_INFORMATION                  0x05
#define IRP_MJ_SET_INFORMATION                    0x06
#define IRP_MJ_QUERY_EA                           0x07
#define IRP_MJ_SET_EA                             0x08
#define IRP_MJ_FLUSH_BUFFERS                      0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION           0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION             0x0b
#define IRP_MJ_DIRECTORY_CONTROL                  0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL                0x0d
#define IRP_MJ_DEVICE_CONTROL                     0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL            0x0f
#define IRP_MJ_SHUTDOWN                           0x10
#define IRP_MJ_LOCK_CONTROL                       0x11
#define IRP_MJ_CLEANUP                            0x12
#define IRP_MJ_CREATE_MAILSLOT                    0x13
#define IRP_MJ_QUERY_SECURITY                     0x14
#define IRP_MJ_SET_SECURITY                       0x15
#define IRP_MJ_POWER                              0x16
#define IRP_MJ_SYSTEM_CONTROL                     0x17
#define IRP_MJ_DEVICE_CHANGE                      0x18
#define IRP_MJ_QUERY_QUOTA                        0x19
#define IRP_MJ_SET_QUOTA                          0x1a
#define IRP_MJ_PNP                                0x1b
#define IRP_MJ_PNP_POWER                          IRP_MJ_PNP      // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION                   0x1b


IRP_MJ_MAXIMUM_FUNCTION 的定義會依據平台不同而有所不同。MajorFunction,顧名思義,就是定義 major function,而每個 major function 裡面還有一堆 minor function(IRP_MN_XXX) 可以實做,喔天啊。不過幸好一個 driver 只需要實做它需要的 major function 就好了。比較重要的當然是 IRP_MJ_READ/IRP_MJ_WRITE 啦。IRP_MJ_POWER 牽涉到電源管理,很重要,但是在 driver 開發前中期是不會去管它的。至於這些 major function 的詳細說明,請參考 DDK 文件,非常詳細。

注意一下,MajorFunction 的型別:

typedef NTSTATUS (*PDRIVER_DISPATCH) (
    IN struct _DEVICE_OBJECT *DeviceObject,
    IN struct _IRP *Irp
);


傳入值一定是 DEVICE_OBJECT 跟 IRP。傳入 DEVICE_OBJECT 是為了取得 DeviceExtension,而 DEVICE_OBJECT 是透過 IoAttachDeviceToDeviceStack() 註冊到 IO Manager,環環相扣,搞清楚就不會摸不著頭緒了。 而 IRP 當然就是希望 driver 處理的東西啦。

至於 IRP 怎麼處理嘛,下一節再說吧。Programming the microsoft windows driver model 花了一整章在說明 IRP 的處理方式耶。

Tuesday, September 04, 2007

Windows Driver programming[5]

Windows driver 永遠是被動者。實做 driver 要用哪種語言呢?當然是 C/C++啦。

既然我永遠都是被動者,那我怎麼知道你呼叫我的時候,我的狀態是什麼?舉個例子來說,當 OS 跟我說, 要我 clean up 的時候,我怎麼知道我要 clean up 什麼東西,有哪些東西是需要等待?Global 變數嗎?這是個好主意。 不過這種方式實在太下流了,連 MS 都不屑使用。這時,當然是 Driver 兩大主角要出場啦:Drvier Object 以及 Device Object。
typedef struct _DEVICE_OBJECT {
 CSHORT Type;
 USHORT Size;
 LONG ReferenceCount;
 struct _DRIVER_OBJECT *DriverObject;
 struct _DEVICE_OBJECT *NextDevice;
 struct _DEVICE_OBJECT *AttachedDevice;
 struct _IRP *CurrentIrp;
 PIO_TIMER Timer;
 ULONG Flags; 
 ULONG Characteristics;
 __volatile PVPB Vpb;
 PVOID DeviceExtension;
 DEVICE_TYPE DeviceType;
 CCHAR StackSize;

 union {
     LIST_ENTRY ListEntry;
     WAIT_CONTEXT_BLOCK Wcb;
 } Queue;

 ULONG AlignmentRequirement;

 KDEVICE_QUEUE DeviceQueue;
 KDPC Dpc;
 ULONG ActiveThreadCount;
 PSECURITY_DESCRIPTOR SecurityDescriptor;
 KEVENT DeviceLock;
 USHORT SectorSize;
 USHORT Spare1;
 struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
 PVOID Reserved;
} DEVICE_OBJECT;

typedef struct _DRIVER_OBJECT {
 CSHORT Type;
 CSHORT Size;

 // The following links all of the devices created by a single driver together on a list, and the Flags word provides an extensible flag
 // location for driver objects.

 PDEVICE_OBJECT DeviceObject;
 ULONG Flags;

 // The following section describes where the driver is loaded.  The count
 // field is used to count the number of times the driver has had its
 // registered reinitialization routine invoked.

 PVOID DriverStart;
 ULONG DriverSize;
 PVOID DriverSection;
 PDRIVER_EXTENSION DriverExtension;

 // The driver name field is used by the error log thread
 // determine the name of the driver that an I/O request is/was bound.

 UNICODE_STRING DriverName;

 // The following section is for registry support.  Thise is a pointer
 // to the path to the hardware information in the registry

 PUNICODE_STRING HardwareDatabase;

 // The following section contains the optional pointer to an array of
 // alternate entry points to a driver for "fast I/O" support.  Fast I/O
 // is performed by invokzing the driver routine directly with separate
 // parameters, rather than using the standard IRP call mechanism.  Note
 // that these functions may only be used for synchronous I/O, and when
 // the file is cached.

 PFAST_IO_DISPATCH FastIoDispatch;

 // The following section describes the entry points to this particular
 // driver.  Note that the major function dispatch table must be the last
 // field in the object so that it remains extensible.

 PDRIVER_INITIALIZE DriverInit;
 PDRIVER_STARTIO DriverStartIo;
 PDRIVER_UNLOAD DriverUnload;
 PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;

typedef struct _DRIVER_EXTENSION {

 // Back pointer to Driver Object

 struct _DRIVER_OBJECT *DriverObject;

 // The AddDevice entry point is called by the Plug & Play manager
 // to inform the driver when a new device instance arrives that this
 // driver must control.

 PDRIVER_ADD_DEVICE AddDevice;

 // The count field is used to count the number of times the driver has
 // had its registered reinitialization routine invoked.

 ULONG Count;

 // The service name field is used by the pnp manager to determine
 // where the driver related info is stored in the registry.

 UNICODE_STRING ServiceKeyName;

 // Note: any new shared fields get added here.

} DRIVER_EXTENSION, *PDRIVER_EXTENSION;

很複雜對不對?不用擔心啦,先看 DEVICE_OBJECT,有沒有看到 DeviceExtension 啊?這個就是 Windows Driver 的奧妙啦(搞不好大家都是這樣玩)。 它的型別是 PVOID,也就是 void *。為甚麼要宣告成 PVOID,那是因為你的東西都可以放在裡面,而且不管是那一種 Driver,都可以把它自己需要紀錄的東西放到 DeviceExtension 內。 而且 Windows 保證,每當呼叫到你的 Driver 所提供的 function 時,一定會把 DEVICE_OBJECT 放到參數內,所以你可以不必擔心怎樣知道你的 Driver 的狀態啦。

接下來得進入正題,Driver 的 entry point:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

DriverEntry 就跟 C 的 main 一樣,只是 C 的 runtime 認 main,而 Windows 認 DriverEntry 罷了。 在 DriverEntry 內要做什麼事呢?記住,IN 表示這個參數是 OS 給的,表示我們要利用這個參數作某些事情,或是把這個參數給設定好。

回頭看 DRIVER_OBJECT,我們要填的東西可多了:有 DriverUnload、AddDevice function、以及 MajorFunction function pointer array。

DriverUnload 很簡單,就是 undo DriverEntry()。而 AddDevice 會等到 OS 真的要把你的 Driver instantiate 的時候,才會呼叫。MajorFunction array 可多了,基本上,OS 會透過這個 function pointer array 來呼叫你填入的 function。

DriverEntry() 呼叫完,OS 會接著呼叫你剛剛在 DRIVER_OBJECT 內填入的 AddDevice():

NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)

DriverObject 就是 DriverEntry() 傳入的 DriverObject。pdo 呢,就是你的下家的 DeviceObject。 要知道,WDM driver stack 是由下往上長的,所以你會有下家幫你處理 IRP,而你的上家也會把 IRP 丟給你處理。 而要把 IRP 丟給下家,得先把這個 pdo 給記起來才行。記到哪裡?當然是萬惡的 DeviceExtension 啦。

問題來啦,DRIVER_OBJECT 是 OS 給的,那 DEVICE_OBJECT 呢?當然是呼叫 IoCreateDevice() 啦:

NTSTATUS IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,
    IN ULONG  DeviceExtensionSize,
    IN PUNICODE_STRING  DeviceName  OPTIONAL,
    IN DEVICE_TYPE  DeviceType,
    IN ULONG  DeviceCharacteristics,
    IN BOOLEAN  Exclusive,
    OUT PDEVICE_OBJECT  *DeviceObject
    );

注意一下,DeviceExtensionSize 是指 OS allocate 出來的 DEVICE_OBJECT 所含的 DeviceExtension 大小。 換句話說,DeviceExtension 是 OS 幫你配置出來的。取得 DEVICE_OBJECT 之後,呼叫 IoAttachDeviceToDeviceStack(),把你的 DeviceObject 放到你的下家之上。大功告成。

當然,在 AddDevice() 內還要初始化許多東西,包括 spinlock/event、Dpc for ISR、double-linked list、DeviceExtension 等等的 resource。

Windows Driver programming[4]

IRQ sharing

共享 IRQ 的意思就是共享 IRQ Line。共享 IRQ 的 Driver 在收到 Interrupt 的時候,得檢查這個 Interrupt 是不是自己的 Interrupt, 因為這個 Interrupt source 有可能是來自別的 Device。

Bus Master

一般而言,可以主動存取 Bus 的裝置就稱為 Bus Master。

Scatter-Gather DMA

跟 MDL 類似,當傳輸 DMA 時,可以一次傳輸不連續的 Bus address. 這個要硬體支援才行。基本上,DMA 傳輸都是連續的 Bus address。一次傳輸多筆不連續的 Bus address 資料需要額外的硬體支援。

IRP(I/O Request Packet)

IRP 的概念跟用法在 MSDN 的 Handling IRPs 一節裡有詳盡的描述。不過看得的人不是已經懂了,就是看了也不懂。我儘量寫的清楚一點。

IRP( I/O Request Packet),顧名思義,就是一個要求做 IO 的封包。那為甚麼文件寫的這麼複雜?因為 WDM driver 是屬於疊疊樂型的, 所以會變成你的 Driver 的上方還有許多 Driver,下方也有很多 Driver。

如果你的 Driver 需要對 Device 做 IO 的話,因為 Driver 並沒有直接面對硬體(下面還有許多 Driver!),所以變成你得透過這些 Driver 幫你轉送這些 IRP 才能把你要的轉給 Device。 並且因為 Driver 的種類太多了,MS 無法為每種 Driver 定 IRP 的 routing 架構,所以只好訂出一般化的 IRP routing 方式,而後果就是,很多名詞變得很抽象,入門者光看這些名稱根本無法瞭解文件在寫甚麼。

但是這樣做好處太多了:
  • 你可以在 Driver 上下插入 Driver,監控進出的 IRP。不爽還可以偷偷把 IRP 幹掉。

壞處就是,架構變得很複雜,跟效能的損失。

而處理 IRP 變成 Driver 最最重要的部份,光是需要注意的便有: Synchronous/Asynchronous IRP、IRP 的配置、IRP 的傳送、取消 IRP、釋放 IRP 等工作。

Synchronous and Asynchronous IRP

IRP 只有一種,就是 IRP。會有 Synchronous 跟 Asynchronous 之分,是源自於 IRP 傳送的方式。

如果你寫過 Socket 程式,那你應該會知道 Asynchronous I/O 跟 Synchronous I/O 的概念。簡言之,便是 Synchronous IRP 一傳送,Driver 就得等這個 IRP 完成才能夠繼續執行, 而 Asynchronous IRP 會繼續執行下去。這個考量在 User mode Application 當然沒問題(其實還是會有啦,不然不會有一堆 Asynchronous API 出來), 只要把這些東西放到 Thread 內,然後再用一些同步機制就可以搞定。

但是這一招在 Driver 裡面是不行的,Windows Driver 裡有定義每個函式能夠執行的 IRQL,而跑在 Dispatch Level 之上的 thread,是不能夠使用 Synchronous IRP 的,因為它們不可以睡覺,而 Synchronous IRP 會讓該 thread 去睡覺。

使用 Asynchronous IRP 也有很大的困擾:同步的問題。在 Driver 裡面同步可不比 Application mode,一不小心就 BSOD 了,麻煩。另外,Asynchronous IRP 必須在 Driver 內部配置跟釋放,而 Synchronous IRP 則是 Driver 配置,系統(I/O manager)釋放。

Windows Driver programming[3]

MDL (memory descriptor list)

Programming the microsoft windows driver model 對於 MDL 的描述不算多,它只有在需要的時候才會提出一些操作 MDL 的 function。 相信大多數人在對於 MDL 根本就還沒頭緒的狀況下,應該會很火大吧?以下是 MDL 的資料結構:

typedef struct _MDL {
    struct _MDL *Next;
    CSHORT Size;
    CSHORT MdlFlags;
    struct _EPROCESS *Process;
    PVOID MappedSystemVa;
    PVOID StartVa;
    ULONG ByteCount;
    ULONG ByteOffset;
} MDL, *PMDL;

MDL 只是一個對實體記憶體的描述,但是因為系統跟 Driver 都是使用虛擬記憶體,所以 MDL 就是把虛擬記憶體『映射』到實體記憶體(from DDK)。

這樣講是很模糊的,其實 MDL 的作用很簡單:當 Driver 要存取某個記憶體位置時,確保 MDL 所描述的記憶體位置不會引起 page fault。 為甚麼?因為『虛擬記憶體』的關係。Driver 存取的是虛擬記憶體,所以當系統丟給 Driver 一塊虛擬記憶體位置,而 Driver 如何知道這塊記憶體所代表的資料到底是在 RAM 還是 Disk(被 paged out) 內?

在 NDIS 裡面,Driver 是不需要去關心系統丟給 Driver 的記憶體到底是如何,反正一定是在 RAM 裡面就是了。但是在 I/O Control 內的 Direct I/O method 裡面, 這可不一定,因為這個記憶體位置可能是指到某個應用程式的虛擬記憶體,而應用程式的虛擬記憶體是非常有可能被 paged out。 所以 Driver 得呼叫 MmGetSystemAddressForMdlSafe() 來鎖定這塊記憶體(系統得把把資料由 Disk 載入到 RAM)。 寫個小程式就可以證明這一點:

PVOID    buf = NULL;
PMDL     mdl = NULL;
NDIS_STATUS    ndis_status;

ndis_status = NdisAllocateMemoryWithTag(&buf, 1000, 0x1111L);
if (ndis_status != NDIS_STATUS_SUCCESS){
    DbgPrint("NetVMini : Can not allocate test Memory for MDL.\n");
} else {
    mdl = IoAllocateMdl(buf, 1000, FALSE, FALSE, NULL);
    if (mdl != NULL) {
        MmBuildMdlForNonPagedPool(mdl);
        DbgPrint("buf = 0x%p\n", buf);
        DbgPrint("MDL.Size = 0x%X\n", mdl->Size);
        DbgPrint("MDL.MdlFlags = 0x%X\n", mdl->MdlFlags);
        DbgPrint("MDL.MappedSystemVa = 0x%p\n", mdl->MappedSystemVa);
        DbgPrint("MDL.StartVa = 0x%p\n", mdl->StartVa);
        DbgPrint("MDL.ByteCount = 0x%X\n", mdl->ByteCount);
        DbgPrint("MDL.ByteOffset = 0x%X\n", mdl->ByteOffset);
        IoFreeMdl(mdl);
        mdl = NULL;
    }
    NdisFreeMemory(buf, 1000, 0);
}

這個程式只是配置一塊 non-paged 記憶體,然後用一個 MDL 來描述它。 下面是程式的結果:

    00000008    0.00103058    buf = 0x85322008   
    00000009    0.00104175    MDL.Size = 0x20   
    00000010    0.00104985    MDL.MdlFlags = 0xC   
    00000011    0.00106243    MDL.MappedSystemVa = 0x85322008   
    00000012    0.00107919    MDL.StartVa = 0x85322000   
    00000013    0.00109120    MDL.ByteCount = 0x3E8   
    00000014    0.00110321    MDL.ByteOffset = 0x8    

buf = MappedSystemVa = StartVa + ByteOffset. Eureka!

看起來直接用 NdisAllocateMemoryWithTag() 所得到指標還比較好? MDL 還是有好處的啦,至少它可以串起一堆 MDL,當系統需要給 Driver 一堆記憶體區塊,而這些記憶體區塊全都是不連續的,那 MDL 就會很好用(NDIS_BUFFER 就是如此)。 而且在 NDIS 6.0 裡面,NET_BUFFER 只能使用 MDL。

跟 MDL 相關的函式有:

PMDL IoAllocateMdl(
    IN PVOID VirtualAddress,
    IN ULONG Length,
    IN BOOLEAN SecondaryBuffer,
    IN BOOLEAN ChargeQuota,
    IN OUT PIRP Irp OPTIONAL
    );

VirtualAddress:這個 MDL 所描述的虛擬記憶體位置。
Length:這個 MDL 所描述的記憶體大小。

DDK 裡面特別強調,如果 Driver 希望建立的 MDL 是映射到 Driver 自己配置的 Non-Paged 記憶體的話,Driver 還得呼叫 MmBuildMdlForNonPagedPool()。 這是因為 IoAllocateMdl() 只有配置記憶體,但是並沒有 Build MDL(好模糊的說法)。 NDIS 6.0 有提供 NdisAllocateMdl(),它提供一氣呵成的服務。

VOID MmBuildMdlForNonPagedPool(
    IN OUT PMDL MemoryDescriptorList
    );

The MmBuildMdlForNonPagedPool routine receives an MDL that specifies a virtual memory buffer in nonpaged pool, and updates it to describe the underlying physical pages. The MDL virtual address that is input must be within the nonpaged portion of system space, such as memory allocated by ExAllocatePoolWithTag with PoolType = NonPagedPool.

VOID IoBuildPartialMdl(
    IN PMDL SourceMdl,
    IN OUT PMDL TargetMdl,
    IN PVOID VirtualAddress,
    IN ULONG Length
    );
VOID IoFreeMdl(
    IN PMDL Mdl
    );
ULONG MmGetMdlByteCount(IN PMDL  Mdl){
    return Mdl->ByteCount;
}

回傳這個 MDL 的大小。

ULONG MmGetMdlByteOffset(IN PMDL  Mdl) {
    return Mdl->ByteOffset;
}

回傳這個 MDL 離 StarVa 有多遠。

PVOID MmGetMdlVirtualAddress(IN PMDL  Mdl) {
    return ((PVOID) ((PCHAR) ((Mdl)->StartVa) + (Mdl)->ByteOffset));
}

取得 MDL 的虛擬記憶體位置。DDK 特別講明,Lower-Level Driver 不可以直接把這個 Address 拿來使用,因為這有可能是 user-space 的記憶體位置。因此,Driver 必須呼叫 MmGetSystemAddressForMdlSafe() 來取得並鎖定這個 Address 所對應到的 system-space 的記憶體位置。

PVOID MmGetSystemAddressForMdlSafe(IN PMDL Mdl, IN MM_PAGE_PRIORITY Priority) {
    if (Mdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL)) {
        return Mdl->MappedSystemVa;
    } else {
        return MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmCached, NULL, FALSE, Priority);
    }
}

這個函式是個 Macro。程式碼很簡單,如果這個 MDL 已經被鎖定並映射,直接回傳 MappedSystemVa,否則就鎖定並映射它。

VOID MmInitializeMdl(IN PMDL  MemoryDescriptorList, IN PVOID  BaseVa, IN SIZE_T  Length){
    MemoryDescriptorList->Next = (PMDL) NULL;
    MemoryDescriptorList->Size = (CSHORT)(sizeof(MDL) + sizeof(PFN_NUMBER) * ADDRESS_AND_SIZE_TO_SPAN_PAGES((BaseVa), (Length))));
    MemoryDescriptorList->MdlFlags = 0;
    MemoryDescriptorList->StartVa = (PVOID) PAGE_ALIGN((BaseVa));
    MemoryDescriptorList->ByteOffset = BYTE_OFFSET((BaseVa));
    MemoryDescriptorList->ByteCount = (ULONG)(Length);
}

這段程式碼已經對 MDL 說明的很清楚了。MDL 的目的是要串起多個分散的記憶體,而 MnInitializeMdl() 只是串起第一個記憶體位置。

VOID MmPrepareMdlForReuse(IN PMDL  MDL) {
    if ((MDL->MdlFlags & MDL_PARTIAL_HAS_BEEN_MAPPED) != 0) {
        MmUnmapLockedPages( MDL->MappedSystemVa, MDL );
    }
}

Very few drivers call this routine (DDK says).

VOID MmProbeAndLockPages(
    IN OUT PMDL  MemoryDescriptorList,
    IN KPROCESSOR_MODE AccessMode,
    IN LOCK_OPERATION Operation
    );

這似乎是用在 Direct IO method 上。它會映射並鎖定 MDL所描述的虛擬記憶體。

ULONG MmSizeOfMdl(
    IN PVOID Base,
    IN SIZE_T Length
    );

The MmSizeOfMdl routine returns the number of bytes to allocate for an MDL describing a given address range.

VOID MmUnmapLockedPages(
    IN PVOID BaseAddress,
    IN PMDL  MemoryDescriptorList
    );

解除 MmProbeAndLockPages() 或 MmMapLockedPagesSpecifyCache() 所造成的影響。

Monday, September 03, 2007

Windows Driver programming[2]

IRQL(Interrupt Request Level)

Microsoft 訂出了 32 個 IRQL,但是呢,基本上 Driver 只會碰到三個 IRQ Level:Passive、Dispatch、Device(DIRQL)。 IRQL 高的 Thread 不會被 IRQL 低的 Thread 中斷。跑在 Passive Level 的 thread,限制最少。 而跑在 Dispath Level 的 Thread,則是不可以 wait event/semaphore/mutex,但是可以 busy waiting。 而跑在 DIRQL 的 Thread,最好連 busy waiting 都不要。

Microsoft 已經訂出所有函式能夠執行的 IRQL,Driver 必須遵照指示。為甚麼會有 IRQL?我相信這是大多數人在看 Programming the microsoft windows driver model 這本書時, 一開始會有的疑問。這牽涉到 OS threading model。要知道,CPU 一次只能執行一個指令,這代表,CPU 一次只能執行一個 thread, 好吧,不要考慮雙 CPU 的情況,畢竟,SMP 的狀況,連 DDK 都講的不清不楚。而執行中的 thread,又有分重要跟不重要(處理 hardware interrupt 的 thread 一定比處理 software interrupt 跟 service call 的 thread 重要吧?)。

當重要的 thread 在處理事情時,一定不會希望被別人搶走 CPU 的執行權吧?而這就是 IRQL 的概念:IRQL 高的 thread 不會被 IRQL 低的 thread 搶走 CPU 使用權。 這其實很簡單,只是這跟 driver 有什麼關係?喔,driver 也者,被動呼叫也,所以它自己是不會去搶 CPU 的,只有某些 OS thread 需要你的 driver 的時候,才會呼叫你。

OS thread 有分重要跟不重要吧?當重要的 thread 呼叫你的時候,結果你卻跑去睡覺,或是不重要的 thread 呼叫你,結果你卻引起 page fault,這都是不被允許的。舉個比較明確的例子(DDK 都是用那些非常抽象的語法), 當 Windows 的某個重要的 thread 在 disable interrupt 之後,呼叫你的 driver,結果你卻在裡面睡大覺,結果就是沒有任何 interrupt 會被處理,OS 就跟當掉沒兩樣,因為它期待你很快就會結束,結果你卻不鳥它。

而另外一個例子,非常容易發生的例子,你一定會遇到的例子,『IRQL less than or equal』,這通常表示,某個不重要的 thread 呼叫你的 driver,可是你的 driver 卻存取一些不在實體記憶體內的資料。

什麼叫做『不在實體記憶體內的資料』?就是你要存取的記憶體位置是非法的,尚未被載入到 ram。有這種事嗎?有的,OS 課本提到的虛擬記憶體。虛擬記憶體的內容有可能是在 disk,或是 ram 內。 而一般而言,driver 直接存取在 disk 內的虛擬記憶體內容,都會產生 page fault,而很不幸的,某些 IRQL 不能允許 page fault 產生,因為它的 IRQL 比處理 page fault 的 thread 還要高,而 IRQL 高的 thread 是不會被 IRQL 低的 thread 搶走 CPU 使用權, 這表示一旦發生 page fault,沒人可以處理這個問題,BSOD 直接產生。所幸,BSOD 跟 kernel dump 會跟我們講問題發生點,所以這個問題很好解決。

基本上,Windows driver 是一堆 function 的集合,等著被呼叫。所以搞清楚每個 function 執行的 IRQL,跟每個 OS service call 的 IRQL 是很重要的。

Windows Driver programming[1]

Windows Driver programming 是我在 rxxltxk 被冷凍時(2006/07~2006/12)無聊研究的題材。 當時剛好有顆 IC 要出來,剛好最會寫 driver 的人走了,剛好被冷凍,只好自己玩玩 Windows Driver。

需要的工具

1. 編譯工具

  • Windows Driver Development Kit(DDK):這個去 Microsoft 官方網站抓,不過要錢。現在加入 Microsoft 的測試計畫可以免費下載。
  • Soft -Ice內建在 DriverStudio 中,去 Compuware 公司抓,這也要錢。 不過當你的 driver 在某個地方一定會讓系統整個當掉的時候,你會發現 Soft-Ice 真的很好用。 Microsoft 有個 kernel mode debugger,不過它只支援 remote debug,這表示你得透過 com port,用另外一台電腦 debug, 雖然它支援 USB remote debug,不過一樣很麻煩,光設定就要人命了。 不過 Soft-Ice也不是萬能的,至少它在執行的時候,會把整個系統停住,這會導致一些很奇怪的現象。 另外,Soft-Ice 跟某些 CPU 會很不合,一開就死。如果你的 CPU 支援 hyper-threading,最好把這個功能關掉。 以上都是鬼扯....SoftIce 的相容性相當不好,導致只好改用 Windows debugging tool,結果發覺意外的好用。當時覺得不好用是因為,公司不給我另外一台機器啊(被冷凍的人還要機器幹嘛?)。windows debugger tool 不用錢喔。而且它還可以分析 kernel dump file,然後跟你的 source code 作連結,幾乎都可以抓出程式死在哪裡。
  • DbgView:當你的 driver 呼叫 DbgPrint()/KdPrint() 時,它會把字串轉到 DbgView 的視窗中,這個在 local debug 的時候很有用。你可以在 http://www.sysinternals.com/Utilities/DebugView.html 下載,不用錢。

2. 書

  • Programming the microsoft windows driver model:這本書一定要看,雖然很難看得懂。你也可以在寫完第一隻 Driver(當然是很爛的 driver)之後再去看,會受益良多。這本書的內容只包含 WDM driver,所以如果你要寫 NDIS driver或 Kernel stream driver,嘿嘿,DDK 繼續 K 吧。
  • Microsoft Windows Internals, Fourth Edition:這算是 OS 參考書,有一些概念是很值得看的。
  • Advanced Windows Debugging:大部分介紹如何使用 Windows Debugging Tools,很實用的一本書。

codeblock