Linux虚拟地址空间布局( 四 )


2) 当程序读取数据段的数据时,系统会出发缺页故障,从而分配相应的物理内存;当程序读取BSS段的数据时,内核会将其转到一个全零页面,不会发生缺页故障,也不会为其分配相应的物理内存 。
运行时数据段和BSS段的整个区段通常称为数据区 。某些资料中“数据段”指代数据段 + BSS段 + 堆 。
 7 代码段(text)代码段也称正文段或文本段,通常用于存放程序执行代码(即CPU执行的机器指令) 。一般C语言执行语句都编译成机器代码保存在代码段 。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可 。代码段通常属于只读,以防止其他程序意外地修改其指令(对该段的写操作将导致段错误) 。某些架构也允许代码段为可写,即允许修改程序 。
代码段指令根据程序设计流程依次执行,对于顺序指令,只会执行一次(每个进程);若有反复,则需使用跳转指令;若进行递归,则需要借助栈来实现 。
代码段指令中包括操作码和操作对象(或对象地址引用) 。若操作对象是立即数(具体数值),将直接包含在代码中;若是局部数据,将在栈区分配空间,然后引用该数据地址;若位于BSS段和数据段,同样引用该数据地址 。
代码段最容易受优化措施影响 。
 8 保留区位于虚拟地址空间的最低部分,未赋予物理地址 。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况 。
它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称 。大多数操作系统中,极小的地址通常都是不允许访问的,如NULL 。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据 。
在32位X86架构的Linux系统中,用户进程可执行程序一般从虚拟地址空间0x08048000开始加载 。该加载地址由ELF文件头决定,可通过自定义链接器脚本覆盖链接器默认配置,进而修改加载地址 。0x08048000以下的地址空间通常由C动态链接库、动态加载器ld.so和内核VDSO(内核提供的虚拟共享库)等占用 。通过使用mmap系统调用,可访问0x08048000以下的地址空间 。
通过cat /proc/self/maps命令查看加载表如下:

Linux虚拟地址空间布局

文章插图
 
【扩展阅读】分段的好处
进程运行过程中,代码指令根据流程依次执行,只需访问一次(当然跳转和递归可能使代码执行多次);而数据(数据段和BSS段)通常需要访问多次,因此单独开辟空间以方便访问和节约空间 。具体解释如下:
当程序被装载后,数据和指令分别映射到两个虚存区域 。数据区对于进程而言可读写,而指令区对于进程只读 。两区的权限可分别设置为可读写和只读 。以防止程序指令被有意或无意地改写 。
现代CPU具有极为强大的缓存(Cache)体系,程序必须尽量提高缓存命中率 。指令区和数据区的分离有利于提高程序的局部性 。现代CPU一般数据缓存和指令缓存分离,故程序的指令和数据分开存放有利于提高CPU缓存命中率 。
当系统中运行多个该程序的副本时,其指令相同,故内存中只须保存一份该程序的指令部分 。若系统中运行数百进程,通过共享指令将节省大量空间(尤其对于有动态链接的系统) 。其他只读数据如程序里的图标、图片、文本等资源也可共享 。而每个副本进程的数据区域不同,它们是进程私有的 。
此外,临时数据及需要再次使用的代码在运行时放入栈区中,生命周期短 。全局数据和静态数据可能在整个程序执行过程中都需要访问,因此单独存储管理 。堆区由用户自由分配,以便管理 。

【Linux虚拟地址空间布局】


推荐阅读