DriverEntry程序

DriverEntry程序

在以前的部分中,我讲了即插即用管理器装载硬件所需要的驱动并调用其AddDevice函数。一个特定的驱动程序可能会被一个以上的相似硬件所使用,并且存在一些只需要在其第一次加载时被执行一次的全局初始化操作。DriverEntry程序负责这些全局初始化操。

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,

   IN PUNICODE_STRING RegistryPath)

{

……

……

……

   }

 

注:

如果你用的是标准的编译程序,你能以“DriverEntry”程序为主入口点来调用内核模式驱动程序,这是因为在构建脚本(Build Script)中已经定义,其会命令连接器将其连接为默认的主入口点,你最好也令你的代码和这相符(否则修改构建脚本,但这有必要么?)

 

例子代码:

你可以用本章所讨论的想法用STUPID例子驱动试验。STUPID仅仅只实现了DriverEntry和AddDevice。其很想我初学的时候写的第一个驱动程序。

 

    在我描述你要往DriverEntry中写的代码之前,我打算说一下关于其自身的函数原型。你和我并不知道(除非我们仔细看了构建脚本中的编译器选项),在x86的计算机的编译器上,内核模式函数和你驱动程序中使用的函数都使用__stdcall约定。这虽然对你的编程没什么影响,但当你调试的时候你应该记住这一点。我有时使用extern “C”编译指令这是因为我通常在C++编译单元中打包我的代码——主要是这样我可以随时声明变量来而不用像C语言中只能在左大括号的后面声明。该预编译指令将禁止编译器生成C++形式的外部函数名修饰,这样连接器就能找到该函数。使用这个指令编译后,驱动程序入口函数的外部名将为_DriverEntry@8

关于DriverEntry原型的另一点是那些“IN”关键字。INOUT都是在DDK中被定义为空串的两个噪声词。其最初的目的是令其为注释功能。也就是说当你看到IN参数,你应该认为该参数是纯粹用于输入目的的,OUT参数则是用于输出目的,而IN OUT参数既用于输入也用于输出。其实,DDK头文件并不总是直观的使用这些关键字,也并不怎么使用它。一个例子:DriverEntry声明了DriverObject指针是IN型;当然,虽然你没改变指针,但你确实改变其指向的对象。

关于需要你注意的该函数原型的最后一点是其声明了一NTSTATUS值作为返回值。NTSTATUS实际上只是一个长整型。但为了你代码更好的可读性你应该用类型定义名NTSTATUS来代替LONG。很多内核模式支持程序都能返回NTSTATUS状态码,状态码的定义你可以在DDK的头文件NTSTATUS.H中找到。关于状态码在下一章我将会介绍更多。目前,你只需知道当你的DriverEntry函数执行完毕时其会返回状态码。


DriverEntry概述

DriverEntry的第一个参数是一个指针,指向一个刚被初始化的驱动程序对象,该对象就代表你的驱动程序。WDM驱动程序的DriverEntry程序会完成对这个对象的初始化并返回。而非WDM驱动程序则有大量额外的工作要做——它们必须探测自己的硬件,为硬件创建设备对象(用于代表硬件),配置并初始化硬件使其正常工作。而对于WDM驱动程序,颇麻烦的硬件探测和配置工作由PnP管理器自动完成,我将在第六章讨论PnP。如果你想知道非WDM驱动程序是如何初始化自身的,参见Art Baker的《The Windows NT Device Driver Book (Prentice Hall, 1997)》、Viscarola和Mason的《Windows NT Device Driver Development (Macmillan, 1998)》。

    DriverEntry的第二个参数是注册表中设备服务键的键名。这个串不是长期存在的(函数返回后可能消失),如果你打算以后再使用该串则必须先把它复制到安全的地方。在WDM中我曾经构造该串的唯一用途是将其作为WMI注册的一部分(请参阅第10章)

WDM驱动程序的DriverEntry的主要工作是给驱动对象填入各种的函数指针。这些指针为操作系统指明了驱动程序容器中各种子程序的位置。驱动对象中的指针包括如下内容:

n         DriverUnload

    用来来指向你创建的清除程序。I/O管理器只会在卸载驱动之前调用该程序。如果没有任何需要清除的,你需要给系统一个DriverUnload函数来动态地卸载你的驱动。

