操作系统在a.out文件里干了些什么
为什么a.out要以段的形式组织。段可以方便地映射到链接器在运行时可以直接载入的对象中!载入器只是取文件中的每个段的映像,并直接将它们放入到内存中。从本质上说,段在正在执行的程序中是一块内存区域,每个区域都有特定的目的。
a.out文件 进程的地址空间
最高内存地址
a.out神奇数字 堆栈段(函数的局部数据)
a.out的其他内容
BSS段所需的大小(1) 空洞
BSS段(未初始化的数据)(1)
数据段(2) 数据段(经过初始化的数据)(2)
经过初始化后的全局和静态变量
文本段(3) 文本段(指令)(3)
可执行文段的指令
未映射区域
最低内存地址
图6-2 可执行文件中的段在内存中如何布局
文本段包含程序的指令。链接器把指令直接从文件复制到内存中(一般调用mmap()系统调用),以后便再也不用管它。因为在典型情况下,程序的文本无论是内容还是大小都不会改变。有些操作系统和链接器甚至可以向段中不同的section赋予适当的属性。例如,文本可以被设置为read-and_execute-only(只允许读和执行),有些数据可以被设置成read-write-no-execute(允许读和写,但不允许执行)而另外一些数据则被设置成read-only(只读)等。
数据段包含经过初始化的全局和静态变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,并把它紧放在数据段之后。当这个内存区进入程序的地址空间后全部清零。包括数据段和BSS段的整个区段此时通常统称为数据区。这是因为在操作系统的内存管理术语中,段就是一片连续的虚拟地址,所以相邻的段被结合起来。一般情况下,在任何进程中数据段是最大的段。
我们需要一些内存空间,用于保存局部变量、临时数据、传递到函数中的参数等。堆栈段(stack segment)就是用于这个目的。我们还需要堆(heap)空间,用于动态分配的内存。只要调用malloc()函数,就可以根据需要在堆上分配内存。
注意虚拟地址的最低部分未被映射。也就是说,它位于进程的地址空间内,但并未赋予物理地址,所以任何对它的引用都是非法的。在典型情况下,它是从地址零开始的几K字节。它用于捕捉使用空指针和小整型值的指针引用内存情况。
当考虑共享库时,进程的地址空间的样子如图6-3所示。
最高地址
堆栈
堆栈限制
---堆栈限制
链接器
---另一块未映射的段
{数据
文本
数据
文本} ---共享库被映射到这里
---空洞(未映射区域)
{数据
文本} ---由正在执行的程序使用
---第0页未被映射
最低地址
图6-3 显示共享库的虚拟地址空间布局