深入理解进程(一)——虚拟地址空间
一、为什么要有虚拟地址空间?
在谈论虚拟地址空间之前,大家要知道一个常识:程序在运行之前是要全部加载到内存中的。
假如现在有两个程序,一个大小为4M,一个大小为8M,而我们的内存大小为16M。首先我们运行4M大小的程序,将其加载到内存中,然后再运行8M大小的程序,同样将其加载到内存中,如下图所示:
那么这个时候我们还剩余4M的空间,此时如果我们想要再加载一个6M大小的程序,那么肯定就加载不了,因为内存不够,但是如果这个程序的优先级很高,那么操作系统就会把4M的程序从内存中拿出来,如下图所示:
这个时候内存剩余空间大小为8M,如果仅仅从空间大小来看,那么是完全可以加载6M大小的程序。而实际上,却是无法加载的,因为程序不可分割,要么有足够且连续的空间将6M大小的程序一次性全部加载完毕,要么不加载。所以我们发现这样的内存管理的效率太低,并且浪费了大量的空间,所以才会有之后的虚拟地址空间。
二、什么是虚拟地址空间
所谓的虚拟地址空间,大家从它的名字就可以听出来,这个空间实际上不存在的,也就是说,它并不像我们所说的物理空间一样是现实存在的。大家可以这样理解,一天同学A告诉同学B需要100平方米的地去种小麦,然后B去找地,可是并没有找到连续的100平方米的地,但是他找到了两块50平方米的地,只不过两块地不在一起。于是B就对A撒谎,说我找到了一块100平方米的地,A也是心大,也不去证实,并且让B帮忙在地上种下小麦,来年丰收,两位同学都很开心。这就是虚拟地址空间,B并没有找到连续的100平方米的地,于是用两块50平方米的去欺骗A,虽然欺骗了A,但是最终结果与预期的一样,这就行了。
现在来理解虚拟地址空间,操作系统为每一个进程都会设置属于自己的虚拟地址空间,换句话说就是,操作系统欺骗了进程,将本来间断的内存空间通过一个虚拟地址空间让进程相信自己所加载的程序处于一个连续的空间之中。也可以理解为,操作系统将一个进程划分为多个块,每个块不需要连续位于内存中,这些块再通过一种映射关系映射到实际的物理地址上。进程访问的都是虚拟地址,而操作系统再通过虚拟地址来寻找实际的物理地址,这就是虚拟地址空间。
三、Linux下虚拟地址空间的布局
内核空间:
内核总是驻留在内存中,是操作系统的一部分。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
栈:
由编译器自动分配释放,行为类似数据结构中的栈(先进后出),它是向下增长的,也就是说向低地址增长
局部变量、函数参数、返回地址等
共享映射段:
装载动态共享库,如C标准库函数(fread、fwrite、fopen等)和Linux系统I/O函数
堆:
堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。它是向上增长的。
BSS:
未初始化或初值为0的全局变量和静态局部变量
已初始化全局变量:
已初始化且初值非0的全局变量和静态局部变量
代码段:
可执行代码、字符串字面值、只读变量
我将在下一节讲述虚拟地址是如何转换为物理地址的