这些段主要是为程序加载器和操作系统提供方便的(尽管它们也为粗粒度保护提供了基础;执行权限可以限制在文本上,写入可以禁止从rodata)。
1
物理内存地址空间可能会被分割,但不是为了这些应用程序段。例如,在NUMA系统中,硬件可能方便地使用特定位来指示哪个节点拥有给定的物理地址。
对于使用地址转换的系统,操作系统可以在物理内存中任意放置段。(使用分段翻译时,外部碎片可能是一个问题;连续的物理内存地址范围可能不可用,需要昂贵的移动内存段。使用分页翻译,外部碎片是不可能的。分段翻译的优点是需要较少的翻译信息:每个段只需要一个基址和边界以及其他元数据,而内存部分通常有许多超过两个页面,每个页面都有一个基地址和元数据。)
没有地址转换,段的放置必然不太随意。幸运的是,大多数程序不关心段放置的具体地址。(单地址空间操作系统
(请注意,共享部分位于固定位置可能是方便的。对于代码,这可以用于避免通过全局偏移表进行间接引用,而无需在程序加载器/动态链接器中进行二进制重写。这也可以减少地址转换开销。)
应用级编程通常已经足够抽象化,使其存在不被注意到。但是,纯粹的抽象自然对物理资源使用的强烈优化,包括执行时间,不友好。
此外,编程系统可以选择使用更复杂的数据放置方式(应用程序员无需知道实现细节)。例如,使用协程可能鼓励使用仙人掌/意大利面条堆栈,其中不期望连续性。同样,垃圾收集运行时可能提供额外的地址空间划分,不仅用于保育院,还用于将叶对象(没有引用可回收内存)与非叶对象分离开来(减少标记/扫描的开销)。还不太寻常的是提供两个堆栈段,一个用于数据,其地址未被采取(或至少大小固定),另一个用于其他数据。
1类Unix操作系统中这些段的传统布局(向下增长堆栈)在平坦的虚拟地址空间中将文本放在最低地址处,rodata紧随其后,初始化数据紧随其后,零初始化数据(bss)紧随其后,堆从bss顶部向上增长,栈从应用程序的虚拟地址空间顶部向下增长。
使堆和栈相互增长允许每个(对于使用该地址空间的单个线程!)任意增长。这种放置还允许程序加载器简单地将程序文件复制到从最低地址开始的内存中,按权限分组内存,并有时允许单个全局指针寻址所有全局/静态数据范围(rodata、数据和bss)。