设备对象 驱动对象
0x01 驱动对象:
一个驱动对象代表了一个驱动程序。或者说一个内核模块。下面有一些域用省略号代替。
这里可以看到三个重要的成员:设备对象,快速IO分发函数,以及普通分发函数
typedef struct _DRIVER_OBJECT {
// 结构的类型和大小。
CSHORT Type;
CSHORT Size;
// 设备对象,这里实际上是一个设备对象的链表的开始。因为 DeviceObject
// 中有相关链表信息。读下一小节“设备对象”会得到更多的信息。
PDEVICE_OBJECT DeviceObject;
……
// 驱动的名字
UNICODE_STRING DriverName;
……
// 快速 IO分发函数
PFAST_IO_DISPATCH FastIoDispatch;
……
// 驱动的卸载函数
PDRIVER_UNLOAD DriverUnload;
// 普通分发函数
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
0x02、设备对象:
设备对象是内核中的重要对象。而在内核的世界里,大部分“消息”以“请求” (IRP)(I/O request package)的方式传递。而设备对象(DEVICE_OBJECT)是唯一可以接受请求的的实体。任何一个“请求”(IRP)都是发送给某个设备对象的。
设备对象的结构是 DEVICE_OBJECT,常常被简称为 DO。一个 DO 可能代表许多东西。浅显的例子是:一个DO可以代表一个实际的硬盘。这很明显:硬盘可以被读,或者
被写。所以这个 DO 将接受读和写两种请求(实际还有更多) 。但是一个 DO 也可能代表一个和硬件毫无关系的东西。比如说内核中可能有一个设备,实现类似“管道”的功能。一个进程打开这个设备对象进行读,另一个进程打开这个设备进行写,就把数据从一个进程传递到了另一个进程。为了接受来自用户进程的请求,我们不得不生成了一个 DO。这个 DO和实际硬件没什么关系。 因为我们总是在内核程序中生成一个 DO,而一个内核程序是用一个驱动对象表示的。所以一个设备对象总是属于一个驱动对象。
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
// 和驱动对象一样
CSHORT Type;
USHORT Size;
// 引用计数
ULONG ReferenceCount;
// 这个设备所属的驱动对象
struct _DRIVER_OBJECT *DriverObject;
// 下一个设备对象。在一个驱动对象中有n 个设备,这些设备用这个指针连接
// 起来作为一个单向的链表。
struct _DEVICE_OBJECT *NextDevice;
ULONG Flags; //设置不同的Flags会导致以不同的方式操作设备。三种读写方式的Flags分别对应为DO_BUFFERED_ID,DO_DIRECT_IO和0
// 设备类型
DEVICE_TYPE DeviceType;
// IRP栈大小
HAR StackSize;
……
}DEVICE_OBJECT;
驱动对象生成多个设备对象。而 Windows在向设备对象发送请求。但是这些请求如何处理呢?实际上,这些请求是被驱动对象的分发函数所捕获的。当 Windows内核向一个设备发送一个请求时,驱动对象的分发函数中的某一个会被调用。分发函数原型如下:
// 一个典型的分发函数,第一个参数 device 是请求的目标 device,第二个参数 irp 是
// 请求的指针。
NTSTATUS MyDispatch(PDEVICE_OBJECT deivce, PIRP irp);
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IRP(I/O request package)是操作系统内核的一个数据结构。应用程序与驱动程序进行通信需要通过IRP包。当上层应用程序需要与驱动通信的时候,通过调用一定的API函数(系统根据IO请求产生相应的IRP),IO管理器针对不同的API产生不同的IRP,根据IRP的MajiorFunction成员,IRP被传递到驱动内部不同的分发函数进行处理。对于不会处理的IRP包,需要提供一个默认的分发函数(派遣函数(Dispatch Function))来处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //建立一个字符串数组与IRP类型对应起来 static char * irpname[] = { "IRP_MJ_CREATE" , "IRP_MJ_CREATE_NAMED_PIPE" , "IRP_MJ_CLOSE" , "IRP_MJ_READ" , "IRP_MJ_WRITE" , "IRP_MJ_QUERY_INFORMATION" , "IRP_MJ_SET_INFORMATION" , "IRP_MJ_QUERY_EA" , "IRP_MJ_SET_EA" , "IRP_MJ_FLUSH_BUFFERS" , "IRP_MJ_QUERY_VOLUME_INFORMATION" , "IRP_MJ_SET_VOLUME_INFORMATION" , "IRP_MJ_DIRECTORY_CONTROL" , "IRP_MJ_FILE_SYSTEM_CONTROL" , "IRP_MJ_DEVICE_CONTROL" , "IRP_MJ_INTERNAL_DEVICE_CONTROL" , "IRP_MJ_SHUTDOWN" , "IRP_MJ_LOCK_CONTROL" , "IRP_MJ_CLEANUP" , "IRP_MJ_CREATE_MAILSLOT" , "IRP_MJ_QUERY_SECURITY" , "IRP_MJ_SET_SECURITY" , "IRP_MJ_POWER" , "IRP_MJ_SYSTEM_CONTROL" , "IRP_MJ_DEVICE_CHANGE" , "IRP_MJ_QUERY_QUOTA" , "IRP_MJ_SET_QUOTA" , "IRP_MJ_PNP" , }; |
0x03 I/O 堆栈(IO_STACK_LOCATION)
一个重要的数据结构---IO_STACK_LOCATION,即I/O 堆栈,这个数据结构和IRP紧密相连。
驱动对象会创建一个个的设备对象,并将这些设备对象“叠”成一个垂直结构,这种垂直结构很像栈,因此称“设备栈”。
IRP会被操作系统发送到设备栈的顶层,如果项层的设备对象的派遣函数结束了IRP的请求,则这次I/O请求结束;如果没有将IRP的请求结束,那么OS将IRP转发到设备栈的下一个设备处理。
因此一个IRP可能会被转发多次,为了记录IRP在每层设备中做的操作,IRP会有一个IO_STACK_LOCATION数组。数组的元素应该大于IRP穿越过的设备数。
每个IO_STACK_LOCATION元素记录着对应设备中做的操作。对于本层设备对应的IO_STACK_LOCATION ,可以通过IoGetCurrentStackLocation函数得到,如:
IO_STACK_LOCATION 结构中会记录IRP的类型,即IO_STACK_LOCATION 中的MajorFunction子域。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