CCOUNTED_UNICODE_STRING "//Device//DevName", usDeviceName, 4
CCOUNTED_UNICODE_STRING "//??//DevName", usSymbolicLinkName, 4
如果你认同我的做法的话,也可以在自己的驱动程序中这样定义驱动名称和符号连接名称:
.const
CCOUNTED_UNICODE_STRING "//Device//devVirtToPhys", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "//??//slVirtToPhys", g_usSymbolicLinkName, 4
(注:原作者的宏在处理英文的Unicode字符串的时候是不错的,但是中文字符串就不行了,所以如果用到中文串,还是乖乖地动态转换最方便,常用的方法 是先用RtlInitAnsiString函数生成一个ANSI_STRING结构,再用RtlAnsiStringToUnicodeString函数 将ANSI_STRING转换到UNICODE_STRING即可,把这两句写成一个子程序或者宏的话,使用起来也是很方便的)。
在早些的Windows NT版本中,对象管理器中的"/??"目录是没有的,所以在那种情况下使用要将"/??"改为"/DosDevices",这种用法在后续的 Windows版本中也可以使用。为了向前兼容,系统在根目录下创建了一个"/DosDevices"连接,直接指向"/??"目录。
//
KeInitializeSpinLock
i)自旋锁(SpinLock)
驱动程序可以在初始化时调用KeInitializeSpinLock创建该对象。在任何代码段访问被保护的数据之前,先调用 KeAcquireSpinLock试图获得该对象的所有权,如果成功,该段代码被系统提升至DISPATCH_LEVEL,进行数据访问。访问完毕后须 调用KeRelease SpinLock释放所有权,运行级别也被恢复。此方法只适用于同步运行级别小于等于DISP ATCH_LEVEL的代码,主要用于多CPU的情形。此外,还有一种中断自旋锁用于与中断处理过程同步,可以将较低级别的代码提升到需要与之同步的中断 DIRQL。
ii)控制器(Controller)
该对象主要用于同步一个驱动程序中的多个设备,保证它们能顺序地访问特定的代码或数据。该对象在驱动程序初始化调用 IoCreateController被创建。设备在StartIo过程中调用IoAllocateController请求获得Controller对 象的独占权。使用完后调用IoFreeController释放。驱动程序停止时调用IoDeleteController从内存删除该对象。该对象有一 个指针ControllerExtension指向一块由驱动程序定义的结构,其中保存有此驱程序的公共数据。
iii)适配器(Adapter)
该对象用于同步多个设备(不一定在一个驱动程序中)对DMA通道的使用。该对象在系统启动侦测硬件时自动被创建。驱动程序在初始化时调用 HalGetAdapter获得该对象的指针。设备在StartIo过程中调用IoAllocateAdapterChannel请求获得DMA通道的独 占权,然后开始传输数据。使用完后调用IoFreeControllerChannel释放DMA通道。
iv)DPC
由于DPC队列中的对象总是被系统顺序地处理,所以也可以将需要同步的代码做成Dpc过程,需要调用时将相应的DPC对象放到队列的末尾即可。
v)其他
同用户模式的应用程序类似,驱动程序也可以使用多线程,也提供了一套用来同步的对象,如Event,Mutex,Semaphore,Timer,Thread。其中Event对象可以被命名,不同的驱动程序可以利用同名的Event对象同步对公共数据的访问。
windows nt下内核模式设备驱动程序的结构和运行
一般来说,设备驱动程序的任务主要有二:第一,接受来自用户程序的读写请求,把
用户的数据传送给设备,或把从设备接收到的数据传送给用户;第二,轮询设备或处理
来自设备的中断请求,完成数据传输。
1.2.1 驱动程序与用户程序的通信
i/o管理器把每一个设备对上层都抽象成了文件,所以在win32用户程序中只要通过以
下几条简单的文件操作api函数就可以实现与驱动程序中的某个设备通信(请注意,一个
驱动程序可以驱动多个设备):
函数名 功能
createfile 打开一个设备,准备进行数据传输。返回一个与设备相关的句柄。
closehandle 关闭一个由createfile打开的设备。
readfile 从设备读取数据。
writefile 向设备写数据。
deviceiocontrol 对设备进行一些自定义的操作,比如更改设置等。
表一
1.2.2 driverentry过程
这是每一个设备驱动程序的入口,每次该程序启动时被系统自动调用。大部分的设备
初始化的工作都在这个过程中完成。包括设置响应各种用户请求的过程的入口,使i/o管
理器能知道当用户的打开、关闭、读写等请求到来时各应调用那些过程来处理。驱动程
序中只有本过程的名字"driverentry"是固定的,以下列出的所有过程都要由本过程向系
统注册。
如果该驱动程序不响应任何请求的话,只要一个driverentry过程就可以构成一个能运
行的驱动程序。
1.2.3 unload和shutdown过程
unload过程负责在驱动程序被停止前做一些必要的处理。比如释放资源,记录最终状
态等。shutdown过程在系统即将关闭时被调用,与前者的区别在于不用释放任何资源。
1.2.4 dispatchopen和dispatchclose过程
这两个过程在用户调用createfile和closehandle时被调用,为即将到来的读写操作做
备,或做一些读写完成后的必要处理。
1.2.5 dispatchread, dispatchwrite与startio过程
这前两个过程在用户调用readfile和writefile时被调用。它们先做一些检验用户请求
合法性的工作,然后启动一个被称为startio的过程开始实际的与硬件间的数据传输。i
/o管理器还通过irp为它们提供了一个指向用户缓冲区的指针,用于与用户程序交换数据
。详情请见1.3.2
1.2.6 接受自定义的其他请求
这两个过程在用户调用deviceiocontrol时被调用。它通过irp获得用户的请求号,以
及一个指向用户缓冲区的指针,可以与用户程序进行通信。
1.2.7 中断处理过程(isr)
这些过程在中断发生时被系统调用。
1.2.8 推迟过程(deferred procedure)
这些过程用来在较低的运行级别完成较高运行级别过程(如中断处理过程)的一
些任务。详情请见1.3.3
1.3.2 几个对象
i) I/O请求包(IRP)
I/O管理器每收到一个来自用户的请求就创建一个该结构,并将其作为参数传给驱
动程序的DispatchXxx、StartIo过程。该结构中存放有请求的类型,用户缓冲区的首地
址,用户请求数据的长度等信息。驱动程序处理完这个请求后,也在该结构中添入处理
结果的有关信息,调用IoCompleteRequest将其返回给I/O管理器,用户程序的请求随即
返回。
ii) DPC
当驱动程序中要用到Dpc过程时,需要创建该对象。具体作用请见1.3.3。
iii) 驱动程序对象(DriverObject)
该对象在驱动程序被启动时由I/O管理器创建, 保存有该程序处理各种请求的过程
入口、该程序所驱动的全部设备对象的链表等。
iv) 设备对象(DeviceObject)
每发现一个可以驱动的设备,
对象有一个指针DeviceExtension指向一块由驱动程序定义的结构,其中保存有关此设备
的如端口号,中断向量等全部信息。
v) 中断对象(Interrupt)
该对象在驱动程序调用IoConnectInterrupt时创建,存有中断及处理的过程的信息。
当一个中断发生时,I/O管理器用它寻找对应的处理过程。
1.3.3 推迟过程调用(Deferred Procedure Call)
由于中断处理过程运行于较高的DIRQL级, 它们能屏蔽许多级别小于或等于它们的过程
的执行,如果它们占用CPU时间过长,很容易使系统性能下降。因此中断处理过程应将一
些不是很紧急的任务放在被称为Dpc的过程中,在完成数据传输等紧急任务后将一个DPC
对象放在系统DPC队列的末尾,然后退出,尽量早地让出CPU。系统将在完成所有DIRQL级
的任务后处理DPC队列,在DISPATCH_LEVEL执行每一个DPC 对象指定的Dpc过程,完成中
处理断过程未尽的任务。
1.3.6缓冲的i/o与直接i/o
在驱动程序创建了一个设备后,可以通过设置deviceobject的flags域的值来将设备设置成缓冲的i/o或直接的i/o。
如果该值被设为do_buffered_io,每当i/o管理器收到一个读写请求,就在内存的非分 页区分配一块与用户区大小相同的区域,并将首指针存放于irp对象的associatedirp.s ystembuffer中,驱动程序就通过这个缓冲区与用户交换数据。每当一个读请求被完成时 i/o管理器自动将该缓冲区中的内容复制到用户区,并释放该区域。
如果用户区大于一页(在80x86上为4096字节),一般将该值设为do_direct_io。这时每当i/o管理器收到一个读写请求,先锁定用户 区的物理内存,然后为其创建一个内 存描述表(mdl),并将该表的首指针存放于irp对象的mdladdress中,驱动程序可以通过调用 mmgetsystemaddressformdl获得用户区在系统空间中的地址。每当一个读请求被完 成时i/o管理器自动将该区域解锁。
1.3.7定时
为了防止当设备出现某种故障时导致读写请求超时,或需要定时轮询某些设备的状态 ,驱动程序需要设置一些定时器。驱动程序中有两种方法可以设置定时器。一种是调用ioinitializetimer将一个定时器过程iotimer与一 个设备对象联系起来。在调用iostar ttimer后,系统将每一秒钟调用一次iotimer,直至驱动程序调用iostoptimer。如果需要设置更小间隔的定时器,需要用到被称为 customtimerdpc的一种推迟过程调用机制。 它可以设置系统每隔一定时间将一个设置好的dpc对象放到dpc队列的末尾,执行一个指定的定时器dpc过程。这个时间间隔可以精确到100ns。