news 2026/3/14 8:09:07

ring0与ring3通信

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ring0与ring3通信

前置知识

IRP是什么

IRP的处理机制类似于windows窗口程序中的“消息处理”机制,驱动程序接收到不同类型的IRP后,会进入不同的派遣函数,在派遣函数中IRP得到处理。

派遣函数是什么

用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IRP的数据结构,不同的IRP数据会被“派遣”道不同的派遣函数中,这也是派遣函数名字的由来。

驱动程序并不是“被直接调用”的,而是由操作系统根据 IRP 的类型进行派遣。

每一种 IRP 都有一个“主功能码(Major Function)”,例如:

  • IRP_MJ_CREATE:打开设备(CreateFile)
  • IRP_MJ_CLOSE:关闭设备(CloseHandle)
  • IRP_MJ_DEVICE_CONTROL:设备控制(DeviceIoControl)

驱动通过注册 派遣函数,告诉系统:

“当收到某种类型的 IRP 时,请调用我这个函数来处理。”

用户模式发起操作(如 DeviceIoControl) 操作系统将其转换为 IRP 根据 IRP 类型,进入驱动中对应的派遣函数 驱动在派遣函数中完成处理并返回结果

什么是设备对象(DEVICE_OBJECT)?

设备对象是驱动在内核中创建的、用于接收 I/O 请求的内核对象。

在 Windows 中,驱动程序本身并不能直接接收 IRP,
真正接收 IRP 的,是驱动创建的 设备对象(DEVICE_OBJECT)。

在代码中,对应的就是:

IoCreateDevice(...)

创建成功后,系统会为该驱动生成一个设备对象,
之后所有发往该设备的 IRP,都会被派遣到这个设备对象所属的驱动中。

对于我们这种 0 环 / 3 环通信驱动来说:

这个设备并不对应真实硬件, 而是一个“纯软件设备”, 用来承载用户态与驱动之间的通信。

设备名(\Device\xxx)是做什么的?

设备对象在内核中必须有一个唯一的名字,
这个名字存在于 内核对象命名空间 中,通常以 \Device\ 开头,例如:

\\Device\\MyDevice

这个名字的特点是:

  • 只在内核态可见

  • Ring3 程序不能直接使用这个名字打开设备

  • 它的主要作用是:在内核中唯一标识一个设备对象

为什么还需要符号链接?

既然 Ring3 不能直接访问 \Device\MyDevice,
那用户程序如何找到这个设备呢?

答案是:通过符号链接(Symbolic Link)。

驱动可以创建一个符号链接,例如:

\\??\\MyDevice->\\Device\\\MyDevice

代码

ring3

#include<stdio.h>#include<windows.h>#include<winioctl.h>#defineOPER1CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineOPER2CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)/***************************************************************************///打开驱动服务句柄//打开三环链接名:\\\\.\\Device/***************************************************************************/intmain(void){DWORD dwInBuffer=0x123456789;TCHAR szOutBuffer[10]={0};DWORD dw;//1. 通过符号链接,打开设备HANDLE g_hDevice=CreateFileW(L"\\\\.\\MyDevice",// 设备名,例如 "\\\\.\\MyDevice"GENERIC_READ|GENERIC_WRITE,// 读写权限0,// 共享模式:0 表示不共享0,// 安全属性:NULL = 默认OPEN_EXISTING,// 只能打开已存在的设备FILE_ATTRIBUTE_NORMAL,// 普通文件属性(对设备来说一般无所谓)0// 模板句柄:不用,传0);//2. 测试通信DeviceIoControl(g_hDevice,//设备句柄OPER2,//操作码&dwInBuffer,//输入缓冲区地址sizeof(dwInBuffer),//输入缓冲区长度szOutBuffer,//输出缓冲区地址sizeof(szOutBuffer),//输出缓冲区长度&dw,//返回长度NULL);//指向OVERLAPPED 此处为NULLprintf("%x",*(int*)szOutBuffer);//3. 关闭设备CloseHandle(g_hDevice);system("pause");return0;}

ring0

