• 19、Windows内存管理


    1、虚拟地址

    Windows的所有程序(ring0,ring3),可以操作的都是虚拟内存。CPU中寄存器CR0一个位PG位来告诉系统是否分页的。1为允许分页。DDK中宏PAGE_SIZE记录着分页大小,一般为4K4GB的虚拟内存会被分割成1M个单元。

    wps_clip_image-3412

    图 物理内存的映射 P120

    2、两种模式

    4G虚拟地址中,低2G为用户模式,高2G为内核模式。Windows规定用户态程序只能访问用户模式地址,而内核态程序可以访问整个4G虚拟地址。[1]

    进程切换时,所有进程的内核地址映射完全一致,进程切换时改变,只是改变用户模式地址的映射。

    wps_clip_image-9322

    图 两种模式 P121

    Windwos驱动程序里的不同例程运行在不同的进程中。

    打印当前进程的进程名:

    void DisplayItsProcessName()

    {

    PEPROCESS pEProcess = PsGetCurrentProcess();

    PTSTR = ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);

    KdPrint(("%s\n", ProcessName));

    }

    3、分页与非分页内存

    可以交换到文件中的虚拟内存页面称为分页内存,否则称为非分页内存。当程序的中断请求级大于等于DISPATCH_LEVEL时,程序只能使用非分页内存。

    #define PAGEDCODE code_seg("PAGE")

    #define LOCKEDCODE code_seg()

    #define INITCODE code_seg("INIT")

    #define PAGEDDATA data_seg("PAGE")

    #define LOCKEDDATA data_seg()

    #define INITDATA data_seg("INIT")

    PAGEDCODE 等放在函数前来表现是可否可以分页等情况。PAGED_CODE()DDK提供的宏,它只在check版本中生效,来检查运行是否低于DISPATCH_LEVEL的中断请求级,如果不低于则产生断言。

    4、驱动程序不适合递归调用或者局部变量是大型结构体。如果需要大型结构体,请使用堆。

    ExAllocatePool

    ExAllocatePoolWithTag

    ExAllocatePoolWithQuotaTag

    ExAllocatePoolWithQuota

    5、链表

    双向链表有两个指针,BLINK指向前一个元素,FLINK指向下一个元素。

    wps_clip_image-7593

    IsListEmpty

    InitializeListHead

    typedef struct _LIST_ENTRY {

    struct _LIST_ENTRY *Flink;

    struct _LIST_ENTRY *Blink;

    } LIST_ENTRY, *PLIST_ENTRY;程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的数据结构串与一个链表。

    InsertHeadList

    wps_clip_image-23641

    图 插入前链表状态

    wps_clip_image-12080

    图 插入后

    InsertTailList

    RemoveHeadList

    RemoveTailList

    和插入链表有两种方法一样,删除链表也有两种方法,一种是从头部删除,另外一种是尾部删除。

    不过有一个问题如下:

    PLIST_ENTRY RemoveHeadList(

    __inout PLIST_ENTRY ListHead

    );

    RemoveHeadList returns a pointer to the entry removed from the list. If the list is empty, RemoveHeadList returns ListHead.

    也就是说这个函数返回的是一个指针,指向从链表中删除下来的元素中的LIST_ENTRY,那么如何得到用户自定义的数据结构的指针呢?;如果 LIST_ENTRY放在一个结构体的首部,那么返回的这个地址就等于用户自定义的数据结构的指针;而如果LIST_ENTRY不放在结构体的首部呢?这时,我们用地址偏移来实现。一个宏如下定义:

    PCHAR CONTAINING_RECORD(

    [in] PCHAR Address,

    [in] TYPE Type,

    [in] PCHAR Field

    );

    如下实现:

    #define CONTAINING_RECORD(address, type, field) ((type *)( \

    (PCHAR)(address) - \

    (ULONG_PTR)(&((type *)0)->field)))

    代码
    1 VOID LinkListTest()
    2 {
    3 LIST_ENTRY linkListHead;
    4  //初始化链表
    5  InitializeListHead(&linkListHead);
    6 PMYDATASTRUCT pData;
    7 ULONG i = 0;
    8  //在链表中插入10个元素
    9  KdPrint(("Begin insert to link list"));
    10  for (i=0 ; i<10 ; i++)
    11 {
    12 pData = (PMYDATASTRUCT)
    13 ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
    14 pData->number = i;
    15 InsertHeadList(&linkListHead,&pData->ListEntry);
    16 }
    17
    18  //从链表中取出,并显示
    19  KdPrint(("Begin remove from link list\n"));
    20  while(!IsListEmpty(&linkListHead))
    21 {
    22 PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
    23 pData = CONTAINING_RECORD(pEntry,
    24 MYDATASTRUCT,
    25 ListEntry);
    26 KdPrint(("%d\n",pData->number));
    27 ExFreePool(pData);
    28 }
    29 }

    4Lookaside结构

    如果驱动程序频繁地从内存中申请回收固定大小的内存,可以使用Lookaside对象。可以将Lookaside对象想象成一个自动的内存分配容器。避免产生内存空洞。

    1)初始化:

    ExInitializeNPagedLookasideList

    ExInitializePagedLookasideList

    2)初始完内存后,可以申请内存了:

    ExAllocateFromNPagedLookasideList

    ExAllocateFromPagedLookasideList

    3)回收内存

    ExFreeToNPagedLookasideList

    ExFreeToPagedLookasideList

    4)删除Lookaside对象

    ExDeleteNPagedLookasideList

    ExDeletePagedLookasideList

    一个例子如下:

    代码
    1 VOID LookasideTest()
    2 {
    3 //初始化Lookaside对象
    4 PAGED_LOOKASIDE_LIST pageList;
    5 ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0);
    6 #define ARRAY_NUMBER 50
    7 PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER];
    8 //模拟频繁申请内存
    9 for (int i=0;i<ARRAY_NUMBER;i++)
    10 {
    11 MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList);
    12 }
    13 //模拟频繁回收内存
    14 for (i=0;i<ARRAY_NUMBER;i++)
    15 {
    16 ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]);
    17 MyObjectArray[i] = NULL;
    18 }
    19 ExDeletePagedLookasideList(&pageList);
    20 //删除Lookaside对象
    21 }

    5、运行时函数

    由编译器提供,不同操作系统实现不同,但是接口一样,如malloc

    1)内存间复制(非重叠)

    RtlCopyMemory

    2)可重叠复制

    RtlMoveMemory

    3)填充内存

    RtlFillMemory

    RtlZeroMemory

    3)内存比较

    RtlCompareMemory

    DDK提供的运行时函数都是RtlXX形式。

    6、使用C++特性分配内存

    不能直接使用newdelete。因为MS编译器没有提供内核模式下的new操作符,我们可以对其进行重载来使用。

    重载有两种方法,一种是类中重载,一种全局重载。

    代码
    1 //全局new操作符
    2 void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool)
    3 {
    4 KdPrint(("global operator new\n"));
    5 KdPrint(("Allocate size :%d\n",size));
    6 return ExAllocatePool(PagedPool,size);
    7 }
    8 //全局delete操作符
    9 void __cdecl operator delete(void* pointer)
    10 {
    11 KdPrint(("Global delete operator\n"));
    12 ExFreePool(pointer);
    13 }
    14
    15 class TestClass
    16 {
    17 public:
    18 //构造函数
    19 TestClass()
    20 {
    21 KdPrint(("TestClass::TestClass()\n"));
    22 }
    23
    24 //析构函数
    25 ~TestClass()
    26 {
    27 KdPrint(("TestClass::~TestClass()\n"));
    28 }
    29
    30 //类中的new操作符
    31 void* operator new(size_t size,POOL_TYPE PoolType=PagedPool)
    32 {
    33 KdPrint(("TestClass::new\n"));
    34 KdPrint(("Allocate size :%d\n",size));
    35 return ExAllocatePool(PoolType,size);
    36 }
    37
    38 //类中的delete操作符
    39 void operator delete(void* pointer)
    40 {
    41 KdPrint(("TestClass::delete\n"));
    42 ExFreePool(pointer);
    43 }
    44 private:
    45 char buffer[1024];
    46 };
    47

    49 void TestNewOperator()
    50 {
    51 TestClass* pTestClass = new TestClass;
    52 delete pTestClass;
    53
    54 pTestClass = new(NonPagedPool) TestClass;
    55 delete pTestClass;
    56
    57 char *pBuffer = new(PagedPool) char[100];
    58 delete []pBuffer;
    59
    60 pBuffer = new(NonPagedPool) char[100];
    61 delete []pBuffer;
    62 }

    7、其它

    1NTSTATUS含义

    ScreenShot003

    NTSTATUS含义 P141

    2)检查内存可用性

    ProbeForRead

    ProbeForWrite

    如果不满足条件时将是引发异常。用异常处理机制进行捕获。

    3)结构化异常处理

    异常概念类似于中断

    Atry-except

    try-except-statement :

    __try

    {

    compound-statement

    }

    __except ( expression )

    {

    compound-statement

    }

    EXCEPTION_CONTINUE_EXECUTION

    EXCEPTION_CONTINUE_SEARCH

    EXCEPTION_EXECUTE_HANDLER

    例子如下:

    代码
    1 #pragma INITCODE
    2 VOID ProbeTest()
    3 {
    4 PVOID badPointer = NULL;
    5 KdPrint(("Enter ProbeTest\n"));
    6 __try
    7 {
    8 KdPrint(("Enter __try block\n"));
    9 //判断空指针是否可读,显然会导致异常
    10 ProbeForWrite(badPointer,100,4);
    11 //由于在上面引发异常,所以以后语句不会被执行!
    12 KdPrint(("Leave __try block\n"));
    13 }
    14
    15 __except(EXCEPTION_EXECUTE_HANDLER)
    16 {
    17 KdPrint(("Catch the exception\n"));
    18 KdPrint(("The program will keep going\n"));
    19 }
    20 //该语句会被执行
    21 KdPrint(("Leave ProbeTest\n"));
    22 }

    其它引发异常函数:

    ExRaiseAccessViolation

    ExRaiseDatatypeMisalignment

    ExRaiseStatus

    Btry-finally

    try-finally-statement :

    __try compound-statement

    __finally compound-statement

    强迫函数有退出前执行一段代码。常用来一些资源的回收工作。

    代码
    1 #pragma INITCODE
    2 NTSTATUS TryFinallyTest()
    3 {
    4 NTSTATUS status = STATUS_SUCCESS;
    5 __try
    6 {
    7 //做一些事情
    8 return STATUS_SUCCESS;
    9 }
    10 __finally
    11 {
    12 KdPrint(("Enter finallly block\n"));
    13 return status;
    14 }
    15 }

    4)防止“侧效”错误(也就是多行宏在if等语句中表达的意思出现了不同),在if,while,for等语句中,无论是否只有一句话,都不能省略{}

    ASSERT断言

    参考:

    【1】 windows驱动开发详解

  • 相关阅读:
    2021-06-22 总结
    【每日一题】13. 罗马数字转整数
    【每日一题】12. 整数转罗马数字
    【每日一题】1269. 停在原地的方案数
    【每日一题】1310. 子数组异或查询
    【每日一题】1734. 解码异或后的排列
    【每日一题】872. 叶子相似的树
    【每日一题】1482. 制作 m 束花所需的最少天数
    【每日一题】1723. 完成所有工作的最短时间
    【每日一题】1486. 数组异或操作
  • 原文地址:https://www.cnblogs.com/mydomain/p/1863027.html
Copyright © 2020-2023  润新知