1.进程的虚拟地址空间
每 个进程都有自己的虚拟地址空间。对32位进程来说,这个地址空间的大小为4GB,这是因为32位指针可以表示从0x00000000到 0xFFFFFFFF之间的任一值,指针在这个范围内可以有4 294 967 296个值,它们覆盖了进城的4GB地址空间。对62位进程来说,它们可以覆盖16EB地址空间,这个地址空间实在太大了!!!
每个进程有自己的私有地址空间,但是要记住这只是虚拟地址空间,不是物理存储器。为了能过正常读/写数据,我们需要把物理存储器分配或映射到相应的虚拟地址空间,否则将导致访问违规。
2.虚拟地址空间的分区
每个进程的虚拟地址空间被划分成许多分区(partition)。由于地址空间的分区依赖于操作系统的底层实现,因此会随着Windows内核的不同而略有变化。表1列出了各平台上对进程地址空间的分区。
分区 |
x 86 32位Windows |
3 GB用户模式下的 x 86 32位Windows |
x 64 64位Windows |
IA-64 64位 Windows |
---|---|---|---|---|
空指针赋值分区 |
0x00000000 |
0x00000000 |
0x00000000'00000000 |
0x00000000'00000000 |
0x0000FFFF |
0x0000FFFF |
0x00000000'0000FFFF |
0x00000000'0000FFFF |
|
用户模式分区 |
0x00010000 |
0x00010000 |
0x00000000'00010000 |
0x00000000'00010000 |
0x7FFEFFFF |
0xBFFEFFFF |
0x000007FF'FFFEFFFF |
0x000006FB'FFFEFFFF |
|
64-KB 禁入分区 |
0x7FFF0000 |
0xBFFF0000 |
0x000007FF'FFFF0000 |
0x000006FB'FFFF0000 |
0x7FFFFFFF |
0xBFFFFFFF |
0x000007FF'FFFFFFFF |
0x000006FB'FFFFFFFF |
|
内核模式分区 |
0x80000000 |
0xC0000000 |
0x00000800'00000000 |
0x000006FC'00000000 |
0xFFFFFFFF |
0xFFFFFFFF |
0xFFFFFFFF'FFFFFFFF |
0xFFFFFFFF'FFFFFFFF |
空指针赋值分区:
保留该区域的目的是为了帮助程序员捕获对空指针的赋值。如果进程中的线程试图读取或写入位于这一分区内的内存地址,就会引发访问违规。没有任何办法可以让我们分配到位于这一地址区间内的虚拟内存。
用户模式分区:
这一分区是进程地址空间的驻地。对所有应用程序来说,进程的大部分数据都保存在这一分区。
内核模式分区:
系 统需要用这一空间来存放内核代码。设备驱动程序代码、设备输入/输出高速缓存、非分页缓冲池分配表、进程页面表,等等。驻留在这一分区内的任何东西为所有 进程共有。该分区中的所有代码和数据都被完全的保护起来。如果一个应用程序试图读取或写入位于这一分区中的内存地址,会引发访问违规。在默认情况下,访问 违规会导致系统先向用户显示一个消息框,然后结束该应用程序。
3.地址空间中的区域
当系统创建一个进程并赋予它地址空间时,可用地址空间中的大部分都是闲置的(free)或尚未分配的(unallocated)
。为了使用这部分地址空间,我们必须调用VirtualAlloc来分配其中的区域(region) 。分配区域的操作被称为预定(reserving) 。
当应用程序预定地址空间区域时,系统会确保区域的起始地址正好是分配粒度 (allocation granularity)的整数倍。分配粒度会根据不同的CPU平台而有所不同。目前,所有的CPU平台都使用相同的分配粒度,大小为64KB—也就是系统会把分配请求取整到64KB的整数倍。
当应用程序预定地址空间的一块区域时,系统会确保区域的起始地址正好是系统页面 大小的整数倍。页面是一个内存单元,系统通过它来管理内存。与分配粒度相似,页面大小会根据不同的CPU而有所不同。x86和x64系统使用的页面大小为4KB,IA-64系统使用的页面大小为8KB。
(即当我们预定地址空间时,分配粒度影响我们的起始地址,因为起始地址必须整除分配粒度。页面大小影响我们预定的大小,大于等于我们预定的大小,因为要是页面大小的整数倍。)
虽然系统规定应用程序在预定地址空间区域时起始地址必须是分配粒度的整数倍,但系统自己却不存在这样的限制。非常有可能出现的情况是,系统预定的区域的起始地址并非64KB的整数倍,但是预定的区域仍然必须是CPU页面大小的整数倍。
当程序不再需要访问所预定的地址空间区域时,应该释放该区域。通过调用VirtualFree函数来完成。
4.给区域调拨物理存储器
为了使用所预定的地址空间区域,我们还必须分配物理存储器,并将存储器映射到所预定的区域。这个过程称为调拨(committing)物理存储器。物理存储器始终都以页面为单位来调拨。我们通过调用VirtualAlloc函数来讲物理存储器调拨给所预定的区域。
当程序不再需要访问所预定区域中已调拨的物理存储器时,应该释放物理存储器。这个过程称为撤销调拨(decommitting)物理存储器,通过调用VirtualFree函数来完成。
( VirtualAlloc, VirtualFree具体使用在以后章节具体讨论 )
说明:预定只是在虚拟地址空间中预定区域,这时并未为该区域指定物理地址空间。通过调拨操作,我们将之前预定的虚拟地址空间绑定到特定的物理地址空间。
5、物理存储器和页交换文件
在老式的操作系统中,物理存储器被认为是极其中的内存的总量。当今的操作系统能让磁盘空间看起来像内存一样。磁盘上的文件一般被称为页交换文件 (paging file),其中包含虚拟内存,可供任何进程使用。
为了能够使用虚拟内存,操作系统需要CPU的大力协助。当线程试图访问存储器中的一个字节时,CPU必须知道该字节是在内存中还是在磁盘上。如果一台机器 准备了1GB的内存,硬盘上还有1GB的也交换文件,那么有用程序会认为可用内存的总量为2GB。当然,这台机器实际上并没有准备2GB的内存。实际上, 是操作系统与CPU分工协作,把内存中的一部分保存到也交换文件中,并在应用程序需要的时候再将页交换文件中的对应部分载入内存。因此,使用也交换文件可 以增大应用程序可用内存的总量。最好把物理存储器看成是保存在磁盘上的也交换文件中的数据。
当一个线程试图访问所属进程的地址空间中的一块数据时,有可能会出现两种情况。图1显示了经简化后的流程:
系统需要在内存和也交换文件之间复制页面的频率越高,硬盘颠簸(thrash)得越厉害,系统运行得也越慢。
要让计算机跑的快,最好是增加内存。
不在页交换文件 中维护的物理存储器:
如 果每次运行一个程序时,系统都必须为该进程的代码和数据预定地址空间区域,为这些区域调拨物理存储器,然后把硬盘上的程序文件中的代码和数据复制到页交换 文件中已调拨的物理存储器中去。那么载入一个程序并让它运行起来会花费很长的时间。事实上,系统并不会执行刚才所说的这些操作。当用户要求执行一个应用程序时,系统会打开该应用程序对应的.exe文件并计算出应用程序的代码和数据的大小。然后系统会预定一块地址空间,并注明与该区域相关联的物理存储器就是.exe文件本身。 是的,系统并没有从页交换文件中分配空间,而是将.exe文件的实际内容用作程序预定的地址空间区域。这样一来,不但载入程序非常快,而且页交换文件也可以保持一个合理的大小。
当把一个程序位于磁盘上的文件映像(即一个.exe或DLL文件)用作地址空间区域对应的物理存储器时,我们称这个文件映像为内存映像文件 (memory mapped file)。当载入一个.exe或DLL时,系统会自动预定地址空间区域并把文件映像映射到该区域。
6.页面保护属性
我们可以给每个已分配的物理存储页指定不同的页面保护属性。表2列出了所有页面保护属性:
保护属性
|
描述
|
---|---|
PAGE_NOACCESS |
试图读取页面、写入页面或执行页面中的代码将引发访问违规。 |
PAGE_READONLY |
试图写入页面或执行页面中的代码将引发访问违规。 |
PAGE_READWRITE |
试图执行页面中的代码将引发访问违规。 |
PAGE_EXECUTE |
试图读取页面或写入页面将引发访问违规。 |
PAGE_EXECUTE_READ |
试图写入页面将引发访问违规。 |
PAGE_EXECUTE_READWRITE |
对页面执行任何操作都不会引发访问违规 |
PAGE_WRITECOPY |
试图执行页面中的代码将引发访问违规。试图写入页面将使系统为进程 单独创建一份该页面的私有副本(以页交换文件为后备存储器) |
PAGE_EXECUTE_WRITECOPY |
对页面执行任何操作都不会引发访问违规。试图写入页面将使系统为 进程单独创建一份该页面的私有副本( 以页交换文件为后备存储器 ) |
写时复制:
PAGE_WRITECOPY 和 PAGE_EXECUTE_WRITECOPY 存在的目的是为了节省内存和页交换文件的使用。 Windows 支持一种机制,允许两个或两个以上的进程共享同一块存储器。让所有的应用程序共享系统的存储页极大地提升了系统的性能,但另一方面,这也要求所有的应用程 序实例只能读取其中的数据或是执行其中的代码。如果某个应用程序实例修改并写入一个存储页,那么这等于是修改了其它实例正在使用的存储页,最终将导致混 乱。
为了避免此类混乱的发生,操作系统会给共享的存储页指定写时复制属性 。当系统把一个.exe或.dll映射到一个地址空间的时候,系统会计算有多少页面是可写的。(通常,包含代码的页面被标记为 PAGE_EXECUTE_WRITECOPY, 而包含数据的页面被标记为 PAGE_WRITECOPY )
具体通过两个图说明:
7.地址空间映射实例
基地址 |
类型 |
大小 |
块数 |
保护属性 |
描述 |
---|---|---|---|---|---|
00000000 |
Free |
65536 |
|||
00010000 |
Mapped |
65536 |
1 |
-RW- |
|
00020000 |
Private |
4096 |
1 |
-RW- |
|
00021000 |
Free |
61440 |
|||
00030000 |
Private |
1048576 |
3 |
-RW- |
Thread Stack |
00130000 |
Mapped |
16384 |
1 |
-R-- |
|
00134000 |
Free |
49152 |
|||
00140000 |
Mapped |
12288 |
1 |
-R-- |
|
00143000 |
Free |
53248 |
|||
00150000 |
Mapped |
819200 |
4 |
-R-- |
|
00218000 |
Free |
32768 |
|||
00220000 |
Mapped |
1060864 |
1 |
-R-- |
|
00323000 |
Free |
53248 |
|||
00330000 |
Private |
4096 |
1 |
-RW- |
|
00331000 |
Free |
61440 |
|||
00340000 |
Mapped |
20480 |
1 |
-RWC |
/Device/HarddiskVolume1/Windows/System32/en-US/user32.dll.mui |
00345000 |
Free |
45056 |
|||
00350000 |
Mapped |
8192 |
1 |
-R-- |
|
00352000 |
Free |
57344 |
|||
00360000 |
Mapped |
4096 |
1 |
-RW- |
|
00361000 |
Free |
61440 |
|||
00370000 |
Mapped |
8192 |
1 |
-R-- |
|
00372000 |
Free |
450560 |
|||
003E0000 |
Private |
65536 |
2 |
-RW- |
|
003F0000 |
Free |
65536 |
|||
00400000 |
Image |
126976 |
7 |
ERWC |
C:/Apps/14 VMMap.exe |
0041F000 |
Free |
4096 |
|||
00420000 |
Mapped |
720896 |
1 |
-R-- |
|
004D0000 |
Free |
458752 |
|||
00540000 |
Private |
65536 |
2 |
-RW- |
|
00550000 |
Free |
196608 |
|||
00580000 |
Private |
65536 |
2 |
-RW- |
|
00590000 |
Free |
196608 |
|||
005C0000 |
Private |
65536 |
2 |
-RW- |
|
005D0000 |
Free |
262144 |
|||
00610000 |
Private |
1048576 |
2 |
-RW- |
|
00710000 |
Mapped |
3661824 |
1 |
-R-- |
/Device/HarddiskVolume1/Windows/System32/locale.nls |
00A8E000 |
Free |
8192 |
|||
00A90000 |
Mapped |
3145728 |
2 |
-R-- |
|
00D90000 |
Mapped |
3661824 |
1 |
-R-- |
/Device/HarddiskVolume1/Windows/System32/locale.nls |
0110E000 |
Free |
8192 |
|||
01110000 |
Private |
1048576 |
2 |
-RW- |
|
01210000 |
Private |
524288 |
2 |
-RW- |
|
01290000 |
Free |
65536 |
|||
012A0000 |
Private |
262144 |
2 |
-RW- |
|
012E0000 |
Free |
1179648 |
|||
01400000 |
Mapped |
2097152 |
1 |
-R-- |
|
01600000 |
Mapped |
4194304 |
1 |
-R-- |
|
01A00000 |
Free |
1900544 |
|||
01BD0000 |
Private |
65536 |
2 |
-RW- |
|
01BE0000 |
Mapped |
4194304 |
1 |
-R-- |
|
01FE0000 |
Free |
235012096 |
|||
739B0000 |
Image |
634880 |
9 |
ERWC |
C:/Windows/WinSxS/x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727. 312_none_10b2ee7b9bffc2c7/MSVCR80.dll |
73A4B000 |
Free |
24072192 |
|||
75140000 |
Image |
1654784 |
7 |
ERWC |
C:/Windows/WinSxS/x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.6000.16386_none_5d07289e07e1d100/ comctl32.dll |
752D4000 |
Free |
1490944 |
|||
75440000 |
Image |
258048 |
5 |
ERWC |
C:/Windows/system32/uxtheme.dll |
7547F000 |
Free |
15208448 |
|||
76300000 |
Image |
28672 |
4 |
ERWC |
C:/Windows/system32/PSAPI.dll |
76307000 |
Free |
626688 |
|||
763A0000 |
Image |
512000 |
7 |
ERWC |
C:/Windows/system32/USP10.dll |
7641D000 |
Free |
12288 |
|||
76420000 |
Image |
307200 |
5 |
ERWC |
C:/Windows/system32/GDI32.dll |
7646B000 |
Free |
20480 |
|||
76470000 |
Image |
36864 |
4 |
ERWC |
C:/Windows/system32/LPK.dll |
76479000 |
Free |
552960 |
|||
76500000 |
Image |
348160 |
4 |
ERWC |
C:/Windows/system32/SHLWAPI.dll |
76555000 |
Free |
1880064 |
|||
76720000 |
Image |
696320 |
7 |
ERWC |
C:/Windows/system32/msvcrt.dll |
767CA000 |
Free |
24576 |
|||
767D0000 |
Image |
122880 |
4 |
ERWC |
C:/Windows/system32/IMM32.dll |
767EE000 |
Free |
8192 |
|||
767F0000 |
Image |
647168 |
5 |
ERWC |
C:/Windows/system32/USER32.dll |
7688E000 |
Free |
8192 |
|||
76890000 |
Image |
815104 |
4 |
ERWC |
C:/Windows/system32/MSCTF.dll |
76957000 |
Free |
36864 |
|||
76960000 |
Image |
573440 |
4 |
ERWC |
C:/Windows/system32/OLEAUT32.dll |
769EC000 |
Free |
868352 |
|||
76AC0000 |
Image |
798720 |
4 |
ERWC |
C:/Windows/system32/RPCRT4.dll |
76B83000 |
Free |
2215936 |
|||
76DA0000 |
Image |
884736 |
5 |
ERWC |
C:/Windows/system32/kernel32.dll |
76E78000 |
Free |
32768 |
|||
76E80000 |
Image |
1327104 |
5 |
ERWC |
C:/Windows/system32/ole32.dll |
76FC4000 |
Free |
11649024 |
|||
77AE0000 |
Image |
1171456 |
9 |
ERWC |
C:/Windows/system32/ntdll.dll |
77BFE000 |
Free |
8192 |
|||
77C00000 |
Image |
782336 |
7 |
ERWC |
C:/Windows/system32/ADVAPI32.dll |
77CBF000 |
Free |
128126976 |
|||
7F6F0000 |
Mapped |
1048576 |
2 |
-R-- |
|
7F7F0000 |
Free |
8126464 |
|||
7FFB0000 |
Mapped |
143360 |
1 |
-R-- |
|
7FFD3000 |
Free |
4096 |
|||
7FFD4000 |
Private |
4096 |
1 |
-RW- |
|
7FFD5000 |
Free |
40960 |
|||
7FFDF000 |
Private |
4096 |
1 |
-RW- |
|
7FFE0000 |
Private |
65536 |
2 |
-R-- |
8.数据对齐的重要性
相 对于操作系统的内存体系结构,数据对齐更多的是CPU体系结构的一部分。只有当访问已对齐的数据时,CPU的执行效率才最高。把数据的地址模除数据的大 小,如果结果为0,那么数据就是对齐的。如果CPU要访问的数据没有对齐,那么会有两种可能。第一种可能是CPU会引发一个异常,另一种可能是CPU会通 过多次访问已对齐的内存,来取得整个错位数据。
x86系统,一旦程序试图访问错位数据,CPU会自动执行必要的操作来访问错位数据。为了得到最佳的应用程序性能,我们在编写代码时应该尽量让数据对齐。因为与访问已对齐的数据相比,系统在最好的情况下页需要花两倍的时间来访问错位数据,而且情况可能会更糟。