阅读:4308回复:19
WIN2000驱动程序设计(翟洪涛)阅读入门精华分享
本人是驱动开发的绝对新新菜鸟,为了写驱动,翻阅了很多资料,但总是对流程不清晰,好在,找到了标题里的chm,翻看了,里面有好多我们这些超级新手非常有帮助和指点的东东,不敢独占,发出来和大家分享。
使用渐进的开发方法 一旦最初的分析和设计完成,就要开始编写代码了。按照以下的步骤进行可以减少调试的时间: 1. 确定驱动程序需要哪些内核模式对象。 2. 确定驱动程序需要哪些上下文环境或者状态信息和这些信息的存储位置。 3. 3.首先编写DriverEntry和Unload例程,最初不要增加即插即用支持,这样允许通过控制面板手动的测试驱动程序的装载和卸载。 4. 4. 添加处理IRP_MJ_CREATE和IRP_MJ_CLOSE的操作和一些不需要进行设备的访问例程。然后可以使用一个简单的WIN32程序调用CreateFile和CloseHandle来测试。 5. 5. 添加寻找和分配驱动程序的硬件的代码,还有在驱动程序被卸载后的重新分配硬件的代码。如果硬件支持即插即用,这一步测试硬件和驱动程序的自动加载能力。 6. 6. 添加处理IRP_MJ_XXX函数的派遣例程,最初的例程应该没有使用物理设备,后来新的代码应该使用简单的WIN32程序进行测试,例如ReadFile和WriteFile调用,或者其它支持的函数。 7. 最后完成Start I/O例程,ISR和DPC例程。现在可以使用真实的数据和硬件进行测试。 应一个有用的提示: 当硬件的确切行为是不能肯定的时候,增加一个DeviceIoControl函数,这个例程直接访问设备的寄存器,这时可用一个简单的WIN32程序直接控制设备寄存器。记得在发布最后的驱动程序版本的时候删除这个功能。 |
|
沙发#
发布于:2008-01-27 17:16
状态返回值
WIN2000的内核模式部分使用32Bits的状态值去描述一个特殊操作的结果。这些代码的数据类型是NTSTATUS,在三种情况下使用这个状态代码。 1. 使用任何WIN2000内部函数的时候,NTSTATUS值汇报调用成功或者失败。 2. I/O管理器调用一个驱动程序支持的回调例程,例程通常返回一个NTSTATUS值给系统。 3. 在完成I/O请求的处理之后,驱动程序必须用一个NTSTATUS值标记IRP,这个值最终被映 射到一个Win32 ERROR_XXX代码。NTSTATUS代码于Win32 error代码不是同一回事。I/O管理器提供它两个之间的映 射。DDK的帮助描述它们的映 射关系,它值得一读。 NTSTATUS.h中描述大量的NTSTATUS值的符号名。所有的这些名字是STATUS_XXX的形式,XXX描述真正的状态信息。STATUS_SUCCESS,STATUS_NAME_EXISTS都是这些名字的实例。 当一个系统例程返回一个NTSTATUS值,DDK头文件提供一个方便的宏去测试调用的成功于失败信息。下面的代码片段解释了这个技术: NTSTATUS status; : status = IoCreateDevice ( ... ); if ( !NT_SUCCESS( status )) { // 产生错误,清除和退出 : } 一定要检查系统例程调用的返回值,否则错误将传送给驱动程序代码的其它部分,也可能是系统代码。尽早的捕捉到错误是软件工程师的主要的规则。 |
|
板凳#
发布于:2008-01-27 17:29
编写一个DriverEntry例程
每一个WIN2000内核模式或者WDM驱动程序都有一个DriverEntry例程。这个例程初始化各个驱动程序的数据结构和准备其它驱动程序部分的运行环境。 运行的上下文环境 再加载驱动程序的时候I/o管理器调用DriverEntry例程。DriverEntry例程运行的IRQL是PASSIVE_LEVEL,这意味着它有权访问分页的存储器资源。 DriverEntry例程收到一个指向它自己的驱动程序对象的指针,这个指针必须初始化。它也收到一个包含注册表中驱动程序的服务键的UNICODE_STRING结构。WDM驱动程序中很少使用这个这注册名,内核模式的驱动程序依赖这个字符串去取出在系统注册表中存储的驱动程序特定的参数。这个注册的字符串的形式大概是HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\ DriverName。 DriverEntry例程的作用 虽然WDM和内核模式的驱动程序有些不同,但大体上一个DriverEntry例程的执行步骤如下: 1. DriverEntry例程查找它所控制的硬件,被分配的硬件被标识它在本驱动程序的控制之下。 2. 用指向其它驱动程序例程入口点的指针初始化来初始化驱动程序对象。 3. 如果驱动程序管理一个多功能的控制器,使用IoCreateController去创建一个控制器对象,然后初始化控制器的extension。 4. 使用IoCreateDevice去为每一个它控制的物理的或者逻辑的设备创建一个设备对象,然后初始化设备extension。 5. 通过IoCreateSymbolicLink函数使这个设备可以被Win32子系统看见。 6. 将设备与一个中断对象联系起来,如果ISR需要使用DPC对象,就在这一步创建和初始化它们。 7. 重复第4步到第6步,直到完成这个驱动程序所有的物理的和逻辑的设备。 8. 如果成功DriverEntry例程应该返回STATUS_SUCCESS给I/O管理器。 DriverEntry 例程参数 意义 IN PDRIVER_OBJECT pDriverObject 驱动程序对象地址 IN PUNICODE_STRING pRegistryPath 驱动程序服务键的注册表路径字符串 Return value STATUS_SUCCESS或者STATUS_XXX 表6.1 DriverEntry例程的参数 在WDM驱动程序中不执行步骤1和3到6,它们被放到AddDevice例程中了。如果DriverEntry例程因为某个原因而使初始化的过程失败,就应该释放任何占用的资源,返回合适的NTSTATUS失败代码给I/O管理器。 |
|
地板#
发布于:2008-01-27 17:31
宣布驱动程序例程入口点
I/O管理器可以找到DriverEntry例程,因为它有一个公认的名字(实际上,连接器使用一个命令行开关声明DriverEntry的地址,然而,DDK文档要求入口点命名为DriverEntry),其它的驱动程序例程不需要固定的名字,因此,I/O管理器需要一些方法定位它们。连接机制是驱动程序对象,驱动程序对象包含指向其它驱动程序例程的指针。DriverEntry例程负责设置这些指针。 这些函数指针有两种: 1. 在驱动程序对象中有明确的名字的函数。 2. 在驱动程序的MajorFunction队列中列出的IRP派遣函数。 下列代码片段是初始化两种函数指针的例子: pDO->DriverStartIo = StartIo; pDO->DriverUnload = Unload; // Initialize the MajorFunction Dispatch table pDO->MajorFunction[ IRP_ MJ_CREATE ] = DispatchCreate; pDO->MajorFunction[ IRP_MJ_CLOSE ] = DispatchClose; : |
|
地下室#
发布于:2008-01-27 17:54
驱动程序初始化程序实例
驱动程序初始化程序实例 下面的例子说明了内核模式的驱动程序怎样初始化它自己。 这个最小的驱动程序必须被手动加载,它没有访问任何的硬件,但是它创建了一个名字是MINIMAL0的内部设备名和一个符号连接名(MIN1),它们都在一个简单的C语言文件Driver.cpp中,它的头文件Driver.h声明了非硬件的驱动程序指定的信息,例如DEVICE_EXTENSION。 DriverEntry例程 在这个例子中,DriverEntry例程比较小而且简单,它的工作包括: 1. 宣布其它例程的入口点。对于这个例子只有Unload例程。 2. 创建一个逻辑设备。对于测试的目的,仅仅创建一个简单的设备。 NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath ) { ULONG ulDeviceNumber = 0; NTSTATUS status; // 如果这个驱动过程控制一个硬件设备,应该将定位硬件的代码放到这儿 // 可以调用IoReportResourceUsage,端口,IRQs和DMA信道会被标记上使用中,它们就 // 在这个驱动程序的控制之下了。 // 宣布其它例序的入口点 pDriverObject->DriverUnload = DriverUnload; // 随着时间的流逝,MajorFunction队列将会被充满 // 创建一个新的设备对象 status = CreateDevice(pDriverObject, ulDeviceNumber); return status; } CreateDevice例程 真正创建设备对象的工作是在CreateDevice例程中完成,虽然这个例程没有做很多的工作,模块化这个工作是合适的,它的任务包括: 1. 选择和组成一个内部设备名。 2. 创建这个内部的设备名。调用IoCreateDevice的时候要指定DEVICE_EXTENSION的大小。 3. 初始化DEVICE_EXTENSION。在这个最小的驱动程序中,DEVICE_EXTENSION保存一个指向设备对象后指针和内部设备名和符号连接名。 4. 构造一个符号连接名和连接这个名字。 // CreateDevice添加一个新的设备 // pDriverObject - I/O管理器传递这个参数 // ulDeviceNumber - 逻辑设备号码(以0为基础) NTSTATUS CreateDevice ( IN PDRIVER_OBJECT pDriverObject, IN ULONG ulDeviceNumber ) { NTSTATUS status; PDEVICE_OBJECT pDevObj; PDEVICE_EXTENSION pDevExt; // 构造内部设备名 CUString devName("\\Device\\MINIMAL"); // 对于"minimal"设备 devName += CUString(ulDeviceNumber); // 现在创建设备 status = IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj ); if (!NT_SUCCESS(status)) return status; // 初始化设备Extension pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension; pDevExt->pDevice = pDevObj; //后指针 pDevExt->DeviceNumber = ulDeviceNumber; pDevExt->ustrDeviceName = devName; CUString symLinkName("\\??\\MIN"); // 构造符号连接名 symLinkName += CUString(ulDeviceNumber+1); // 以1 为基础 pDevExt->ustrSymLinkName = symLinkName; //创建符号连接名 status = IoCreateSymbolicLink( &(UNICODE_STRING)symLinkName, &(UNICODE_STRING)devName ); if (!NT_SUCCESS(status)) { IoDeleteDevice( pDevObj ); return status; } return STATUS_SUCCESS; // Made it } CreateDevice使用了一个C++类CUString,使用这个类使Unicode字符串添加到设备名简单化,这个类会在以后讨论。 |
|
5楼#
发布于:2008-01-27 17:57
Unload例程的作用
虽然各个驱动程序的Unload例程不尽相同,但是它大致执行下列工作: 1. 对于一些硬件,设备的状态应该存储在注册表中。在下次DriverEntry例程执行的时候驱动程序可以恢复到最近的状态。例如,声卡驱动程序可能存储当前它的音量设置信息。 2. 如果这个设备支持中断,Unload例程必须禁止它们和断开它们与中断对象的连接。一旦中断对象被删除,设备将不会产生任何中断请求。 3. 释放属于驱动程序的任何硬件。 4. 从Win32的名字空间移除符号连接名。这个动作可以调用IoDeleteSymbolicLink来实现。 5. 使用IoDeleteDevice移除设备对象。 6. 如果管理多部件的控制器,为每一个连接到控制器的设备重复步骤4和5,然后移除控制器对象它自己,使用IoDeleteController函数。 7. 重复步骤4到6,移除所有的术语这个驱动程序的控制器和设备。 8. 释放驱动程序持有的任何缓冲池。 对于WDM驱动程序,这些任务在RemoveDevice例程中执行。 注意,一个驱动程序的Unload例程不是在系统被关闭的时候被调用。在系统被关闭的时候任何特殊的工作在特殊的shutdown例程中执行。 驱动程序卸载实例 在最小的驱动程序中,Unload例程执行取消DriverEntry例程中执行的操作,它必须删除每一个符号连接名和创建的设备对象,Unload例程依赖驱动程序对象的连接列表。 驱动程序对象的DeviceObject域指向驱动过程控制的第一个设备,每个设备通过NextDevice域指向下一个设备。记得一个DEVICE_EXTENSION结构里有一个指向符设备对象的后指针。 // DriverUnload pDriverObject - I/O管理器传递的参数 VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject ) { PDEVICE_OBJECT pNextObj; // 循环每一个驱动过程控制的设备 pNextObj = pDriverObject->DeviceObject; while (pNextObj != NULL) { //从设备对象中取出设备Extension PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) extObj->DeviceExtension; // 取出符号连接名 UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName; IoDeleteSymbolicLink(&pLinkName); // 删除符号连接名 //一个问题...我们需要删除设备对象,但是设备对象是被pNextObj存储着的,如果我们先删 //除这个设备对象,我们将不能在列表中到达下一个设备,除非创建另一个指针,可以使用设备 //Extension的后指针。所以先刷新下一个指针,这样会简单些... pNextObj = pNextObj->NextDevice; // 使用设备Extension 删除设备 IoDeleteDevice( pDevExt->pDevice ); } //最后, 如果在DriverEntry 例程中分配有硬件,可以使用IoReportResourceUsage //函数在这儿进行释放 } |
|
6楼#
发布于:2008-01-27 18:02
测试驱动程序
甚至是无用的最小的驱动程序,也有好几种方法进行测试,可以测试以下几个方面: 1. 是否编译连接成功。 2. 是否在加载和卸载的时候毁坏系统。 3. 是否创建设备对象和Win32符号连接成功。 4. 是否在unload的时候释放所有它占用的资源。 这些目标不是非常高,但它是购建剩下的有用的驱动程序的一个重要的里程碑。 Win2000 DDK Windows 2000设备驱动开发包必须被安装在开发的系统中,通常,它安装在一个名字是NTDDK的活页夹下面,DDK允许两种不同的编译环境,checked或者free,checked编译环境像Visual C++的Debug编译环境一样,而free编译环境则像Release编译环境一样。这两个不同编译环境有不同的库的设置,Libchk和Libfre,最初的驱动程序最好使用checked编译环境执行。 创建驱动程序的结果 一旦驱动程序成功的编译和连结后,就创建了一个*.SYS的文件,这个例子是Minimal.SYS。这个文件必须在它被加载之前被复制到适当的路径,一般是\WINNT\System32\Drivers,WINNT这个部分取决于安装WIN2000时选择的名字。 手动安装内核模式的驱动程序 要安装一个新的设备驱动程序,仅仅拷贝驱动程序文件到目标目录是不够的,对于非WDM的驱动程序,适当的标识必须在驱动程序加载之前放入注册表, 注册表的HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services路径下保持了一个可用的设备驱动程序列表,每个驱动程序都有一个独一无二的子键,它的名字必须于驱动程序*.SYS的名字相同,只是没有.SYS的扩展名。 对于每个驱动程序子键,必须提供三个DWORD值: ErrorControl,Start和Type,这些标识描述了当驱动程序被加载(例如,系统导入或者需要使用)的时候,它怎样汇报错误(例如,MsgBox),和一个非常一般的驱动程序类型(内核模式或者文件系统)的描述。对于最小的驱动程序这些设置可能是: ErrorControl = 1 Start = 3 Type = 1 随意地,必须设置一个DisplayName名字,当使用设备管理器的时候,这个名字显示在计算器的控制面板窗口。 一个文件Minimal.reg自动的设置这个子键和值,仅仅只需要在Windows Explorer中双击文件名就可以了,添加之后重启Windows 2000。 加载驱动程序 一旦安装到系统中,设备的注册键的Start值可以设定系统引导的时候自动加载或者不加载驱动程序,如果Start值是3,意思是需要加载。在控制面板可以开始和停止驱动程序。 服务控制管理器(SCM)处理了大部分的动态的加载设备驱动程序的工作,SCM管理大多数的运行在Win2000下的软件组件。例如,它可以开始,停止和控制服务。一个服务是一个运行在用户模式的上下文环境之外的程序。 服务控制管理器处理了大部分的设备驱动程序动态加载的工作,它管理运行在Win2000下的大部分的软件组件。例如,它可以开始,停止和控制这些服务。一个服务是运行在笨拙的用户的上下文环境之外的程序,计算机管理控制台为加载设备的驱动程序提供有用的信息和一个手动的加载和卸载内核模式驱动程序的机制。 WIN2000计算机管理控制台 一旦这个最小的驱动程序被成功的创建和拷贝到驱动程序路径中,计算器管理控制台可以检测到它的出现。打开设备管理器,它会显示系统中现在安装的设备列表,这个最小的驱动程序将在里面。通过双击驱动程序图标,选择常规标 签,可以选择开始和停止驱动程序。 系统工具中的系统信息工具也可以显示驱动程序的信息。 |
|
7楼#
发布于:2008-01-27 18:16
启用特殊的功能代码
如果想要启用特殊的功能代码,驱动程序必须声明一个响应这个请求的派遣例程,声明机制是很简单的,只需要在DriverEntry例程中存储派遣例程函数的地址到驱动程序对象的MajorFunction表的适当的位置,I/O功能代码是这个表的索引。如下列代码片段所示: NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDO, IN PUNICODE_STRING pRegPath ) { : pDO->MajorFunction[ IRP_MJ_CREATE ] = DispCreate; pDO->MajorFunction[ IRP_MJ_CLOSE ] = DispClose; pDO->MajorFunction[ IRP_MJ_CLEANUP ] = DispCleanup; pDO->MajorFunction[ IRP_MJ_READ ]= DispRead; pDO->MajorFunction[ IRP_MJ_WRITE ] = DispWrite; : return STATUS_SUCCESS; } 注意,每一个I/O功能代码(表的索引)是被一个IRP_MJ_XXX形式的符号所标识,它们被NTDDK.h和WDM.h文件定义,这些符号常量总是被用在固定的地方。 声明方法也允许一个单一的例程被用来处理多个请求类型。DriverEntry可以放置公共的派遣例程,因为IRP命令包含请求代码,公共的函数可以被作为合适的例程而被调用。 最后,驱动程序不支持的例程应该被DriverEntry例程忽略,I/O管理器在调用DriverEntry例程之前就已经将整个的MajorFunction表填充为_IopInvalidDeviceRequest。 决定需要支持那些功能代码 所有的驱动程序必须支持IRP_MJ_CREATE功能代码,因为这个功能代码是用来响应Win32用户模式的CreateFile调用,如果不支持这功能代码,Win32程序就没有办法获得设备的句柄,类似的,驱动程序必须支持IRP_MJ_CLOSE功能代码,因为它用来响应Win32用户模式的CloseHandle调用。顺便提一下,系统自动调用CloseHandle函数,因为在程序退出的时候,所有的句柄都没有被关闭。 其它的功能代码是否支持依赖它控制的设备的性质,当编写分层的驱动程序的时候,高层的驱动程序必须支持连接所有的低层驱动程序的例程,因为用户请求先通过高层的驱动程序。 IRP功能代码 意义 调用者 IRP_MJ_CREATE 请求一个句柄 CreateFile IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle IRP_MJ_CLOSE 关闭句柄 CloseHandle IRP_MJ_READ 从设备得到数据 ReadFile IRP_MJ_WRITE 传送数据到设备 WriteFile IRP_MJ_DEVICE_CONTROL 控制操作 DeviceIoControl IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) 没有Win32调用 IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers FlushConsoleInputBuffer PurgeComm IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown 表7.1常用的功能调用 |
|
8楼#
发布于:2008-01-28 09:25
good!good!
|
|
9楼#
发布于:2008-05-22 16:23
呵呵,不错
本来一头雾水的,现在还剩一脸迷茫了~ |
|
10楼#
发布于:2008-05-23 09:05
参考一下,希望可以从中得到启发
|
|
11楼#
发布于:2008-07-04 22:25
不错, 不错
|
|
12楼#
发布于:2008-08-02 15:55
确实有点迷茫了,,哈哈,,不过,,我相信,,我再看看其他知识点后,回看,,一定会大有收获的!!
|
|
13楼#
发布于:2008-08-11 15:54
现在又看了一遍,感觉没那么陌生了,虽然现在好多还是看把懂!!过一段时间再来,,温故温故!!
|
|
14楼#
发布于:2008-08-25 19:53
看完就略懂了,呵呵
|
|
15楼#
发布于:2008-11-13 19:08
顶啊,正么好的东西
|
|
16楼#
发布于:2009-06-02 21:28
不错学习了
|
|
17楼#
发布于:2009-06-07 17:34
先copy下来了,回头好好看看
|
|
18楼#
发布于:2010-06-18 08:35
呵呵,自己的书,顶
|
|
19楼#
发布于:2010-06-29 14:14
|
|