Windows Via C/C++ 读书笔记 9 Windows Memory Architecture

这章讲进程内存与操作系统物理内存之间的关系,操作系统如何管理。关键字:虚拟地址空间,reserve commit free三步。

Windows Via C/C++ 读书笔记 

Windows Memory Architecture 

1. 进程的虚拟地址空间

每个进程都有自己的虚地址空间,32bit系统的地址空间是4GB0x00000000~0xFFFFFFFF),64bit系统是16EB。各个进程的实际地址是操作系统分配的地址加上虚拟空间地址的映射,因为各个进程的实际地址空间被分配在不同的区域,所以各个进程间的地址空间是隔离的,不能互相访问。

2. 虚拟地址空间分配

Partition

x86 32-Bit Windows

x86 32-Bit Windows with 3 GB User-Mode

x64 64-Bit Windows

IA-64 64-Bit Windows

NULL-Pointer Assignment

0x00000000

0x00000000

0x00000000'00000000

0x00000000'00000000

0x0000FFFF

0x0000FFFF

0x00000000'0000FFFF

0x00000000'0000FFFF

User-Mode

0x00010000

0x00010000

0x00000000'00010000

0x00000000'00010000

0x7FFEFFFF

0xBFFEFFFF

0x000007FF'FFFEFFFF

0x000006FB'FFFEFFFF

64-KB Off-Limits

0x7FFF0000

0xBFFF0000

0x000007FF'FFFF0000

0x000006FB'FFFF0000

0x7FFFFFFF

0xBFFFFFFF

0x000007FF'FFFFFFFF

0x000006FB'FFFFFFFF

Kernel-Mode

0x80000000

0xC0000000

0x00000800'00000000

0x000006FC'00000000

0xFFFFFFFF

0xFFFFFFFF

0xFFFFFFFF'FFFFFFFF

 

2.1. Null-Pointer Assignment Partition

这个区域被认为是空指针区域,即任何试图访问这块区域的操作都会产生"access violation "错误。这个机制是用来帮助程序员尽早发现错误。比如某个指针指向NULL0x0000),当试图访问这个指针指向的内存时,操作系统能够发现这个错误,抛出一个"access violation "错误。

2.2. User-Mode Partition

这是用户使用的内存地址(相对地址)。因为不同进程被隔离在了不同的内存区域,因此一个进程不会因为另一个进程访问到它的内存,造成崩溃。

通过设置boot configuration data (BCD),可以修改User-Mode的大小。

2.3. Kernal-Mode Partition

内核模式区域是由操作系统使用,用来 线程管理、内存管理、文件系统、网络支持、驱动程序加载等。这块区域会被所有的进程共享,任何试图访问这块内存的代码都会导致访问非法错误,系统会弹出一个错误对话框,并结束进程。

3. 内存管理

所有进程可以使用的内存大小为物理内存和磁盘虚拟内存的总和,它们的管理单位是页面(Page)。Page3个状态:freereservedcommitted。操作系统负责物理内存和磁盘虚拟内存的交换。当操作系统需要物理内存时,把最近最少使用内存映射到文件,然后使用这块内存。

进程操作内存的步骤为3步:

1. 先从进程自己的虚地址空间申请(Alloca)一块内存(n个页面大小),其它申请操作不能再使用这段地址。这步操作成为Reserving。这块内存的Pages状态由free变为reserved

2. 请求操作系统分配一块物理内存与已经reserve的内存映射。对内存进行读写操作。这一步成为commit,内存状态由reserved变为committed

3. 释放内存。内存状态变为free

3.1. Reserve Memory

调用函数:

LPVOID VirtualAlloc(

  LPVOID lpAddress,

  SIZE_T dwSize,

  DWORD flAllocationType,

  DWORD flProtect

);

这步操作实际上操作系统还没有给进程分配一块实际的物理内存或者磁盘虚拟内存,通过写程序debug(后面提供这个MSDN的例子程序),这个指针有地址,但是没有内存。如果程序这个时候试图访问这块内存,是会导致异常的。

注:后面讲的例子程序就是利用了这个特点,当有访问异常的时候,说明还没有给这个地址分配实际的物理内存,由异常处理函数来做Commit操作,给该地址分配一个Page。因为进程不需要给所有reserved的内存都commit一块物理内存,只给有需要的内存分配,这么做可以节省实际的物理内存。

3.2. Commit Memory

调用函数与Reserve相同。

这一步请求操作系统为进程的地址空间分配一块实际的物理内存,供进程读写。

3.3. Free Memory

调用函数:

BOOL VirtualFree(

  LPVOID lpAddress,

  SIZE_T dwSize,

  DWORD dwFreeType

);

释放内存。

3.3.1. 例子Reserve and Commit Memory

// A short program to demonstrate dynamic memory allocation using 

