Lookaside结构和内存操作函数

 

1.PC上有三条总线,分别是数据总线、地址总线和控制总线。32位的CPU寻址能力为4GB(2^32)个字节。用户最多可以使用4GB的真实物理内存。PC中会拥有很多设备,其中很多设备提供了自己的设备内存。例如,显卡就会提供自己的显存。这部分内存会映射到PC的物理内存上,也就是读写这段物理地址,其实会读写设备的内存地址,而不会读写物理内存地址。

 

2.在CPU中有一个重要的寄存器CR0,它是32位的寄存器,其中的一个位(PG位)是负责告诉系统是否分页的。Windows在启动前会将它的PG位置1,即Windows允许分页。DDK中有个宏PAGE_SIZE记录着分页的大小,一般为4KB。

 

3.虚拟内存中第N个分页单元对应着物理内存第M个分页单元。这种对应不是一一对应,而是多对一的映射,多个虚拟内存页可以映射同一个物理内存页。还有一部分单元会被映射成磁盘上的文件,并标记为脏的。读取这段虚拟内存的时候,系统会发出一个异常,此时会触发异常处理函数。异常处理函数会将这个页的磁盘文件读入内存,并将标记设置为不脏。当让经常不读写的内存页,可以交换成文件,并将此页设置为脏。还有一部分单元什么也没有对应,即空的。

 

4.Windows这样设计的原因:

①:虚拟的增加了内存的大小。不管PC是否有足够的4GB的物理内存,操作系统总会有4GB的虚拟内存。这就允许使用者申请更多的内存,当物理内存不够的时候,可以通过将不常用的虚拟内存页交换成文件,等需要的时候再去读取。

②:是不同进程的虚拟内存互补干扰,为了让系统可以同时运行不同的进程,Windows操作系统让每个进程看到的虚拟内存都不同。这个方法使不同的进程会有不同的物理内存到虚拟内存的映射。

 

5.Windows驱动程序里的不同例程运行在不同的进程中。DriverEntry例程和AddDevice例程是运行在系统(System)进程中的。而其中一些例程,例如:IRP_MJ_READ和IRP_MJ_WRITE的派遣函数会运行于应用程序的上下文中,也就是运行在某个进程的环境中,所能访问的虚拟地址是这个进程的虚拟地址。

 

6.Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存,而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存

 

7.当程序的中断请求级在DISPATCH_LEVEL之上时(包括DISPATCH_LEVEL),程序只能使用非分页内存,否则将导致蓝屏死机。

 

8.Lookaside结构:

频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序猿提供了Lookaside结构来解决这个问题。可以将Lookaside结构想象成一个内存容器。在初始的时候,它先向Windows申请了比较大的内存。以后程序猿每次申请内存的时候,不是直接向Windows申请内存,而是向Lookaside对象申请内存。

初始化Lookaside:

ExInitializeNPagedLookasideList()

ExInitializePagedLookasideList()

申请内存:

ExInitializeNPagedLookasideList()

ExInitializePagedLookasideList()

回收内存:

ExFreeToNPagedLookasideList()

ExFreeToPagedLookasideList()

删除Lookaside对象:

ExDeleteNPagedLookasideList()

ExDeletePagedLookasideList()

实例:

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
#pragma INITCODE
VOID LookasideTest()
{
    //初始化Lookaside对象
    PAGED_LOOKASIDE_LIST pageList;
    ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0);

#define ARRAY_NUMBER 50
    PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER];
    //模拟频繁申请内存
    for (int i=0;i<ARRAY_NUMBER;i++)
    {
        MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList);
    }

    //模拟频繁回收内存
    for (i=0;i<ARRAY_NUMBER;i++)
    {
        ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]);
        MyObjectArray[i] = NULL;
    }

    ExDeletePagedLookasideList(&pageList);
    //删除Lookaside对象
}

 

9.内存间复制:

RtlCopyMemory() 用这个可以复制内存,但内部没有考虑内存重叠的情况。如下:有三段等长的内存,ABCD分别代表三段内存的起始地址和终止地址。如果需要将A到C段的内存复制到B到D这段内存上,这时B到C段的内存就是重叠的部分。

image

