21
1月
2016

5、Windows的设备驱动框架-DriverEntry

假定该模块业已装入,并且I/O管理已经调用了这个模块的初始化程序DriverEntry(),我们就从这儿开始。下面的代码仍引自ReactOS。

"老式"设备没有"即插即用"的功能,所以其驱动模块一般都是在系统初始化的时候装入内核并得到初始化的,但是应用软件和系统工具也可以通过系统调用NtLoadDriver()在运行中动态装载这样的驱动模块。Windows内核装载一个驱动模块的映像以后就会调用其入口函数,其函数名默认为DriverEntry()。但也可以用别的函数名,只要在编译、连接时特别加以指定即可。

1.NTSTATUS STDCALL 
2.DriverEntry(PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath) 
3.{ 
4.PDEVICE_EXTENSION DeviceExtension; 
5.PDEVICE_OBJECT DeviceObject; 
6.UNICODE_STRING DeviceName = RTL_CONSTANT_STRING
(L"\\Device\\Beep"); 
7.NTSTATUS Status; 
8.
9.DriverObject->Flags = 0; 
10.DriverObject->MajorFunction[IRP_MJ_CREATE] = BeepCreate; 
11.DriverObject->MajorFunction[IRP_MJ_CLOSE] = BeepClose; 
12.DriverObject->MajorFunction[IRP_MJ_CLEANUP] = BeepCleanup; 
13.DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
BeepDeviceControl; 
14.DriverObject->DriverUnload = BeepUnload; 
15.
16.Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), 
17.&DeviceName, FILE_DEVICE_BEEP, 0,
FALSE, &DeviceObject); 
18.if (!NT_SUCCESS(Status)) return Status; 
19.
20./* set up device extension */ 
21.DeviceExtension = DeviceObject->DeviceExtension; 
22.DeviceExtension->BeepOn = FALSE; 
23.
24.KeInitializeDpc(&DeviceExtension->Dpc, BeepDPC, DeviceExtension); 
25.KeInitializeTimer(&DeviceExtension->Timer); 
26.KeInitializeEvent(&DeviceExtension->Event, 
SynchronizationEvent, FALSE); 
27.return(STATUS_SUCCESS); 
28.}

不管叫什么名字,入口函数的调用界面总是固定不变的。第一个参数DriverObject是个指针,指向由内核为之创建好的空白"驱动对象"即DRIVER_OBJECT数据结构。第二个参数RegistryPath也是指针,指向一个Unicode字符串,这就是驱动模块映像的文件路径名。不过,通过NtLoadDriver()装载驱动模块时给定的并非文件路径名,而是一个"服务名",根据服务名可以从注册表(Registry)查得相应的文件路径名,RegistryPath就指向这个来自注册表的路径名。 由内核为其创建的驱动对象基本上是空白的,所以驱动模块初始化的任务之一就是正确填写这个数据结构中的"主函数"指针数组。当然,不同设备的驱动各有一组不同的主函数。从代码中可以看出,此种设备所提供的主函数只有4个,即:IRP_MJ_CREATE、IRP_MJ_CLOSE、IRP_MJ_CLEANUP、IRP_MJ_DEVICE_CONTROL,其余的主函数操作均不支持。数据结构DriverObject基本上是空白的,但是"主函数"指针数组中的函数指针则都指向一个默认的空函数,所以未加填写的指针都指向这个默认的空函数。 此外,还有个函数BeepUnload,那是在主函数之外的,显然是用于模块的卸载。DRIVER_OBJECT数据结构中还有两个不在主函数指针数组之内的函数指针,即DriverInit和DriverStartIo,这里均未得到设置。 进一步,这里并未涉及驱动对象的扩充部分,老式驱动的驱动对象是不需要扩充部分的,其指针DriverExtension为NULL。这是因为,DRIVER_EXTENSION数据结构的目的在于提供堆叠函数AddDevice(),而老式驱动不需要形成堆叠。 然后是创建本模块的设备对象,这是由IoCreateDevice()完成的,这个函数由内核提供。从调用参数中可以约略看出:这个设备的设备名为"\Device\Beep";设备类型为FILE_DEVICE_BEEP,这是winddk.h中原来就有定义的。其余的下面还会讲到。IoCreateDevice()这个函数比较大,我们需要分段阅读。

 1.[DriverEntry() > IoCreateDevice()] 
