进程地址空间
- 📖1. 地址空间概念
- 📖2. 写时拷贝
- 📖3. 虚拟地址空间的优点
📖1. 地址空间概念
在学习C/C++内存管理时,我们可能见过这样一幅图:
但是我们可能不是很理解它,首先有一个问题:这段空间指的是内存吗?
首先,这段空间它并不是内存,它是操作系统提供的一个易用的物理内存抽象,叫做地址空间. 通过这个物理内存抽象,我们可以让每个进程都认为它有这么一段供自己使用的空间,但是它并不是真实的物理空间,那么真实的物理空间在哪里呢?
我们知道,数据实际上肯定是存储在物理内存中的,所以当我们拿到虚拟地址的时候,需要将它转换为物理地址供进程使用,所以,我们需要通过页表来完成从虚拟地址到物理地址的映射,当我们拿到一个虚拟地址时,便可以通过页表和MMU
中的地址翻译硬件将它转化为实际的物理地址.
对于上面的地址空间的布局图,我们可以写出代码来验证一下:
运行结果:
可以看出,堆和栈之间是有一段比较大的地址镂空的.
再接着,我们通过代码来观察一个现象:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int g_val = 10;
int main()
{
pid_t id = fork();
if(id == 0)
{
int flag = 0;
while(1)
{
printf("我是子进程,%d, ppid:%d, g_val:%d, &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
flag++;
if(flag == 5)
{
g_val = 20;
printf("我是子进程,g_val已经被我修改\n");
}
}
}
else
{
while(1)
{
printf("我是父进程,%d, ppid:%d, g_val:%d, &g_val:%p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
return 0;
}
运行结果如下:
我们可以看到,在未修改g_val
之前,父进程和子进程的g_val
值相同,并且地址也相同,那么我们可能会想,子进程与父进程用的是同一地址处的同一份数据,但是当g_val
的值被子进程修改之后,我们惊奇的发现:父进程和子进程中的g_val
地址是相同的,但是值却不一样,这怎么可能?
所以,由这里我们就能看出,我们看到的地址,并不是真实的,它是虚拟地址,那么,接下来,我们就一起来看一看进程地址空间:
每一个进程在启动的时候,操作系统都会给它创建一个地址空间,该地址空间就是虚拟地址空间.
Linux的虚拟地址空间,对于32位处理器,虚拟内存空间为4G
,每个进程都认为自己拥有4G
的空间,实际上,在虚拟内存对应的物理内存上,可能只对应一点点的物理内存.
进程得到的这4G
的虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理碎片,还有一部分存储在外部磁盘上,在需要时进行数据交换.
这也是内存管理中的虚拟内存技术:OS为每个程序分配地址(逻辑地址),每个地址由多个页组成,这些页可以不在真正的物理地址中,而是被映射到了物理地址中,也不需要被映射到连续的物理地址中,当程序引用到不在物理地址中的页时,硬件执行操作将缺失的页装入物理内存.
📖2. 写时拷贝
此时我们便可以解释上述代码中发生的现象:
我们将这种行为称为写时拷贝:等到修改数据时才分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,目的是避免不必要的内存拷贝
📖3. 虚拟地址空间的优点
- 对内存提供保护
物理内存本身是不受限制访问的,任何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限,这是利用
CPU
模式MMU
的内存保护机制实现的。例如,Text Segment
被只读保护起来,防止被错误的指令意外改写,内核地址空间也被保护起来,防止在用户模式下执行错误的指令意外改写内核数据。这样,执行错误指令或恶意代码的破坏能力受到了限制,顶多使当前进程因段错误终止,而不会影响整个系统的稳定性
- 让每个进程都拥有独立的地址空间
所谓独立的地址空间是指,不同进程中的同一个
VA
被MMU
映射到不同的PA
,并且在某一个进程中访问任何地址都不可能访问到另外一个进程的数据,这样使得任何一个进程由于执行错误指令或恶意代码导致的非法内存访问都不会意外改写其它进程的数据,不会影响其它进程的运行,从而保证整个系统的稳定性。另一方面,每个进程都认为自己独占整个虚拟地址空间,这样链接器和加载器的实现会比较容易,不必考虑各进程的地址范围是否冲突
- 虚拟地址到物理地址的映射给分配和释放内存带来方便
物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存。比如要用
malloc
分配一块很大的内存空间,虽然有足够多的空闲物理内存,却没有足够大的连续空闲内存,这时就可以分配多个不连续的物理页面而映射到连续的虚拟地址范围
- 一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理使得这种情况下各进程仍然能够正常运行
因为各进程分配的只不过是虚拟内存的页面,这些页面的数据可以映射到物理页面,也可以临时保存到磁盘上而不占用物理页面,在磁盘上临时保存虚拟内存页面的可能是一个磁盘分区,也可能是一个磁盘文件,称为交换设备
(Swap Device)
。当物理内存不够用时,将一些不常用的物理页面中的数据临时保存到交换设备,然后这个物理页面就认为是空闲的了,可以重新分配给进程使用,这个过程称为换出(Pageout
)。如果进程要用到被换出的页面,就从交换设备再加载回物理内存,这称为换入(Pagein)
。换出和换入操作统称为换页(Paging)