#include<ntifs.h>#defineOPER1CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineOPER2CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)#defineDEVICE_NAMEL"\\Device\\MyDevice"// Ring3用CreateFile打开设备时,用"\\\\.\\MyDevice"#defineSYMBOLICLINK_NAMEL"\\??\\MyDevice"// IRP_MJ_CREATE处理函数//参数1:一个驱动可以有多个 DEVICE_OBJECT,所以同一个分发函数可以用 pDevObj 区分“这次是哪个设备收到的 IRP”。//参数2:这次 I/O 请求对应的 IRP 结构体,里面记录了://1.这是一个 Create 请求(MajorFunction = IRP_MJ_CREATE)//2.打开方式、共享模式等参数//3.一个 IO_STATUS_BLOCK:pIrp->IoStatusNTSTATUSIrpCreateProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){DbgPrint("(mydriver)DispatchCreate ... \n");//每个 IRP 里都有一个 IO_STATUS_BLOCK IoStatus//IoStatus.Status:本次 I/O 请求的 NTSTATUS 返回码//IoStatus.Information:与这次请求相关的“额外信息”(比如读/写了多少字节)//你必须显式把 IoStatus.Status 设置成成功或失败,//否则就是“垃圾值”,I/O 管理器按照那个垃圾值判断,于是 Ring3 的 CreateFile 返回失败。pIrp->IoStatus.Status=STATUS_SUCCESS;//这个字段的含义取决于 是什么 IRP://对 IRP_MJ_READ:常常表示“实际读取了多少字节”//对 IRP_MJ_WRITE:常常表示“实际写入了多少字节”//对 IRP_MJ_DEVICE_CONTROL:表示“返回给用户缓冲区的数据长度”//对 IRP_MJ_CREATE / IRP_MJ_CLOSE:一般没有额外数据需要告诉 Ring3,所以设为 0pIrp->IoStatus.Information=0;//告诉 I/O 管理器:“这个 IRP 我处理完了,可以结束这次 I / O 请求了。”//如果不调用IoCompleteRequest,Ring3 那边的 CreateFile 会一直等(同步调用),永远等不到结果,表现就是程序卡死/挂住。IoCompleteRequest(pIrp,IO_NO_INCREMENT);//return STATUS_SUCCESS; 和 IoStatus.Status 的关系//这里返回值也是 NTSTATUS,I / O 管理器确实也会用这个值,但真正传回 Ring3 的是 pIrp->IoStatus.Status。returnSTATUS_SUCCESS;}// IRP_MJ_CLOSE处理函数NTSTATUSIrpCloseProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){DbgPrint("(mydriver)DispatchClose ... \n");pIrp->IoStatus.Status=STATUS_SUCCESS;pIrp->IoStatus.Information=0;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnSTATUS_SUCCESS;}// IRP_MJ_DEVICE_CONTROL处理函数 用来处理与Ring3交互NTSTATUSIrpDeviceControlProc(PDEVICE_OBJECT pDevObj,PIRP pIrp){NTSTATUS status=STATUS_INVALID_DEVICE_REQUEST;ULONG uRead;//当设置交互模式为pDeviceObj->Flags |= DO_BUFFERED_IO;那 I/O 管理器会://1.在内核态分配一块缓冲区:SystemBuffer//2.把 Ring3 的输入缓冲区(InBuff)拷贝过来//3.把 SystemBuffer 指针放到 pIrp->AssociatedIrp.SystemBuffer//4.完成 IRP 时,再把 SystemBuffer 前 IoStatus.Information 个字节拷回 Ring3 的输出缓冲区(OutBuff)//设置临时变量的值PIO_STACK_LOCATION pIrpStack=IoGetCurrentIrpStackLocation(pIrp);// 获取控制码ULONG uIoControlCode=pIrpStack->Parameters.DeviceIoControl.IoControlCode;// 获取缓冲区地址(输入和输出的缓冲区都是一个)PVOID pIoBuffer=pIrp->AssociatedIrp.SystemBuffer;// Ring3 发送数据的长度:用户写进来的数据长度(你最多只能读这么多字节)。ULONG uInLength=pIrpStack->Parameters.DeviceIoControl.InputBufferLength;// Ring0 发送数据的长度:用户预留的输出缓冲区最大长度(你最多填这么多字节)。ULONG uOutLength=pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;switch(uIoControlCode){caseOPER1:{DbgPrint("(mydriver)IrpDeviceControlProc -> OPER1 ... \n");pIrp->IoStatus.Information=0;status=STATUS_SUCCESS;break;}caseOPER2:{DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 接收字节数:%d \n",uInLength);DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 输出字节数:%d \n",uOutLength);// Read From Buffermemcpy(&uRead,pIoBuffer,4);DbgPrint("(mydriver)IrpDeviceControlProc -> OPER2 ... %x \n",uRead);// Write To BufferuRead++;memcpy(pIoBuffer,&uRead,4);// Set StatuspIrp->IoStatus.Information=4;status=STATUS_SUCCESS;break;}}// 设置返回状态pIrp->IoStatus.Status=status;IoCompleteRequest(pIrp,IO_NO_INCREMENT);returnstatus;}VOIDDriverUnload(PDRIVER_OBJECT pDriver){UNICODE_STRING SymbolicLinkName={0};DbgPrint("(mydriver)驱动程序停止运行了 . \r\n");// 删除符号链接 删除设备RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);IoDeleteSymbolicLink(&SymbolicLinkName);IoDeleteDevice(pDriver->DeviceObject);}NTSTATUSDriverEntry(PDRIVER_OBJECT pDriver,PUNICODE_STRING pReg){DbgPrint("(mydriver)驱动程序开始运行了 . \r\n");//初始化ring0设备名称UNICODE_STRING Devicename;RtlInitUnicodeString(&Devicename,DEVICE_NAME);// 创建设备PDEVICE_OBJECT pDeviceObj=NULL;NTSTATUS status=IoCreateDevice(pDriver,//驱动对象0,//设备扩展大小:为每个设备对象 额外分配的扩展空间大小&Devicename,//设备名称FILE_DEVICE_UNKNOWN,//设备类型FILE_DEVICE_SECURE_OPEN,//设备特性FALSE,//是否“独占设备”:TRUE:同一时间只能有 一个 句柄打开这个设备。第二个进程再 CreateFile 就会失败。//FALSE:多个进程 / 线程可以同时打开。&pDeviceObj);//输出:系统创建好的 PDEVICE_OBJECT 指针,通过它你以后能访问到设备扩展、标志位等等。if(status!=STATUS_SUCCESS){DbgPrint("(mydriver)创建设备失败! \r\n");returnstatus;}//设置交互数据的方式pDeviceObj->Flags|=DO_BUFFERED_IO;// 创建符号链接名称(给ring3用)UNICODE_STRING SymbolicLinkName;RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);// 创建符号链接status=IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);if(status!=STATUS_SUCCESS){DbgPrint("(mydriver)创建符号链接失败! \r\n");IoDeleteDevice(pDeviceObj);returnstatus;}// 设置分发函数和卸载函数pDriver->MajorFunction[IRP_MJ_CREATE]=IrpCreateProc;pDriver->MajorFunction[IRP_MJ_CLOSE]=IrpCloseProc;pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL]=IrpDeviceControlProc;pDriver->DriverUnload=DriverUnload;pDriver->DriverUnload=DriverUnload;return0;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 0:49:09

