一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
地址空间
早期的操作系统进程数量很少,为内存管理设计的机制和策略比较少,之后多进程和分时系统的需求越来越高涨,如何在有限的内存上让几百上千的进程互相不干扰的运行,同时保持高效,成为了亟待解决的问题,解决这一问题的一系列技术就是内存的虚拟化技术。
内存虚拟化的第一个关键概念是地址空间(address space) ,简单来说它就是内存这一概念在操作系统中的虚拟化。进程的地址空间(内存)中包含运行程序的所有内存状态,总结一下的话,就是代码、堆栈和堆(code、stack、heap)
以一个单线程的进程为例,操作系统为其分配的地址空间中包含了:
- 代码,也就是由代码编译而来形成的一条条指令;
- 堆栈,包含跟踪函数调用链的堆栈、参数、临时变量、返回值;
- 堆,由指令申请的空间,包含数据和主动分配的空间如malloc、new等
对于程序来说,这一块地址空间中的内存就是它可以调用的所有内存,从地址0开始,一直可以索引到最后。但实际上,真实的内存调用过程是:在一些重要硬件的帮助下,操作系统获取每个进程的内存引用(索引),并将它们映射到实际的物理地址上。
通过这项技术,操作系统给每一个用户进程都提供了一个大的(后面会说具体有多大)、稀疏的、私有的地址空间的错觉,这一技术是世界上所有现代计算机的基础。
为了达到现代操作系统的性能要求,虚拟化内存技术有三大目标
- 透明性:进程不应该知道内存是虚拟的,相反,要像真的有那么多内存一样让进程自由分配
- 高效率:虚拟化内存应该要快,不能让程序运行的更慢,应该要实现简单,不需要为支持虚拟化而使用太多内存
- 保护:进程之间、进程与操作系统之间的内存应当收到保护,不能相互覆盖
要实现这些目标,我们将会用到的方法有三个方面,必要的硬件支持、大量的低级机制、一些关键(高级)方法,这些都是之后需要了解的内容
在Linux中,我们可以使用free命令来查看内存的使用情况,free命令产生内存使用表,利用man(manual操作面板)命令来查看free的使用描述和表头含义
实例
因为这一技术过于重要,我们用一个例子来帮助理解地址空间这一概念。
用过C语言的人都知道,我们经常能够看到一串串很长的十六进制码,那其实就是当前程序在运行时在它的地址空间中的一个虚拟地址,作为(非内核开发的)程序员,我们不管用任何语言能够看到的地址都是虚拟地址,只有操作系统才知道这些虚拟地址实际指向的物理地址
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
printf("location of code : %p\n", main);
printf("location of heap : %p\n", malloc(100e6)); int x = 3;
printf("location of stack: %p\n", &x); return x;
}
上面这个程序能够打印出main()例程的位置(代码所在的位置),malloc()返回的堆分配值的值,以及一个整数在堆栈上的位置。打印结果如下
location of code : 0x1095afe50
location of heap : 0x1096008c0
location of stack: 0x7fff691aea64
可以看到代码首先在地址空间的上段,堆(heap)也是,而堆栈(stack)一直在这个巨大的地址空间(32位OS会给一个进程4GB的地址空间)