什么是虚拟地址空间?
虚拟地址空间就是每个程序在运行起来之后所独占的内存空间,也就是进程自己的地址空间。
虚拟地址空间的大小由地址总线的宽度也就是计算机的字长决定:
对于 32 位系统,进程的虚拟地址空间大小为:
$$ 2^{32} bit = 4^{30} Byte = 4 GiB $$
对于 64 位系统,进程的虚拟地址空间大小为: $$ 2^{64}bit = 16^{30} GiB = 16 ^{20} TiB = 16^{10} PiB= 16 EiB $$
不过理论是理论,实际是实际。
- 对于 32 位的
linux
系统而言,操作系统占用了空间中上面的 1GiB(从0xC0000000
到0xFFFFFFFF
),程序可以使用的虚拟空间原则上只有 3GiB(从0x00000000
到0xBFFFFFFF
),对于 64 位的 OS 跟进程各自占用 128T 的空间,分别在最高处和最低处。 - 对于 32 位的
windows
系统而言,操作系统 2GiB,程序 2GiB(不过windows
系统可以设置启动参数来将 OS 占用的虚拟地址空间大小缩小到 1GiB).
进程的虚拟地址空间用于存放进程运行所必不可少的数据,内存地址从低到高生长,各个区域分别为:
- 代码段(.text):程序代码段
- 数据段(.data):已初始化的静态常量、全局变量
- BSS 段(.bss):未初始化的静态变量、全局变量
- 堆:动态分配的内存,从低地址开始向上增长;
- 文件映射段:动态库、共享内存等,从高地址开始向下增长;
- 栈:局部变量和函数调用的上下文等。栈的大小是固定的,一般是
8 MB
,从高地址开始向下增长。
为什么需要虚拟地址空间?
虚拟地址空间其实是一种应对多进程环境下的策略,这种对程序员透明的抽象方式可以使每个进程都无法感知到其他进程的存在,让各个进程之间的内存空间相互隔离,程序员也无需关心进程运行的物理地址的事情,极大地降低了程序员的心智负担。
32 位的机器,程序使用的空间大小能超过 4GiB 吗?
如果指的是虚拟地址空间,那么答案是“否”。因为 32 位的 CPU 只能使用 32 位的指针,最大的寻址范围就到 4GiB。
如果指的是计算机的内存空间,答案为“是”。Intel 从 95 年推出的 Pentium Pro CPU 开始采用 36 位的物理地址,可以访问达 64GiB 的物理内存。同时,Intel 修改了页映射的方式,使得新的映射方式Physical Address Extension, PAE可以访问到更多的物理内存。
在
windows
下,进程可以拿一段连续的内存地址作为窗口,然后从高于 4GiB 的物理空间中申请多个大小等于窗口大小的物理空间并进行编号 A, B, C 等,用到哪部分就把窗口映射到哪部分。这一操作也叫做AWE(Address Windowing Extensions)。在
linux
下则使用mmap
系统调用来实现。mmap
系统调用的主要作用是使进程之间通过映射同一个普通文件来实现共享内存(IPC)。普通文件被映射到地址空间之后,进程可以像访问普通内存一样对文件进行访问,而不需要调用write
,read
函数。mmap
本质上并不分配空间,只是将文件映射到进程地址空间(当然,会占掉虚拟内存空间),映射成功后就可以直接用memcpy
等操作来写文件,因而用户对这段内存区域的修改就可以直接反映到内核空间(当然反过来也一样)。void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
对映射空间所写的内容并不会立刻更新到文件中,而是有一段时间的延迟,内核会挑个时间进行写入操作。如果需要即使写入可以调用
msync
来强制同步。