副标题[/!--empirenews.page--]
驱动对象
驱动程序:就是一个.sys 模块,
驱动对象:则是.sys 被加载到内核中的实例化出来的对象,用于表示这个驱动模块.
Windows内核使用DRIVER_OBJECT 结构体来描述一个驱动对象.
虽然Windows内核源码使用C语言编写,但也使用了面向对象的思想. 在面向对象思想中,有父类,抽象类,纯虚函数的概念,纯虚函数就是一个类中无须定义的虚函数. 拥有这个函数的类被称为抽象类,继承抽象类的子类必须实现纯虚函数.
在DRIVER_OBJECT 这个结构体中,它相当于一个类. 类中有几个字段是函数指针,这些函数指针能够保存一些系统会调用的特定函数.
驱动对象就类似于一个类对象,这个类对象直接被操作系统内核框架所操作,当一个驱动对象要加载时,系统会调用DriverEntry 函数来构造此对象,函数的第一个参数就是DRIVER_OBJECT* ,结构体指针,这个结构体指针就类似于this 指针(C语言没有this指针,此处只是类比),在这个函数内,需要编写代码来初始化驱动对象的一些必要字段
PDRIVER_INITIALIZE DriverInit - 驱动对象初始化函数指针,此函数指针被I/O管理器初始化,指向DriverEntry 函数
PDRIVER_STARTIO DriverStartIo - 驱动对象用于处理I/O的函数指针,此函数指针可以被设置为NULL
PDRIVER_UNLOAD DriverUnload - 驱动对象的卸载函数指针,当驱动被卸载时会被系统调用,此函数指针必须被赋值,否则当驱动被卸载时,字段为NULL会引发系统蓝屏
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1] - 驱动对象的派遣函数指针数组. 这个数组中,不同的下标保存着不同的函数,下表列出了每个元素的下标(宏)所保存的函数指针,(这个数组内的函数指针都可以设置为NULL):
名称 调用时机 触发调用的API
IRP_MJ_CLEANUP 当本驱动对象的设备对象句柄被关闭(且引用计数为0),此函数会被调用. 但仍有I/O请求未被完成,可以在此函数中清理未完成的I/O请求.
CloseHandle
IRP_MJ_CLOSE 当本驱动对象的设备对象句柄被关闭(且引用计数为0),并且I/O请求已经被完成或已经全部取消,此函数会被调用. 此函数相当于设备对象的析构函数
CloseHandle
IRP_MJ_CREATE 当本驱动对象的设备对象被打开(通过CreateFile /ZwCreateFile ),此函数会被调用. 此函数相当于设备对象的构造函数
CreateFile
IRP_MJ_DEVICE_CONTROL 设备控制,用于读/写设备对象
DeviceIoControl
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区
FlushFileBuffers
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER 电源管理器发出的的请求
IRP_MJ_QUERY_INFORMATION 获取设备对象的长度
GetFileSize
IRP_MJ_READ 读取设备对象的内容
ReadFile
IRP_MJ_SET_INFORMATION 设置设备对象的长度
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE 将数据写到设备对象
WriteFile
这些函数都是可选的,当用户层或内核层通过设备的符号链接操作设备(打开,读,写,关闭)时,这些函数就会被调用.
也就是说,以前所使用的用户层API : CreateFile ,GetFileSize ,ReadFile ,WriteFile ,CloseHandle 这些API操作的是一个设备对象,在内核中,文件属于一个设备对象.
同一个函数,能够操作不同的对象,这就是内核中通过C语言实现的多态了.
设备对象
设备对象一般是由驱动对象出来的(非即插即用驱动) . 驱动对象能够保存各种派遣函数,但是,这些派遣函数的一般是由I/O管理器所调用. 在调用派遣函数时,I/O 管理器会将附加的信息打包到一个结构体,并传递给派遣函数. 一般这个结构体被称为IRP 结构. 而只有设备对象才能接收到I/O 管理器的I/O 请求(什么是I/O请求? 比如,当一个文件的设备对象被打开(CreateFile )之后,对此文件的设备对象的读(ReadFile )和写(WriteFile )就是I/O 请求) .
但是设备对象是不能独立存在的,设备对象能够接收I/O 请求,但是却没有处理I/O 请求的派遣函数,处理I/O 请求的派遣函数保存在驱动对象中.
因此,在一个驱动项目中,要有两种对象存在:
驱动对象,能够保存处理I/O 请求的派遣函数.
设备对象,能够接收到I/O 请求.
而且,驱动对象无法被用户层代码所访问到,但是设备对象可以.
设备对象虽然在内核层,但是创建了DOS 下的符号链接之后,在用户层中就可以通过CreateFile 来打开设备对象. 并能够通过ReadFile ,DeviceIOControl ,GetFileSize 等函数来间接地调用保存在驱动对象中的派遣函数. 它们的关系如下图所属:

设备对象的创建和销毁
IoCreateDevice - 创建设备对象
IoDeleteDevice - 销毁设备对象
符号链接
符号链接就是一个名字,DosDevicesD: 这是一个盘符,但其实也可以视为一个符号链接名.它的作用是能够让用处层的API发出IO请求,并能够在发出IO请求时指定一个设备来处理此IO请求:
当用户层的应用程序发出一个IO请求时,对象管理器通过此符号链接名称来找到对应的设备,对象管理器能够解析符号链接的名称,以确定IO请求的目的地.
符号链接是给设备对象使用的,设备对象默认没有符号链接,没有符号链接的设备对象无法被用户层的代码所使用. 为设备对象创建符号链接之后才可以.
在内核中,符号链接的种类有两种:
|