派遣函数

驱动程序的主要功能是用来处理IO请求,而大部分的IO请求是在派遣函数中完成的,用户模式下所有的IO请求都会被IO管理器封装为一个IRP结构,类似于Windows窗口程序中的消息,不同的IRP被发送到不同的派遣函数中处理

IRP与派遣函数

IRP

IRP(I/O Request Package)输入输出请求包,IRP的两个最基本的结构是MajorFunction和MinorFunction,分别记录IRP的主要类型和子类型,它们是一组函数指针数组,不同的项纪录的是处理当前请求的回调函数,可以在这些派遣函数中继续通过MinorFunction来判断

每个驱动都有一个唯一的DRIVER_OBJECT结构,这个结构中有一个MajorFunction数组,通过这个数组可以将IRP与处理它的派遣函数关联起来,当应用层有一个针对于某个设备对象的I/O请求时,会根据这个设备对象所在驱动找到对应的MajorFunction结构,再根据请求类型来找到它对应的处理函数。

IRP类型

与应用层中有不同的消息类型,系统会根据消息类型调用具体消息处理函数类似,IRP也有不同的类型,在应用层调用不同的函数时会产生不同的IRP类型,例如调用应用层函数CreateFile或者内核函数ZwCreateFile会产生IRP_MJ_CREATE类型的IRP。下面是不同操作所对应产生的IRP请求列表

IRP类型

来源

IRP_MJ_CREATE

创建设备,CreateFile会产生此IRP

IRP_MJ_CLOSE

关闭设备,CloseHandle会产生此IRP

IRP_MJ_CLEANUP

清除工作,CloseHandle会产生此IRP

IRP_MJ_DEVICE_CONTROL

DeviceIoControl函数会产生此IRP

IRP_MJ_PNP

即插即用消息,NT驱动不支持此中IRP,只有WDM驱动才支持此中驱动

IRP_MJ_POWER

在操作系统处理电源消息时会产生此IRP

IRP_MJ_QUERY_INFORMATION

获取文件长度,GetFileSize会产生此IRP

IRP_MJ_READ

读取设备内容,ReadFile会产生此IRP

IRP_MJ_SET_INFORMATION

设置文件长度,SetFileSize

IRP_MJ_SHUTDOWN

关闭系统前会产生此IRP

IRP_MJ_SYSTEM_CONTROL

系统内部产生控制信息,蕾西与调用DeviceIoControl函数

IRP_MJ_WRITE

对设备进行WriteFile时会产生此IRP

对派遣函数的简单处理

大部分的I/O请求都来自于应用层调用相应的API对设备进行I/O操作类似于CreateFile、ReadFile等函数产生,最简单的做法是将IRP设置为成功,然后结束IRP请求,并让派遣函数返回成功,结束这个IRP调用函数IoCompleteRequest。

VOID 
  IoCompleteRequest(
    IN PIRP  Irp, //代表要结束的IRP
    IN CCHAR  PriorityBoost//代笔线程恢复时的优先级别
    );

其实当应用层调用相关函数进行I/O操作时,会陷入睡眠或者阻塞状态,等待派遣函数成功返回,当派遣函数返回时会唤醒之前的等待线程,而第二个参数就是制定这个被唤醒的线程以何种优先级别运行。一般设置为IO_NO_INCREMENT表示不增加优先级,对于键盘,或者鼠标一类的需要更快的相应,这个时候可以设置为IO_MOUSE_INCREMENT 或者IO_KEYBOARD_INCREMENT下面是完成优先级的一个表

在应用层打开设备

在应用层一般通过设备名称打开驱动中的设备对象,设备名称一般只能在内核层使用,应用层能看到的是设备的符号链接名,符号链接名一般以”\??\”开头,在应用层的写法有些不同,应用层设备的符号链接名称以“\.\开头”,因此在内核层符号链接为:”\??\HelloDevice”到了应用层则应该写为”\.\HelloDevice”。

设备栈

驱动对象会创建多个设备对象,并将这些设备对象叠成一个垂直的结构,这种垂直结构被称作设备栈,IRP请求首先被发往设备栈上的顶层设备上,如果这个设备不处理可以将它下发到下层的设备对象中,直到某个设备结束这个IRP请求。为了记录IRP在每层设备中做的操作,IRP中有一个IO_STACK_LOCATION数组,这个数组对应于设备栈中各个设备对IRP所做的操作。在本层的设备中可以使用函数IoGetCurrentIrpStackLocation得到本层设备对应的IO_STACK_LOCATION结构,下面是它对应的结构图

缓冲区方式读写操作

在调用IoCreateDeivce函数完成设备对象的创建之后,需要设置该设备对象的缓冲区读写方式,这个值是由DEVICE_OBJECT中的Flag来设置,主要有三种DO_DIRECT_IO、DO_BUFFERED_IO 、0。 应用层在对设备进行读写操作时,会提供一个缓冲区用于保存需要传入到设备对象或者保存由设备对象传入的数据,Flag值规定就规定了设备对象是如何使用这个缓冲区的。 1. DO_DIRECT_IO:内核直接通过地址映射的方式将那块缓冲区映射为内核地址,然后在驱动中使用。当使用这种方式时内核可以在IO_STACK_LOCATION结构中的MdlAddress拿到这块内存,通过函数MmGetSystemAddressFromMdlSafe传入MdlAddress值可以得到应用层传下来的缓冲区地址 2. DO_BUFFERED_IO:内核会在内核的地址空间空另外开辟一段内存,将缓冲区的数据简单拷贝到这个新开辟的空间中。通过这种方式的读写可以在IRP结构的AssociatedIrp.SystemBuffer中获取。 3. 0:内核直接使用应用层的地址,对那块内存进行操作,这种方式是十分危险的,如果进行线程切换,这个地址就不一定指向之前的内存,这样就可能造成系统崩溃蓝屏。这种方式可以通过IRP中的UserBuffer拿到缓冲区地址 另外缓冲区的长度可以通过IO_STACK_LOCATION中的Parameters.Read.Length和Parameters.WriteLength分别获取读写缓冲区的长度