n         DriverExtension->AddDevice

    用来指向AddDevice函数。即插即用管理器会为每一个你负责的硬件实例调用一次AddDevice。由于AddDevice对于WDM驱动的工作方式非常重要,在下一节(AddDevice程序)中我将具体阐述它是如何工作的。

n         DriverStartIo

如果驱动程序使用标准的队列I/O请求方式,你应该设置这组驱动对象指向StartIo程序。不要担心(但确实是这样)是否明白我所说的“标准的”队列方式。在第五章你就全明白了,而且你会发现WDM驱动不应该使用它。

n         MajorFunction

    I/O管理器初始化该函数指针向量来指向哑元派遣函数,该哑元派遣函数返回每个请求的失败。你大概只需要处理几种IRP的类型——否则驱动程序基本上就废了,所以至少应该设置与那几种IRP类型相对应的指针元素,使它们指向相应的派遣函数。第五章详细地讨论了IRP和派遣函数。现在你只需知道你必须要处理两种IRP而且可能还会有一些其他的IRP需要处理。

    如下是一个比较完整的DriverEntry程序:

 

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath)
    {
       

1


    DriverObject->DriverUnload = DriverUnload;
    DriverObject->DriverExtension->AddDevice = AddDevice;
    

2


    DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 
      DispatchWmi;
    

3


……
……
……
    

4


    servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool,
      RegistryPath->Length + sizeof(WCHAR));
    if (!servkey.Buffer)
      return STATUS_INSUFFICIENT_RESOURCES;
    servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
    RtlCopyUnicodeString(&servkey, RegistryPath);
    servkey.Buffer[RegistryPath->Length/sizeof(WCHAR)] = 0;
    

5


    return STATUS_SUCCESS;
    }

 

 

1.      这两条语句设定了在驱动中别处的函数指针的入口点。我选择了能代表它们的函数:DriverUnload和AddDevice

2.      每个WDM驱动必须处理PNP,POWER,和SYSTEM_CONTROL I/O请求。在这里你要给这些请求派遣函数。现在的这个IRP_MJ_SYSTEM_CONTROL在早期的Windows XP DDK的测试版里被叫做IRP_MJ_SYSTEM_WMI,所以我在这里把这个派遣函数叫做DispatchWmi。

3.      在省略号的位置,你可以添加一些附加的MajorFunction指针。

4.      如果你打算在你驱动的别的地方要访问注册表服务键值,这里是一个不错的复制RegistryPath字符串的地方。我已经假设你在别处已经声明了一个名叫servkey的UNICODE_STRING的全局变量。我将在下一章中描述Unicode字符串的工作方式。

5.      若执行成功则返回STATUS_SUCCESS。若发生了错误,其会返回一个错误代码,该代码将来自NTSTATUS.H中已经被定义到的标准错误代码,也可能来自由你自己定义的代码。STATUS_SUCCESS的值为0。

 

 

子程序命名

许多驱动程序作者其驱动中的子程序命名都包含了驱动程序的名字。例如,许多程序员会将AddDevice和DriverUnload函数命名为如Stupid_AddDevice,Stupid_DriverUnload这样的形式。Microsoft早期版本的WindDbg调试器强制要求程序员使用像这样的约定因为其已经有了一个全局命名空间了。该调试器之后的版本没有这个限制,但你仍可以在DDK的例子中看到这一约定。

现在我。是一个狂热的代码重用者和一个冷漠的打字员。就我来说,在每个项目中给同样的子程序用一个名字是很舒服的事。这样我只需要将一代码中的子程序直接粘贴到另一段代码上而不再需要做一系列名字上的修正了。我还可以清晰地以此来比较两个驱动的不同。

 

DriverUnload

WDM驱动的DriverUnload函数的目的是清理DriverEntry所做的所有全局初始化操作。其几乎无事可做。若你在DriverEntry中复制了RegistryPath字符串,DriverUnload则会在此释放内存。代码如下:

 

VOID DriverUnload(PDRIVER_OBJECT DriverObject)

   {

   RtlFreeUnicodeString(&servkey);

   }

 

 

若DriverEntry返回失败,系统不会调用DriverUnload程序。因此,DriverEntry若在返回一错误之前产生了一些需要清理的结果,其才得执行该程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章