而RtlCopyMemory没有考虑重叠的部分,它不能保证重叠部分是否被复制。memcpy

而memmove这个函数对两个内存是否重叠进行了判断,这种判断却牺牲了速度。如果你能确保复制的部分没有重叠,请选择使用memcpy函数。否则使用memmove函数。DDK提供对其进行了封装,RtlMoveMemory。

 

10.填充内存:RtlFillMemory     

     内存清零:RtlZeroMemory

     内存比较:RtlCompareMemory/RtlEqualMemory

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma INITCODE
VOID RtlTest()
{
    PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
    //用零填充内存
    RtlZeroMemory(pBuffer,BUFFER_SIZE);

    PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
    //用固定字节填充内存
    RtlFillMemory(pBuffer2,BUFFER_SIZE,0xAA);

    //内存拷贝
    RtlCopyMemory(pBuffer,pBuffer2,BUFFER_SIZE);

    //判断内存是否一致
    ULONG ulRet = RtlCompareMemory(pBuffer,pBuffer2,BUFFER_SIZE);
    if (ulRet==BUFFER_SIZE)
    {
        KdPrint(("The two blocks are same.\n"));
    }
}

 

11.重载new和delete操作符

实例:

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
//全局new操作符
void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool)
{
    KdPrint(("global operator new\n"));
    KdPrint(("Allocate size :%d\n",size));
    return ExAllocatePool(PagedPool,size);
}
//全局delete操作符
void __cdecl operator delete(void* pointer)
{
    KdPrint(("Global delete operator\n"));
    ExFreePool(pointer);
}

class TestClass
{
public:
    //构造函数
    TestClass()
    {
        KdPrint(("TestClass::TestClass()\n"));
    }

    //析构函数
    ~TestClass()
    {
        KdPrint(("TestClass::~TestClass()\n"));
    }

    //类中的new操作符
    void* operator new(size_t size,POOL_TYPE PoolType=PagedPool)
    {
        KdPrint(("TestClass::new\n"));
        KdPrint(("Allocate size :%d\n",size));
        return ExAllocatePool(PoolType,size);
    }

    //类中的delete操作符
    void operator delete(void* pointer)
    {
        KdPrint(("TestClass::delete\n"));
        ExFreePool(pointer);
    }
private:
    char buffer[1024];
};


void TestNewOperator()
{
    TestClass* pTestClass = new TestClass;
    delete pTestClass;

    pTestClass = new(NonPagedPool) TestClass;
    delete pTestClass;

    char *pBuffer = new(PagedPool) char[100];
    delete []pBuffer;

    pBuffer = new(NonPagedPool) char[100];
    delete []pBuffer;
}

 

12.检查内存可用性

ProbeForRead/ProbeForWrite

这两个函数不是返回该段内存是否可读写,而是当不可读写的时候,引发一个异常。

 

13.结构化异常处理

__try

{}

__except(filter_value)

{}

filter_value:

①:EXCEPTION_EXECUTE_HANDLE,该数值为1,进入到__except进行错误处理,处理完后不再回到__try{}块中,转而继续执行。

②:EXCEPTION_CONTINUE_SEARCH,该数值为0,不使用__except中的异常处理,转而向上一层回传,如果已经是最外层,则向操作系统请求异常处理函数。

③:EXCEPTION_CONTINUE_EXECUTION,该数值为-1,重复先前错误的指令,很少用到。

实例:

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
#pragma INITCODE
VOID ProbeTest()
{
    PVOID badPointer = NULL;

    KdPrint(("Enter ProbeTest\n"));

    __try
    {
        KdPrint(("Enter __try block\n"));

        //判断空指针是否可读,显然会导致异常
        ProbeForWrite(badPointer,100,4);

        //由于在上面引发异常,所以以后语句不会被执行!
        KdPrint(("Leave __try block\n"));
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        KdPrint(("Catch the exception\n"));
        KdPrint(("The program will keep going\n"));
    }

    //该语句会被执行
    KdPrint(("Leave ProbeTest\n"));
}

__try

{}

__finally{}     //无论try中运行什么代码即使是return或异常,在程序退出前都会运行finally中的语句。

 

14.ASSERT断言。

本文链接:http://www.alonemonkey.com/lookaside-memory-fun.html