点我查看秘籍连载数组
Linux的虚拟地址空间采用“分段+分页”结合的方式实现。先看分段,以后再介绍分页。函数
分段是将内存划分红各个段落(Segment),每一个段落的长度能够不一样,且虚拟地址空间中未使用的空间不会映射到物理内存中,因此操做系统不会为这段空间分配物理内存。这样的话,内核为刚建立的进程分配的物理内存能够很小,随着进程运行不断使用内存,内核再为进程按需分配物理内存。也就是说,尽管地址空间的范围和物理内存大小同样,但不会将所有空间映射到物理内存。布局
对于Linux进程的虚拟地址空间来讲,它的内存布局以下图。优化
虚拟空间分了以下几个段:操作系统
从上面的描述大概也能推测出,除了堆内存外,其它段落空间都是自动填充分配的,用户没法控制这些内存的使用。而堆内存段是用户能使用的自由内存区,绝大多数程序的用户数据都丢在这里面,算是一个大杂烩空间。翻译
例如,下图中是一段C代码和内存布局之间的对应关系。设计
提示:其它语言的内存布局
上面的布局C程序的内存布局,也是Linux下进程的内存布局。其它语言(好比CPython)编写的程序运行起来后,只要是在Linux下运行,其进程的布局也会如此。只不过这些语言的程序中,全局变量、局部变量等可能和C的布局不同,这和各语言的底层设计有关。好比C编写的某动态语言,它不要求指定变量的数据类型,那么在加载到内存的时候天然不知道该变量类型所需的空间大小,当它转换成C后(尽管不会真的转换成C代码),这个变量只能丢进堆内存做为动态数据。3d
使用分段的好处就是“各段自扫门前雪”,虽然在地址空间中每一个分段的地址都是连续的,但实际上,每一个分段映射到物理内存地址时是独立的,段与段之间能够不连续。这是由于CPU为每一个段都使用一对(即两个)特殊的寄存器:基址寄存器和界限寄存器。blog
而界限寄存器中的值用来表示该段在物理内存中的大小,即已为该段分配了多少内存。当准备用虚拟地址加基址计算物理地址时,须要先根据界限寄存器中的值检查将要访问的物理内存地址是否超出了这个段的范围。若是超出了,则表示访问了不属于该段的内存,也即内存的越界访问,而用户进程是没有权限访问其它进程或未分配内存的地址的,这时会收到一个SIGSEGV(segmentation violation)信号并提示:Segmentation Fault,即段错误或段异常。收到这个信号后默认状况下会终止该进程,由于它访问了非法地址,可是能够设置该信号的信号处理程序,从而作出其它处理。进程
例如,下图中的进程访问了Kernel段或者unallocated memory部分的内存,都会报错。Kernel段除了内核进程,任何用户进程都没法访问,典型的地址是用户进程想要访问0x0地址时,而该地址属于Kernel,因此报错。而unallocated memory是还未分配的内存,界限寄存器会保护该段没法访问。
再例如,C数组的越界访问时也会出现该问题。下图直观地显示了在Windows中一样的内存越界错误。
也就是说,基址寄存器是用来转换地址的,界限寄存器是用来保护进程不越界访问内存的。CPU借助基址寄存器和界限寄存器管理并提供地址翻译和内存保护的功能,一般称为内存管理单元(Memory Management Unit,MMU)。
最后再说明一点,内存地址翻译的任务既能够由操做系统来作,也能够由硬件CPU来作。但若是彻底由操做系统来完成,就须要频繁地陷入到内核态,这样效率会很是低。因此,这项任务交给CPU硬件来完成,操做系统只需在必要的时候介入,好比分配内存、回收内存等。