BDA驱动学习笔记(4):IRP
NT中的驱动采用分层结构,一个应用层的IO命令需要通过IO子系统,IO系统服务层,若干层的驱动,最后才能到达硬件,硬件有什么数据需要返回,也需要经过这些层,一点都不能省。某一层的驱动只能和自己相邻层的驱动联系,而且联系都要通过IO Manager,用一个名叫IRP的数据结构完成通信。IRP基本上是NT驱动架构中最重要的一个数据结构了,哪儿都能看到它,哪儿都需要用到它。为了对整个分层架构有个大体的了解,我们先来看一个图,这个图在DDK中也可以看到,是某个文件操作的详细过程图,看起来好像和我们的视频采集没多少关系,但实际上大体步骤还是一样的。
1. IO子系统调用IO服务组件,要求打开一个文件。
2. IO manager调用object manager寻找文件,并调用安全组件来查看该调用者是否真的有权限
3. IO manager分别在文件系统和磁盘上寻找相关的文件实体,如果没找到,操作挂起;如果找到了,则继续。
4. IO manager生成一个IRP包,初始化一些数据
5. IO manager找到file system driver,并往它传入先前生成的IRP。IRP中有一个栈,指定了哪一层做哪些操作。刚传入的IRP不一定有一个完整的栈信息,因为驱动程序在处理过程中,可以修改它下面层的驱动对应的IRP栈信息。实际上跨层操作并不好,每一层的栈信息基本都是上一层给指定的,而不是一开始指定。驱动也可以把一个命令分成好几个小命令,分别往底层传。
6. 所有层上的驱动都完成自己应该做的工作
7. 所有层上的驱动分别调用complete operation。complete operation是上一层驱动指定的,表示让下层驱动完成操作后就调用该方法,好让它做一些确定是否成功,回收资源之类的操作。
8. 把IO STATUS拷贝到子系统空间中,好让最顶部的调用者知道调用结果
9. IO manager 清空IRP
10. IO manager把文件句柄返回给调用者
IRP中有一堆的数据结构,比较重要的有如下几个
IRP主要数据项 |
说明 |
IO_STATUS_BLOCK IoStatus |
存放I/O请求的状态 |
PVOID AssociatedIrp.SystemBuffer |
如果设备执行缓冲I/O,则为指向系统空间缓冲区的指针。 否则为NULL |
PMDL MdlAddress |
如果设备执行直接I/O,指向用户空间缓冲区的内存描述表的指针 |
PVOID UserBuffer |
I/O缓冲区的用户空间地址 |
BOOLEAN Cancel |
指示IRP已被取消 |
访问硬件的任何设备必须使用某种机制保证驱动程序的不同部分不同时访问相同的硬件。在一个多处理器系统中,“Write”IRP处理程序可以同时在两个不同的处理器上运行。如果它们两个都试图访问相同的硬件,则会出现不可预料的结果。同样,如果一个“Write”IRP正在试图访问硬件的同时发生了中断,那么,两个动作可能会相互影响。
内核采用两种机制来同步这些冲突操作:
第一种是采用临界段例程,使用这些临界段例程保证代码不会被中断处理程序中断。这些临阶段例程在内部使用了中断自旋锁,所以可以保证多处理器同步。
第二种是使用StartIo例程串行处理IRP,每个设备对象有一内部的IRP队列,驱动程序的派发例程将IRP插入这个队列中。内核I/O管理器从这个队列一个个的取出IRP,并把它们传递到驱动程序的StartIo例程。所以StartIo例程串行的处理IRP,保证不与其它的IRP处理例程冲突。
如果一个IRP已经在一个队列中,此时用户线程突然中止或其调用Win32函数取消了这次I/O,驱动程序必须取消这个IRP。这可以通过给每一个排队的IRP挂接一个取消回调例程来实现。
如果用户态程序关闭了设备的文件句柄,而这个设备有重叠请求在等待,则必须要有“清理”例程。清理例程负责取消与一个文件句柄关联的所有IRP。