IO设备控制操作

DeviceIoControl与驱动设备交互

BOOL DeviceIoControl(
  HANDLE hDevice, //驱动对象句柄
  DWORD dwIoControlCode, //控制码
  LPVOID lpInBuffer, //传入到驱动中的数据缓冲
  DWORD nInBufferSize, //缓冲大小
  LPVOID lpOutBuffer, //驱动传出数据的缓冲
  DWORD nOutBufferSize, //输出数据缓冲区的大小
  LPDWORD lpBytesReturned, //实际返回数据的大小
  LPOVERLAPPED lpOverlapped//异步函数
);

这是一个应用层的API函数,用于向驱动发送控制码,在驱动中,根据控制吗的不同而采用不同的处理方式进行处理,应用层可以通过后面几个参数实现与驱动的数据共享。 控制码采用宏CTL_CODE来定义

#define CTL_CODE(DeviceType, Function, Method, Access)

这个宏有四个参数,第一个是设备对象的类型,就是在调用IoCreateDevice创建设备对象时传入的DeviceType的值,第二个参数是设备对象的控制码,为了与系统定义的区分开,一般用户自定义的取值在0x800之上。第三个参数是操作模式,主要有这样几个值:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT、METHOD_NEITHER,这些值主要针对的是设备对象的三种缓冲区的读写方式。第四个参数是访问权限,一般给FILL_ANY_ACCESS; 这个函数在使用的时候需要注意下面几点: 1. 这个函数是在应用层调用,所以必须在调用这个函数前使用CreateFile打开这个设备对象。在调用CreateFile时会向I/O管理器发送一个Create请求,这个请求被I/O管理器包装成IRP,这个IRP的类型为IRP_MJ_CREATE,I/O管理器需要根据驱动的返回值来判断怎么处理这个请求,只有当驱动向I/O管理器返回一个成功的时候才会为其分配句柄,所以驱动中需要自己实现Create的分发派遣函数。 2. 驱动中需要自定义一个分发函数用于处理这个IOControl发下来的信息,函数中可以从IO_STACK_LOCATION结构中的Parameters.DeviceIoControl.IoControlCode获得用户层传下来的控制码 3. 默认情况下我们会在结束IOControl这个IRP的时候会给定一个返回长度为0,这个时候I/O管理器会将这个值回填到DeviceIoControl函数中的倒数第二个参数中,因此DeviceIoControl的这个参数不能为NULL,否则会造成程序崩溃

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Danny的专栏

机房收费系统(VB.NET)——存储过程实战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

18850
来自专栏IT 指南者专栏

MyBatis 框架之基础初识

? 1、什么是 MyBatis MyBatis 本是 apache 的一个开源项目 iBatis,后改名为 MyBatis,它 是一个优秀的持久层框架,对 ...

29570
来自专栏安恒网络空间安全讲武堂

从零基础到解题之 Python is the best language

-目录- 前言 环境搭建 源码结构 题目分析 Python is the best language1 Python is the best langua...

30540
来自专栏逆向技术

x64内核HOOK技术之拦截进程.拦截线程.拦截模块

            x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么...

52370
来自专栏Star先生的专栏

从源码中分析 Hadoop 的 RPC 机制

RPC是Remote Procedure Call(远程过程调用)的简称,这一机制都要面对两个问题:对象调用方式余与序列/反序列化机制。本文给大家介绍从源码中分...

77500
来自专栏程序员的SOD蜜

PDF.NET(PWMIS数据开发框架)之SQL-MAP目标和规范

SQL-MAP的目标: 集中管理SQL语句,所有SQL语句放在专门的配置文件中进行管理; 通过替换SQL配置文件,达到平滑切换数据库到另外一个数据库,比如从O...

27780
来自专栏Java3y

纳税服务系统四(角色模块)【角色与权限、角色与用户】

需求分析 我们直接来看看原型图,看看需求是怎么样的: ? 这里写图片描述 ? 这里写图片描述 我们看到上图,就会发现角色模块主要还是CRUD,唯一不同的就是它不...

75980
来自专栏ml

------------数据库的加锁操作(上)

       从事一个项目,需要考虑数据的安全性,之前对于数据库这部分的数据操作学习的比较零散,由于手头的项目,于是系统的 学习了下数据库操作加锁的知识: --...

564100
来自专栏Golang语言社区

GO语言常用的文件读取方式

本文实例讲述了GO语言常用的文件读取方式。分享给大家供大家参考。具体分析如下: Golang 的文件读取方法很多,刚上手时不知道怎么选择,所以贴在此处便后速查。...

41970
来自专栏分布式系统进阶

Influxdb 数据写入流程

因此对写入请求的处理就在函数 func (h *Handler) serveWrite(w http.ResponseWriter, r *http.Reque...

21330

扫码关注云+社区

领取腾讯云代金券