// a structured exception handler. 

#include 

#include               // for printf

#include              // for exit

#define PAGELIMIT 80            // ask for this many pages

LPTSTR lpNxtPage;               // address of the next page to ask for

DWORD dwPages = 0;              // count of pages gotten so far

DWORD dwPageSize;               // the page size on this computer

INT PageFaultExceptionFilter(DWORD dwCode)

{

  LPVOID lpvResult;

  // If the exception is not a page fault, exit.

  if (dwCode != EXCEPTION_ACCESS_VIOLATION)

    {

      printf("Exception code = %d/n", dwCode);

      return EXCEPTION_EXECUTE_HANDLER;

    }

  printf("Exception is a page fault/n");

  // If the reserved pages are used up, exit.

  if (dwPages >= PAGELIMIT)

    {

      printf("Exception: out of pages/n");

      return EXCEPTION_EXECUTE_HANDLER;

    }

  // Otherwise, commit another page.

  lpvResult = VirtualAlloc(

                     (LPVOID) lpNxtPage, // next page to commit

                     dwPageSize,         // page size, in bytes

                     MEM_COMMIT,         // allocate a committed page

                     PAGE_READWRITE);    // read/write access

  if (lpvResult == NULL )

    {

      printf("VirtualAlloc failed/n");

      return EXCEPTION_EXECUTE_HANDLER;

    } else {

      printf ("Allocating another page./n");

    }

  // Increment the page count, and advance lpNxtPage to the next page.

  dwPages++;

  lpNxtPage += dwPageSize;

  // Continue execution where the page fault occurred.

  return EXCEPTION_CONTINUE_EXECUTION;

}

VOID ErrorExit(LPTSTR lpMsg)

{

  printf ("Error! %s with error code of %ld/n",

          lpMsg, GetLastError ());

  exit (0);

}

VOID main(VOID)

{

  LPVOID lpvBase;               // base address of the test memory

  LPTSTR lpPtr;                 // generic character pointer

  BOOL bSuccess;                // flag

  DWORD i;                      // generic counter

  SYSTEM_INFO sSysInfo;         // useful information about the system

  GetSystemInfo(&sSysInfo);     // initialize the structure

  printf ("This computer has page size %d./n", sSysInfo.dwPageSize);

  dwPageSize = sSysInfo.dwPageSize;

  // Reserve pages in the process's virtual address space.

  lpvBase = VirtualAlloc(

                     NULL,                 // system selects address

                     PAGELIMIT*dwPageSize, // size of allocation

                     MEM_RESERVE,          // allocate reserved pages

                     PAGE_NOACCESS);       // protection = no access

  if (lpvBase == NULL )

    ErrorExit(TEXT("VirtualAlloc reserve failed"));

  lpPtr = lpNxtPage = (LPTSTR) lpvBase;

  // Use structured exception handling when accessing the pages.

  // If a page fault occurs, the exception filter is executed to

  // commit another page from the reserved block of pages.

  for (i=0; i < PAGELIMIT*dwPageSize; i++)

    {

      __try

        {

          // Write to memory.

          lpPtr[i] = 'a';

        }

      // If there's a page fault, commit another page and try again.

      __except ( PageFaultExceptionFilter( GetExceptionCode() ) )

        {

          // This code is executed only if the filter function is 

          // unsuccessful in committing the next page.

          ExitProcess( GetLastError() );

        }

    }

  // Release the block of pages when you are finished using them.

  bSuccess = VirtualFree(

                lpvBase,       // base address of block

                0,             // bytes of committed pages

                MEM_RELEASE);  // decommit the pages

  printf ("Release %s./n", bSuccess ? "succeeded" : "failed" );

}

例子里面关键看它的三个步骤,对应我前面讲的内存管理三步:

1 Reserve

它先reserve了块内存,大小为PAGELIMIT*dwPageSize。页面大小从操作系统获取,我的系统是4960字节。

  lpvBase = VirtualAlloc(

                     NULL,                 // system selects address

                     PAGELIMIT*dwPageSize, // size of allocation

                     MEM_RESERVE,          // allocate reserved pages

                     PAGE_NOACCESS);       // protection = no access

2.Commit

它的Commit操作放在了异常处理函数PageFaultExceptionFilter里面。当程序运行到给lpPtr赋值(写操作)的时候,如果这个时候还没有给这个地址做commit,那么程序会出一个异常,然后就进入PageFaultExceptionFilter里面。在这个函数里面调用VirtualAlloc函数分配一块实际的物理内存。程序继续while循环,这个时候赋值就会成功。

3.Free

这个例子建议单步调试看运行效果,就能理解这章的内容了。我刚开始看也是云里雾里,结合msdn,才把它看明白。

你可能感兴趣的:(C/C++)