Windows 驱动跑在核心态 (Kernel mode) ,驱动的调用者跑在用户态。如何使用户态进程与核心态驱动共享内存呢 ?

我们知道 32 Windows 中,默认状态下虚拟空间有 4G ,前 2G 是每个进程私有的,也就是说在进程切换的时候会变化,后 2G 是操作系统的,所以是固定的。既然用户态进程和核心态驱动在同一个进程空间里,是不是只要直接传个内存地址过来,就可以访问了?理论上可以但实际上不行,因为用户态的进程在不断地切换,使驱动运行时没法保证前面的用户态进程是哪个,也就不确定前 2G 虚拟地址空间的映射情况,那么用户态进程传来的地址也许不是合法的。

比较常用的做法是通过 MDL 进行内存的重映射。简单地说就是将同一块物理内存同时映射到用户态空间和核心态空间。

具体来说,可以有两种做法:用户态进程分配空间,内核态去映射。另一种是内核态分配空间,用户态进程去映射。

前者伪码:

// assume uva is a virtual address in user space, uva_size is its size
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
	MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
} __except(EXCEPTION_EXECUTE_HANDLER) {
	DbgPrint("error code = %d", GetExceptionCode);
PVOID kva = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
// use kva 
MmUnlockPages(mdl);
IoFreeMdl(mdl);

* 记得在 driver unl oad 之前 mdl unlock free 掉,否则会 BSoD

后者伪码:

PVOID kva = ExAllocatePoolWithTag(NonPagedPool, 1024, (ULONG)'PMET');
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
	MmBuildMdlForNonPagedPool(mdl);
} __except(EXCEPTION_EXECUTE_HANDLER) {
	DbgPrint("error code = %d", GetExceptionCode);
PVOID uva = MmMapLockedPagesSpecifyCache(mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); 

* 如果 kva 是分配在 nonpagedpool ,那这些物理页本身就是被 lock 住的,因此用的是 MmBuildMdlForNonPagedPool ,如果是分配在 paged pool 里的用 MmProbeAndLockPages

除了这种最原始的方式, Windows 还提供了两种称为DO_BUFFERED_IO 和 DO_DIRECT_IO的方式,前者中系统自动将用户态空间内存拷贝到了到核心态空间(Associated-Irp.SystemBuffer),后者由系统自动生成 MDL (Irp->MdlAddress)。其实这两种方法本质都是系统帮忙做了上面的部分流程,从而可以让程序员省了那些操作。

前面提到了一个关键数据结构 MDL memorydescriptor list ),系统用它来描述虚拟空间对应物理内存的 layout MDL 分为两部分:固定长部分和变长部分,固定长部分结构如下:

typedef struct _MDL {
  struct _MDL *Next;
  CSHORT Size;
  CSHORT MdlFlags;
  struct _EPROCESS *Process;
  PVOID MappedSystemVa;
  PVOID StartVa;
  ULONG ByteCount;
  ULONG ByteOffset;
} MDL, *PMDL;

Next: 指向下一个MDL结构,从而构成链表,有时一个IRP会包含多个MDL

Size:MDL本身的大小,注意包含了定长部分和变长两部分的size

MdlFlags:属性标记,如所描述的物理页有没有被lock住等

Process:顾名思义,指向该包含该虚拟地址的地址空间的对应进程结构

MappedSystemVa:内核态空间中的对应地址

StartVa:用户或者内核地址空间中的虚拟地址,取决于在哪allocate的,该值是页对齐的

ByteCount:MDL所描述的虚拟地址段的大小,byte为单位

ByteOffset:起始地址的页内偏移,因为MDL所描述的地址段不一定是页对齐的

如allocate出来的虚拟地址为0xac004010,则StartVa为0xac004000,ByteOffset为0x10,MmGetMdlVirtualAddress给出StartVa + ByteOffset。

变长部分包含了物理页编号数组,可以用

PPFN_NUMBER  pfn = MmGetMdlPfnArray(mdl)

来得到,注意里面只包含了pfn,不包含页内偏移量。数组的元素个数可以由ADDRESS_AND_SIZE_TO_SPAN_PAGES得到。

默认情况下,进程内存有一半为内 所有,一半为进程本身所有。但是因为 r3 的进程在不断的切换,所以不能在 r0 直接分配一个低位的内存(你不知道现在正在哪个进程的高位 )。 当然,使用 KeStackAttachProcess 附加到指定的进程上操作内存(读/写),最后再使用 KeUnstackDetachProcess 脱离进程,这样也可以 实现 r0 和 r3 通信,但更推荐使用 MDL 的方式对将同一块物理内存同时映射到 r0 和 r3,这样我们只需要使用 DeviceIoControl 向 驱动
前面分别通过读写 驱动 (IRP)和IO控制(ControlCOde)两种方法来 实现 ring3和ring0的通信,今天学习 共享内存 通信方法 共享内存 实现 有两种方式: 1.应用程序分配内存,提供给 驱动 程序,由 驱动 程序映射并锁定该内存。 2. 驱动 程序分配内存,然后映射到应用程序地址范围内。 这里主要学习按书上的第二种方法的 实现 。 1. 驱动 程序分配出一块内 空间。 2.使用 MDL 描述这
https://blog.csdn.net/wdykanq/article/details/7752909 http://blog.51cto.com/laokaddk/404584 内 层创建内存映射 用户 层 PVOID pShareMM_SYS; P MDL pShareMM_ MDL ; PVOID pShareMM_User; PKUANGKEEP...
Windows 共享数据和信息的机制:RPC、COM、OLE、DDE、窗口消息、剪贴板、邮箱、管道、套接字以及内存映射文件。 内存映射:通过让两个或多个进程映射同一个文件。(在进程空间 保留一个地址空间区域,将物理存储器提交给该区域) 内存映射文件的物理存储器(用来作为虚拟内存)来自一个位于磁盘 驱动 器上的数据文件。一旦该文件被映射,就可以访问它,就像文件已经加载内存一样。(操作系统使得内存能够将...
接前文 Windows 驱动 开发(一) MDL 驱动 读写 下面是 驱动 申请内存 NTSTATUS AllocMemory(IN ULONG ProcessPid, IN SIZE_T Length, OUT PVOID Buffer) NTSTATUS Status = STATUS_SUCCESS; PEPROCESS pEProcess = NULL; KAPC_STATE ApcState = { 0 }; PVOID BaseAddress = NULL; Status = PsLookup
在不同的场合,很多 驱动 编写人员需要在 驱动 用户 程序间 共享内存 。两种最容易的技术是:  l 应用程序发送IOCTL给 驱动 程序,提供一个指向内存的指针,之后 驱动 程序和应用程序就可以 共享内存 。(应用程序分配 共享内存 )  l 由 驱动 程序分配内存页,并映射这些内存页到指定 用户 模式进程的地址空间,并且将地址返回给应用程序。( 驱动 程序分配 共享内存 )  使用IOCTL共享Buffer:  ...