驱动开发入门winnt

来源:互联网 发布:js委托事件注册 编辑:程序博客网 时间:2024/05/17 01:12
本书中的样例程序都是 Windows NT 内核模式设备驱动程序。 本章介绍构建驱动程序以及理解本书样例所需的信息。但是本章可不是驱动程序大全,详尽讨论驱动程序的最好的信息来源就是 Art Baker 所著的 The Windows NT Device Driver Book: A Guide for Programmers 和 Windows NT Device Driver Kit (DDK) 所带的文档。

PREREQUISITES TO WRITING NT DEVICE DRIVERS

要建立 Windows NT 内核模式驱动程序开发环境,必须安装以下工具软件:

Windows NT Device Driver Kit (DDK) from Microsoft 为进行设备驱动的开发,需要安装 DDK 。DDK 包含开发设备驱动所需的头文件、库和工具。

32-bit compiler 要编译设备驱动还需要32位的编译器。我们强烈推荐使用微软的编译器来构建本书的样例。

Win32 Software Development Kit (SDK) 尽管编译本书的样例不需要,但我们还是推荐你安装上最新版本的 Win32 SDK。 而且,在用 DDK 工具构建设备驱动时,还应将环境变量 MSTOOLS 设为 Win32 SDK 所安装的位置。没安装 Win32 SDK 的话可以在控制面板里的“系统”程序中添加此环境变量来蒙混系统,让它以为已经安装了。

DRIVER BUILD PROCEDURE

Windows NT 4.0 Device Driver Kit 安装后在开始菜单加入了四个快捷方式:Free Build Environment、Checked Build Environment、DDK Help 和 Getting Started. Free Build Environment 和 Checked Build Environment 快捷方式都指向一个叫 SETENV.BAT 的批处理文件,但命令行参数不同。假设 DDK 安装在目录 E:/DDK40 下,则 Free Build Environment 快捷方式使用以下命令:

%SystemRoot%/System32/cmd.exe /k E:/DDK40/bin/setenv.bat E:/DDK40 free

而 Checked Build Environment 快捷方式使用以下命令:

%SystemRoot%/System32/cmd.exe /k E:/DDK40/bin/setenv.bat E:/DDK40 checked

两个快捷方式都启动了 CMD.EXE 并使其执行带有相应参数的 SETENV.BAT 文件。因为有 /k 开关,命令执行完后 CMD.EXE 仍然在运行。SETENV.BAT 设置了环境变量,这些变量都加到了 CMD.EXE 进程的环境变量列表里。 DDK 的工具都靠 CMD.EXE 来运行,因此就可以使用这些环境变量。SETENV.BAT 所设定的环境变量有:BUILD_DEFAULT、BUILD_DEFAULT_TARGETS、BUILD_MAKE_PROGRAM 和 DDKBUILDENV。

驱动程序都是用一个由 DDK 提供的叫 BUILD.EXE 的工具程序编译的。此程序以一个名为 SOURCES 的文件作为输入。SOURCES 文件中包含着目标可执行文件的文件名、目标可执行文件的类型(比如是 DRIVER 还是 PROGRAM)和要创建的可执行文件的路径。

DDK 自带的样例设备驱动程序中都有一个 makefile。然而,那可不是样例驱动的真正的 makefile。 样例驱动的 makefile 都只是包含进一个共有的 makefile,MAKEFILE.DEF。这个 MAKEFILE.DEF 保存在 DDK 安装目录的 INC 目录中。

以下是一个 DDK 样例的 makefile:## DO NOT EDIT THIS FILE!!! Edit ./sources. if you want to add a new source# file to this component. This file merely indirects to the real make file# that is shared by all the driver components of the Windows NT DDK#!INCLUDE $(NTMAKEENV)/makefile.def

本书中的一些驱动样例含有汇编语言文件(.ASM 文件)。直接在 SOURCES 文件里指定 .ASM 文件是不行的,必须创建一个名为 I386 的目录存放源程序文件。所有 .ASM 文件都必须放在 I386 目录下。BUILD.EXE 会自动用 ML.EXE 编译这些 .ASM 文件。

按照 SOURCES 文件指定的设置和平台相关的环境变量,BUILD.EXE 就生成了驱动程序或是应用程序。若在构建过程中遇到错误,就记录在一个叫 BUILD.ERR 的文件里。若有警告,则记录在 BUILD.WRN 文件里。 BUILD 还会生成一个 BUILD.LOG 文件,它记录着 BUILD 所使用的命令列表和命令返回的信息。

STRUCTURE OF A DEVICE DRIVER

正如每一个 Win32 应用程序都有一个入口点(main/WinMain)一样,每个内核模式驱动也都有个入口点,叫作DriverEntry. 一个叫 SYSTEM 的特殊进程负责加载这些驱动。在系统中,每个驱动都由一个设备名代表,这样每一个驱动都必须为其设备创建一个设备名。这项工作是靠 IoCreateDevice 函数完成的。若 Win32 函数需要打开设备驱动的句柄,驱动还需要在 DosDevices 对象目录下为设备创建一个符号链接。这又是靠调用 IoCreateSymbolicLink 函数来完成的。 一般来说,都在驱动的 DriverEntry 函数里为设备创建设备对象和符号链接对象并做一些驱动程序的或是与驱动相关的初始化。