突破语音合成边界:微软VibeVoice-1.5B技术深度剖析与实践指南

突破语音合成边界&#xff1a;微软VibeVoice-1.5B技术深度剖析与实践指南 【免费下载链接】VibeVoice-1.5B 项目地址: https://ai.gitcode.com/hf_mirrors/microsoft/VibeVoice-1.5B 在语音合成技术领域&#xff0c;传统文本转语音&#xff08;TTS&#xff09;系统长期…

作者头像 李华
网站建设 2026/3/13 1:10:34

10、MySQL、邮件服务与企业应用实践

MySQL、邮件服务与企业应用实践 数据库选择依据 在设计不同类型的应用时,数据库的选择至关重要。对于人力资源应用,由于数据具有关系性,如员工的姓名、社保号码、工资等相关信息,选择关系型数据库是合适的。而对于多媒体应用,像照片、视频和艺术作品等,对象数据库更为流…

作者头像 李华
网站建设 2026/3/14 7:09:00

21、BIND与DHCP在DNS中的应用详解

BIND与DHCP在DNS中的应用详解 1. 反向查找区域文件 在示例反向查找区域文件中,存在一个针对网络 10.1.1.0/24 的反向区域。 $ORIGIN 指令是可选的,但它能让区域文件更易读,该指令主要用于补全未完全限定的资源记录(RRs)。例如,当使用IP地址 10.1.1.[1,2,6] 时, …

作者头像 李华
网站建设 2026/3/13 0:48:02

27、Linux 系统故障排查与性能优化指南

Linux 系统故障排查与性能优化指南 1. 进程排查工具——ps 命令 在排查进程相关问题时, ps 命令非常实用。以下是几个常见的 ps 命令选项及示例: - 查看进程运行时间 :可以帮助解决内存耗尽问题。例如,查看 init 和 rsyslog 进程的运行时间: $ ps -eo pid,c…

作者头像 李华
网站建设 2026/3/13 0:29:06

mysql的列为什么要设置not null default ‘‘?

1.如果不设置&#xff0c;那么会出现空字符串和null一起存在的现象 2.如果这个字段是索引&#xff0c;那么会为空字符串和null都存储在二级索引中 3.存储占用更多的二级索引空间&#xff0c;还需要考虑null值查询的特殊处理 4.没有空字符串等值查询效率高&#xff0c;如果设置n…

作者头像 李华
网站建设 2026/3/12 23:12:53

41、树莓派硬件接口与软件应用全解析

树莓派硬件接口与软件应用全解析 1. Gertboard编程 Gertboard编程与底层Arduino代码颇为相似,都是直接对内存映射的输入输出(IO)进行操作,这就要求具备汇编程序员的精细技能。以下是一段示例代码: PWMCLK_DIV = 0x5A000000 | (32<<12); PWMCLK_CNTL = 0x5A0000…

作者头像 李华