【驱动学习】4.1-内存操作及内存属性

概述:

内存属性

在了解自旋锁之前需要先了解下内核的内存分配相关内容,相关的 API 可以参考 内存相关 一文。 这里补充下 ExAllocatePool 的参数1,也就是申请的页属性参数 POOL_TYPE

微软官方说明:_POOL_TYPE (wdm.h) - Windows drivers | Microsoft Learn

这里主要就常用的三个进行简单说明:

  • NonPagedPool 非分页池,这是不可分页的系统内存。 可以从任何 IRQL 访问非分页池,但它是一种稀缺资源,驱动程序应仅在必要时进行分配。该内存不会被交换到磁盘上,并且可以直接被内核访问,适用于需要快速访问的内存,例如驱动程序的代码、中断处理程序、系统调用等。

  • PagePool 分页池,它是可分页的系统内存。 只能在 IRQL < DISPATCH_LEVEL分配和访问分页池。

  • NonPagedPoolExecute

    带有执行权限的非分页内存。如果是使用 NonPagedPoolExecute 申请的内存则需要留意,避免被利用。 从 Windows 8 开始,NonPagedPoolExecuteNonPagedPool 值的备用名称。 此值指示分配的内存是非分页且可执行的,即在此内存中启用指令执行。 若要从早期版本的 Windows 移植驱动程序,通常应将驱动程序源代码中 NonPagedPool 名称的所有或大多数实例替换为 NonPagedPoolNx。 避免将 NonPagedPool 名称的实例替换为 NonPagedPoolExecute ,除非显式需要可执行内存。 有关详细信息,请参阅 No-Execute (NX) Nonpaged Pool。

内存操作

ExAllocatePool 内存申请

RtlZeroMemory 内存初始化

RtlCopyMemory 内存拷贝

ExFreePool 内存释放

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// #include <ntddk.h>

#include <ntifs.h>

typedef struct _MyStruct
{
ULONG x;
ULONG y;
}MyStruct, *PMyStruct;

NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrint("Bye, Driver\n");

return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Hello World, Driver\n");
DbgPrint("PDRIVER_OBJECT-> [0x%08x]\n", pDriver);
DbgPrint("PUNICODE_STRING->[%ws]\n", pReg->Buffer);

// 1 用于分配与释放非标签内存
PVOID buffer = ExAllocatePool(NonPagedPool, 1024);

DbgPrint("[*] 分配内存地址=%p \n", buffer);

if (buffer)
{
ExFreePool(buffer);
}

// 用于分配带有标签的内存
PMyStruct pStu = (PMyStruct)ExAllocatePoolWithTag(NonPagedPoolExecute, sizeof(PMyStruct), "KernelTest");

if (pStu)
{
pStu->x = 100;
pStu->y = 200;

DbgPrint("[*] 分配内存 x = %d, y= %d", pStu->x, pStu->y);

ExFreePoolWithTag(pStu, "KernelTest");
}

UNICODE_STRING dst = { 0 };
UNICODE_STRING src = RTL_CONSTANT_STRING(L"Hello KernelTest");

dst.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, src.Length);
if (dst.Buffer == NULL)
{
DbgPrint("[-] 分配空间错误");
}

dst.Length = dst.MaximumLength = src.Length;
RtlCopyUnicodeString(&dst, &src);

DbgPrint("[*] 输出拷贝 = %wZ", dst);

pDriver->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}

输出

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0xb0402e30]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfMemory]
[*] 分配内存地址=FFFFAA02B11358F0
[*] 分配内存 x = 100, y= 200
[*] 输出拷贝 = Hello KernelTest

双向链表

双向链表的结构体如下所示:

1
2
3
4
5
typedef struct _LIST_ENTRY
{
struct _LIST_ENTRY* Flink; // 指向后一个节点
struct _LIST_ENTRY* Blink; // 指向前一个节点
}LIST_ENTRY, * PLIST_ENTRY;

需要注意的是:FLink 指向后一个节点,Blink 指向前一个结点。

多线程访问

需要注意的是,多线程访问同一数据结构时会存在线程同步问题,常见的使用方式就是使用锁机制进程同步。自旋锁是一种常用的锁机制,它是一种高 IRQL 锁,用于同步和独占地访问某个资源。

**自旋锁的基本思想:**当一个线程尝试获取锁时,如果锁已经被占用,则该线程不断循环(即自旋),直到锁被释放。自旋锁适用于锁的持有时间较短,且竞争者较少的情况下,可以避免进程上下文的切换和调度开销。在下一章将详细记录和说明。

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// #include <ntddk.h>

#include <ntifs.h>

typedef struct _MyStruct
{
ULONG x;
ULONG y;
LIST_ENTRY lpListEntry;
}MyStruct, * PMyStruct;

NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrint("Bye, Driver\n");

return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("Hello World, Driver\n");
DbgPrint("PDRIVER_OBJECT-> [0x%016x]\n", pDriver);
DbgPrint("PUNICODE_STRING->[%ws]\n", pReg->Buffer);

// 初始化头节点
LIST_ENTRY ListHeader = { 0 };
InitializeListHead(&ListHeader);

// 定义链表元素
MyStruct testA = { 0 };
MyStruct testB = { 0 };
MyStruct testC = { 0 };

testA.x = 10;
testA.y = 20;

testB.x = 100;
testB.y = 200;

testC.x = 1000;
testC.y = 2000;

// 分别插入节点到头部和尾部
InsertHeadList(&ListHeader, &testA.lpListEntry);
InsertTailList(&ListHeader, &testB.lpListEntry);
InsertTailList(&ListHeader, &testC.lpListEntry);

// 输出链表数据
PLIST_ENTRY pListEntry = NULL;
pListEntry = ListHeader.Flink;

int i = 0;

while (pListEntry != &ListHeader)
{
// 计算出成员距离结构体顶部内存距离
PMyStruct ptr = CONTAINING_RECORD(pListEntry, MyStruct, lpListEntry);
DbgPrint("节点元素x = %d, 节点元素y = %d\n", ptr->x, ptr->y);

if (i++ > 10)
{
break;
}
// 获取下一个链表地址
pListEntry = pListEntry->Flink;
}

pDriver->DriverUnload = UnloadDriver;

return STATUS_SUCCESS;
}

驱动输出:

1
2
3
4
5
6
Hello World, Driver
PDRIVER_OBJECT-> [0x00000000a78b4950]
PUNICODE_STRING->[\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\KmdfListEntry]
节点元素x = 10, 节点元素y = 20
节点元素x = 100, 节点元素y = 200
节点元素x = 1000, 节点元素y = 2000

【驱动学习】4.1-内存操作及内存属性
https://hodlyounger.github.io/2025/01/23/A_OS/Windows/驱动/windows驱动开发教程/【驱动学习】4.1-内存操作及内存属性/
作者
mingming
发布于
2025年1月23日
许可协议