本书的大多数设备驱动样例都是伪设备驱动。 这些驱动并不控制任何的物理设备,而是用来完成只能由设备驱动才能完成的工作(设备驱动工作在处理器的最高优先级模式——Intel 处理器的 Ring 0)。 另外,DriverEntry 提供其它函数的入口点,比如 OPEN、 CLOSE、DEVICEIOCONTROL 等等。 提供入口点就是向 device object 的一些域添加函数指针,而 device object 又是传递给 DriverEntry 的一个参数。

因为本书的大多数设备驱动样例都是伪设备驱动,所以 DriverEntry 函数都是一样的。只有与驱动相关的初始化是不同的。我们没有在每个驱动样例中都重复相同的代码,而是使用了一个宏。这个宏叫做

MYDRIVERENTRY:#define MYDRIVERENTRY(DriverName, DeviceId, DriverSpecificInit) /PDEVICE_OBJECT deviceObject = NULL; /NTSTATUS ntStatus; /WCHAR deviceNameBuffer[] = L"//Device//"##DriverName; /UNICODE_STRING deviceNameUnicodeString; /WCHAR deviceLinkBuffer[] = L"//DosDevices//"##DriverName; /UNICODE_STRING deviceLinkUnicodeString; //RtlInitUnicodeString (&deviceNameUnicodeString, deviceNameBuffer); /ntStatus = IoCreateDevice (DriverObject, /                            0, /                            &deviceNameUnicodeString, /                            ##DeviceId, /                            0, /                            FALSE, /                            &deviceObject /                            ); //if (NT_SUCCESS(ntStatus)) { /    RtlInitUnicodeString (&deviceLinkUnicodeString, deviceLinkBuffer);/    ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString, /                                        &deviceNameUnicodeString);/    if (!NT_SUCCESS(ntStatus)) {/        IoDeleteDevice (deviceObject); /        return ntStatus; /    } //    ntStatus=##DriverSpecificInit; //    if (!NT_SUCCESS(ntStatus)) {/        IoDeleteDevice (deviceObject); /        IoDeleteSymbolicLink(&deviceLinkUnicodeString); /        return ntStatus; /    } ///    DriverObject->MajorFunction[IRP_MJ_CREATE] = /    DriverObject->MajorFunction[IRP_MJ_CLOSE] = /    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch; /    DriverObject->DriverUnload = DriverUnload; /    return STATUS_SUCCESS; /} else { /    return ntStatus; /};

这个宏使用以下三个参数:

.第一个参数是驱动名,用来创建设备名和符号链接名。 .第二个参数是设备 ID,用来唯一标识设备。 .第三个参数是函数名,包含驱动相关的初始化。

这个宏扩展后会调用所需的函数,如 IoCreateDevice 和 IoCreateSymbolicLink。若成功,驱动调用由第三个参数指定的驱动相关的初始化函数。 若成功,此宏就向 DriverObject 中填入驱动提供的其它函数的指针。一旦在DriverEntry 中使用了此宏,就需要编写 DriverDispatch 和 DriverUnload 函数,因为此宏引用了这些函数。

此宏的定义可在配套光盘中的 UNDOCNT.H 里找到。

对驱动程序的所有请求都是以 I/O Request packet (IRP) 的形式发送的。 驱动程序希望系统通过 DriverEntry 里填入的函数指针来为所有的驱动请求调用相应的驱动程序函数。 今后的讨论中我们假定所有的驱动程序函数指针都指向 DriverDispatch 函数。

当应用程序用 CreateFile API 打开一个 device driver 的句柄时,调用 DriverDispatch 函数所用的参数 IRP 包含着 IRP_MJ_CREATE 的命令代码。当应用程序调用 CloseHandle API 函数关闭 device driver 句柄时,这个 IRP 就包含着 IRP_MJ_CLOSE 命令代码。当应用程序调用 DeviceIoControl API 函数与设备交换数据时,此 IRP 包含着 IRP_MJ_DEVICE_CONTROL 命令代码。 若驱动程序被多个进程使用,则可以使用 CREATE 和 CLOSE 入口点来完成进程各自的初始化
因为所有的 IRP 都要经过 DriverDispatch,就需要有一种方法来区分实际所请求的函数。通过 I/O Request Packet (IRP) 的 MajorFunction 域就是干这个的。request packet 中有用来完成请求所需的函数代号和其它的参数。当从系统中卸载驱动时,就会调用 DriverUnload 函数。与 DriverUnload 相同,DriverUnload 函数也是在 SYSTEM 进程上下文中调用的。一般来说,在 DriverUnload 函数里,驱动程序删除由 DriverEntry 创建的符号链接和设备名,并完成一些驱动相关的反初始化。

SUMMARY

在这一章里,我们讲到了构建 Windows NT 设备驱动程序的软件要求, 构建驱动程序的过程以及一个典型驱动的的结构。之后我们还解释了一个简单的宏,这个宏可以为一般驱动的生成入口代码。
原创粉丝点击