Ring3/Ring0的四种通信方式

21.1.5  DeviceIoControl函数与IoControlCode

打开驱动设备后,Ring3还要和驱动进行通讯或调用驱动的派遣例程,这需要用到一个非常重要的函数:DeviceIoControl。

 
 
  1. BOOL DeviceIoControl(  
  2.   HANDLE hDevice,           //设备句柄  
  3.   DWORD dwIoControlCode,            //Io控制号  
  4.   LPVOID lpInBuffer,            //输入缓冲区指针  
  5.   DWORD nInBufferSize,          //输入缓冲区字节数  
  6.   LPVOID lpOutBuffer,           //输出缓冲区指针  
  7.   DWORD nOutBufferSize,         //输出缓冲区字节数  
  8.   LPDWORD lpBytesReturned,      //返回输出字节数  
  9.   LPOVERLAPPED lpOverlapped     //异步调用时指向的OVERLAPPED指针  
  10. );  

该函数共有8个参数,hDevice是要通信的设备句柄;dwIoControlCode是Io控制号;lpInBuffer是输入缓冲区指针;nInBufferSize是输入缓冲区字节数;lpOutBuffer是输出缓冲区指针;nOutBufferSize是输出缓冲区字节数;lpBytesReturned是返回输出字节数;lpOverlapped是异步调用时指向的OVERLAPPED指针。

其中的第二个参数IoControlCode尤为重要,由宏CTL_CODE构造而成:

 
 
  1. #define CTL_CODE( DeviceType, Function, Method, Access ) ( \  
  2. ((DeviceType) << 16) | ((Access) << 14) |
    ((Function) 
    << 2) | (Method)  )  

IoControlCode由四部分组成:DeviceType、Access、Function、Method,如图21.1.11所示。

 
图21.1.11  IoControlCode的四个组成部分

DeviceType表示设备类型;

Access表示对设备的访问权限;

Function表示设备IoControl的功能号,0~0x7ff为微软保留,0x800~0xfff由程序员自己定义;

Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:

 
 
  1. #define METHOD_BUFFERED                0  
  2. #define METHOD_IN_DIRECT               1  
  3. #define METHOD_OUT_DIRECT              2  
  4. #define METHOD_NEITHER                  3  

最值得关注的也就是Method,如果使用了METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。

如果使用了METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。

但是如果使用了METHOD_NEITHER方式,虽然通信的效率提高了,但是不够安全。驱动的派遣函数中可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。

21.1.5节中提到了Ring3/Ring0通信的四种内存访问方式分别为:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT和METHOD_NEITHER。

METHOD_BUFFERED可称为"缓冲方式",是指Ring3指定的输入、输出缓冲区的内存读和写都是经过系统的"缓冲",具体过程如图21.1.12所示。

这种方式下,首先系统会将Ring3下指定的输入缓冲区(UserInputBuffer)数据,按指定的输入长度(InputBufferLen)复制到Ring0中事先分配好的缓冲内存(SystemBuffer,通过pIrp->AssociatedIrp.SystemBuffer得到)中。驱动程序就可以将SystemBuffer视为输入数据进行读取,当然也可以将SystemBuffer视为输出数据的缓冲区,也就是说SystemBuffer既可以读也可以写。驱动程序处理完后,系统会按照pIrp->IoStatus->Information指定的字节数,将SystemBuffer上的数据复制到Ring3指定的输出缓冲区(UserOutputBuffer)中。可见这个过程是比较安全的,避免了驱动程序在内核态直接操作用户态内存地址的问题,这种方式是推荐使用的方式。

 
(点击查看大图)图21.1.12  METHOD_BUFFERED方式的内存访问

METHOD_NEITHER可称为"其他方式",这种方式与METHOD_BUFFERED方式正好相反。METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲,而METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,如图21.1.13所示。

 
图21.1.13  METHOD_NEITHER方式的内存访问

驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。

由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。

这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。

 
图21.1.14  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于STM32的TCP通信中使用ringbuffer的示例程序: ```c #define RINGBUF_SIZE 512 // 定义ringbuffer的大小 typedef struct ringbuf { uint8_t buffer[RINGBUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; } ringbuf_t; // 初始化ringbuffer void ringbuf_init(ringbuf_t *ringbuf) { ringbuf->head = 0; ringbuf->tail = 0; } // 判断ringbuffer是否为空 bool ringbuf_is_empty(ringbuf_t *ringbuf) { return (ringbuf->head == ringbuf->tail); } // 判断ringbuffer是否已满 bool ringbuf_is_full(ringbuf_t *ringbuf) { return (((ringbuf->tail + 1) % RINGBUF_SIZE) == ringbuf->head); } // 向ringbuffer中写入数据 void ringbuf_put(ringbuf_t *ringbuf, uint8_t data) { // 如果ringbuffer已满,则丢弃最旧的数据 if (ringbuf_is_full(ringbuf)) { ringbuf->head = (ringbuf->head + 1) % RINGBUF_SIZE; } ringbuf->buffer[ringbuf->tail] = data; ringbuf->tail = (ringbuf->tail + 1) % RINGBUF_SIZE; } // 从ringbuffer中读取数据 uint8_t ringbuf_get(ringbuf_t *ringbuf) { uint8_t data = 0; // 如果ringbuffer为空,则返回0 if (!ringbuf_is_empty(ringbuf)) { data = ringbuf->buffer[ringbuf->head]; ringbuf->head = (ringbuf->head + 1) % RINGBUF_SIZE; } return data; } ``` 使用ringbuffer时,可以先初始化一个ringbuf_t类型的结构体,然后使用ringbuf_put()函数向ringbuffer中写入数据,使用ringbuf_get()函数从ringbuffer中读取数据。在使用ringbuffer时,需要注意保证写入和读取的顺序和速度匹配,避免数据丢失或混乱。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值