驱动的一些基本函数

RtlInitUnicodeString函数的作用是计算Unicode字符串的大小并且填充UNICODE_STRING结构,一般来说, Unicode字符串都是在代码中静态定义的,并且在运行中保持不变,所以在链接的时候就把UNICODE_STRING结构给填好是完全可能的并且是很 容易的,这样更容易理解、 更节省空间(省去8字节的UNICODE_STRING结构、最多3字节的对齐空间以及至少14字节调用RtlInitUnicodeString的代 码)。这就是我为什么不喜欢以上代码的原因,我经常使用CCOUNTED_UNICODE_STRING宏来完成它,这样上面的代码就可以用2行来完成:

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) 
  
      每发现一个可以驱动的设备,

驱动程序调用IoCreateDevice创建一个该对象。该 

对象有一个指针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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值