2. 
3.NTSTATUS STDCALL 
4.IoCreateDevice(PDRIVER_OBJECT DriverObject, ULONG 
DeviceExtensionSize, 
5. PUNICODE_STRING DeviceName, DEVICE_TYPE DeviceType, 
6. ULONG DeviceCharacteristics, BOOLEAN Exclusive, 
7. PDEVICE_OBJECT *DeviceObject) 
8.{ 
9. PDEVICE_OBJECT CreatedDeviceObject; 
10. ...... 
11. 
12. PAGED_CODE(); 
13. /* Check if we have to generate a name */ 
14. if (DeviceCharacteristics & FILE_AUTOGENERATED_DEVICE_NAME) 
15. { //如果要求自动生成对象名 
16./* Generate it */ 
17. swprintf(AutoNameBuffer, L"\\Device\\%08lx", 
18. InterlockedIncrementUL(&IopDeviceObjectNumber)); 
19. /* Initialize the name */ 
20. RtlInitUnicodeString(&AutoName, AutoNameBuffer); 
21.DeviceName = &AutoName; 
22. } 
23. /* Initialize the Object Attributes */ 
24. InitializeObjectAttributes(&ObjectAttributes, DeviceName, 
25. OBJ_KERNEL_HANDLE, NULL, NULL); 
26. /* Honor exclusive flag */ 
27. if (Exclusive) ObjectAttributes.Attributes |= OBJ_EXCLUSIVE; 
28. /* Create a permanent object for named devices */ 
29. if (DeviceName) ObjectAttributes.Attributes |= OBJ_PERMANENT; 
30. /* Align the Extension Size to 8-bytes */ 
31.AlignedDeviceExtensionSize = (DeviceExtensionSize + 7) &~ 7; 
32. 
33. /* Total Size */ //总的大小为三个部分之和 
34.TotalSize = AlignedDeviceExtensionSize + sizeof(DEVICE_OBJECT) + 
35. sizeof(EXTENDED_DEVOBJ_EXTENSION); 
36. /* Create the Device Object */ 
37. *DeviceObject = NULL; 
38.Status = ObCreateObject(KernelMode, IoDeviceObjectType,
&ObjectAttributes, 
39. KernelMode, NULL, TotalSize, 0, 0,
(PVOID*)&CreatedDeviceObject); 
40. if (!NT_SUCCESS(Status)) return Status; 
41. 
42. /* Clear the whole Object and extension so we don't 
null stuff manually */ 
43. RtlZeroMemory(CreatedDeviceObject, TotalSize); 
44. /* Setup the Type and Size. Note that we don't use 
the aligned size, 
45. * because that's only padding for the DevObjExt 
and not part of the Object. */ 
46. CreatedDeviceObject->Type = IO_TYPE_DEVICE; 
47. //设备对象的Size只包括两部分,而不包括DEVOBJ_EXTENSION数据结构 
48.CreatedDeviceObject->Size = sizeof(DEVICE_OBJECT) + 
49. (USHORT)

50.DeviceExtensionSize; 
51. /* The kernel extension is after the driver internal 
extension */ 
52. //设备对象的上面是扩充部,再上面才是DEVOBJ_EXTENSION数据结构 
53.DeviceObjectExtension = (PDEVOBJ_EXTENSION) 
54. ((ULONG_PTR)(CreatedDeviceObject + 1) + 
AlignedDeviceExtensionSize); 
55. /* Set the Type and Size. Question: why is Size 0 on Windows? */ 
56. DeviceObjectExtension->Type = IO_TYPE_DEVICE_OBJECT_EXTENSION; 
57. DeviceObjectExtension->Size = 0; 
58. /* Link the Object and Extension */ 
59. DeviceObjectExtension->DeviceObject = CreatedDeviceObject; 
60. CreatedDeviceObject->DeviceObjectExtensionDevice
ObjectExtension = DeviceObjectExtension; 
61. 
62. /* Set Device Object Data */ 
63. CreatedDeviceObject->DeviceTypeDeviceType = DeviceType; 
64. CreatedDeviceObject->Characteristics = DeviceCharacteristics; 
65. CreatedDeviceObject->DeviceExtension = DeviceExtensionSize ? 
66.
CreatedDeviceObject + 1 : NULL; 
67. CreatedDeviceObject->StackSize = 1; 
68. CreatedDeviceObject->AlignmentRequirement = 0; 
69. /* Set the Flags */ 
70. CreatedDeviceObject->Flags = DO_DEVICE_INITIALIZING; 
71. if (Exclusive) CreatedDeviceObject->Flags |= DO_EXCLUSIVE; 
72. if (DeviceName) CreatedDeviceObject->Flags |= DO_DEVICE_HAS_NAME; 

第一个参数DriverObject就是指向驱动对象的指针。任何设备对象,都是依附于某个驱动对象的,因为设备对象只提供数据而并不提供操作。第二个参数DeviceExtensionSize是设备对象扩充部的大小。不同的设备有不同种类的数据,这些数据就存储在设备对象的扩充部,所以在创建设备对象时需要知道其大小。 代码的开头有个PAGED_CODE(),设备对象的创建只允许在PASSIVE_LEVEL上进行,在中断处理程序内部或DPC函数中是不允许创建设备对象的。 设备对象的数据结构由ObCreateObject()分配和创建,这个函数的代码就不看了,但是其中几个调用参数需要加以说明。 首先,对象的类型为IoDeviceObjectType,就是"设备对象"。这实际上是个结构指针,这个数据结构的内容就决定了设备对象的特征。但是前面从DriverEntry()也传下来一个参数DeviceType,并且我们已经知道那是FILE_DEVICE_BEEP,这是怎么回事呢?是这样的,前者主要是供ObCreateObject()使用的,是对象管理意义上的类型,而后者纯粹是供设备驱动使用的,是I/O管理意义上的类型。 其次,需要为包括扩充部在内的设备对象分配的存储空间大小是TotalSize。这是由三个部分构成的。其一为sizeof(DEVICE_OBJECT);其二为AlignedDeviceExtensionSize即边界对齐以后的扩充部大小,这是通过参数DeviceExtensionSize传下来的,不同的设备驱动模块各有其自己的扩充部大小和结构;其三为sizeof(EXTENDED_DEVOBJ_ EXTENSION),这是进一步扩充了的DEVOBJ_EXTENSION结构。不过,ObCreateObject()实际分配的缓冲区还要更大一些,还要增加一个sizeof(OBJECT_HEADER)。 ObCreateObject()通过参数CreatedDeviceObject返回所创建的设备对象。这个设备对象的头部是个DEVICE_OBJECT数据结构,后面是自定义的扩充部,最后是个EXTENDED_DEVOBJ_EXTENSION数据结构。下面的RtlZeroMemory()所填0的大小是TotalSize,也就是说,保留OBJECT_HEADER数据结构的内容不变。 下面又涉及几个与类型有关的字段: 首先把DEVICE_OBJECT结构中的Type字段设置成IO_TYPE_DEVICE。 然后把扩充部的Type字段设置成IO_TYPE_DEVICE_OBJECT_EXTENSION。 最后把DEVICE_OBJECT结构中的DeviceType字段设置成参数DeviceType即FILE_DEVICE_BEEP。

 
1.[DriverEntry() > IoCreateDevice()] 
2. 
3. /* Attach a Vpb for Disks and Tapes, and create 
the Device Lock */ 
4. if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK) || 
5. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_VIRTUAL_DISK) || 
6. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_CD_ROM) || 
7. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_TAPE)) 
8. { 
9. /* Create Vpb */ 
10.Status = IopCreateVpb(CreatedDeviceObject); 
11. if (!NT_SUCCESS(Status)) 
12. { 
13. /* Reference the device object and fail */ 
14. ObDereferenceObject(DeviceObject); 
15. return Status; 
16. } 
17. /* Initialize Lock Event */ 
18. KeInitializeEvent(&CreatedDeviceObject->DeviceLock, 
19.
SynchronizationEvent, TRUE); 
20. } 
21. /* Set the right Sector Size */ 
22. switch (DeviceType) 
23. { 
24. /* All disk systems */ 
25. case FILE_DEVICE_DISK_FILE_SYSTEM: 
26. case FILE_DEVICE_DISK: 
27. case FILE_DEVICE_VIRTUAL_DISK: 
28. /* The default is 512 bytes */ 
29. CreatedDeviceObject->SectorSize = 512; 
30. break; 
31. /* CD-ROM file systems */ 
32. case FILE_DEVICE_CD_ROM_FILE_SYSTEM: 
33. /* The default is 2048 bytes */ 
34. CreatedDeviceObject->SectorSize = 2048; 
35. } 
36. /* Create the Device Queue */ 
37. if ((CreatedDeviceObject->DeviceType == FILE_
DEVICE_DISK_FILE_SYSTEM) || 
38. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_FILE_SYSTEM) || 
39. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_CD_ROM_FILE_SYSTEM) || 
40. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_NETWORK_FILE_SYSTEM) || 
41. (CreatedDeviceObject->DeviceType == FILE_
DEVICE_TAPE_FILE_SYSTEM)) 
42. { 
43. /* Simple FS Devices, they don't need a real Device Queue */ 
44. InitializeListHead(&CreatedDeviceObject->Queue.ListEntry); 
45. } 
46. else 
47. { 
48. /* An actual Device, initialize its DQ */ 
49. KeInitializeDeviceQueue(&CreatedDeviceObject->DeviceQueue); 
50. } 
51. 
52. /* Insert the Object */ 
53.Status = ObInsertObject(CreatedDeviceObject, NULL, 
54. FILE_READ_DATA | FILE_WRITE_DATA, 1, 
55. (PVOID*)&CreatedDeviceObject, &TempHandle); 
56. if (!NT_SUCCESS(Status)) return Status; 
57. 
58. /* Now do the final linking */ 
59. ObReferenceObject(DriverObject); 
60. ASSERT((DriverObject->Flags & DRVO_UNLOAD_INVOKED) == 0); 
61. CreatedDeviceObject->DriverObjectDriverObject = DriverObject; 
62. IopEditDeviceList(DriverObject, CreatedDeviceObject, IopAdd); 
63. /* Close the temporary handle and return to caller */ 
64. ObCloseHandle(TempHandle, KernelMode); //关闭临时句柄 
65.*DeviceObject = CreatedDeviceObject; 
66. return STATUS_SUCCESS; 
67.} 

如前所述,在我们现在这个情景中,设备的类型为FILE_DEVICE_BEEP。这是一种很普通、很简单的设备类型,并无什么特殊之处。但是,如果是FILE_DEVICE_DISK一类的块存储设备,那就不一样了。块存储设备都是与文件系统相联系的,一个块设备就是一个"文件卷(Volume)"。所以,在Windows的设备驱动框架中,此类设备对象需要通过一个称为"(文件)卷参数块(Volume Parameter Block)"的数据结构和文件系统挂上钩。这个问题以后还要专门介绍,现在只要知道对于此类设备需要通过IopCreateVpb()分配一个VPB,并让DEVICE_OBJECT结构中的字段Vpb指向这个VPB数据结构,就可以了。 另一方面,块设备还有个参数,就是块的大小。DEVICE_OBJECT结构中还有个字段SectorSize就用来记录这个参数,所以也要根据具体的设备类型加以设置。例如对于FILE_DEVICE_CD_ROM_FILE_SYSTEM就是2048,而对于别的块设备则为512。 不仅如此,在Windows的设备驱动框架中,设备对象还可能代表着文件系统。如果是文件系统,就要用到DEVICE_OBJECT结构中的Queue,否则就要用到DEVICE_OBJECT结构中的DeviceQueue。所以也要根据实际要求加以初始化。 完成了这些操作以后,就可以把所创建的设备对象"安装"到对象目录中了,这是由ObInsertObject()完成的,读者不妨回到"对象管理"一章去回顾一下这个函数的代码。 回到DriverEntry()的代码中,有些设备对象可能还需要别的初始化操作,我们手头的这个对象正是这样。这个设备对象所代表的是不分层的"老式"设备驱动,这样的设备驱动(程序)大多需要直接处理外部设备的操作,需要考虑类如中断向量等问题。而分层的PnP设备,则一般只有最底层的"端口"设备驱动才需要考虑这些问题。 我们手头的"Beep"设备实际驱动的是扬声器,其本身并不产生中断请求,所以不需要考虑设置中断向量和中断响应程序的问题。实际上,严格地说,中断还是需要的,因为设备驱动需要定时(例如每隔几个毫秒)把代表着音频的数据写入扬声器的接口。但是,定时器是由内核提供和管理的资源,使用者只要将需要定时执行的程序(相当于中断服务程序)与定时器(相当于中断源)挂上钩就行了。 此外,有些比较费时的操作是不宜放在中断服务程序中执行的。因为只要一进入中断服务,进一步的中断就被关闭了。或者,在采用中断优先级的系统中,也只有优先级更高的中断才能发生。这样,如果关闭中断的时间过长,就会丢失别的中断请求。为此就需要把比较费时的操作放在中断服务程序外面,在开中断的条件下执行。这样的程序称为"延迟过程调用(Deferred Procedure Call)"即DPC。之所以说"延迟",是因为有关的操作本应在中断服务程序中完成,而现在则推迟到中断服务程序以后才来执行。关于DPC函数的机制后面还要详细介绍。 下面先安排好DPC,Beep设备对象的自定义扩充部结构中有个成分就是Dpc,这是个KDPC数据结构,用来设置和记录有关的信息,其中最重要的就是DPC函数BeepDPC()。

1.[DriverEntry() > KeInitializeDpc()] 
2.
3.VOID NTAPI 
4.KeInitializeDpc(IN PKDPC Dpc, IN PKDEFERRED_ROUTINE DeferredRoutine, 
5.IN PVOID DeferredContext) 
6.{ 
7./* Call the internal routine */ 
8.KiInitializeDpc(Dpc, DeferredRoutine, DeferredContext, DpcObject); 
9.} 
10.
11.VOID NTAPI 
12.KiInitializeDpc(IN PKDPC Dpc, IN PKDEFERRED_ROUTINE DeferredRoutine, 
13.IN PVOID DeferredContext, IN KOBJECTS Type) 
14.{ 
15./* Setup the DPC Object */ 
16.Dpc->TypeType = Type; 
17.Dpc->Number = 0; 
18.Dpc->Importance= MediumImportance; 
19.Dpc->DeferredRoutineDeferredRoutine = DeferredRoutine; 
20.Dpc->DeferredContextDeferredContext = DeferredContext; 
21.Dpc->DpcData = NULL; 
22.} 
 

Categories: 驱动开发

Overall Rating (0)

0 out of 5 stars

Leave your comments

Post comment as a guest

0 Character restriction
Your text should be more than 3 characters
  • No comments found