The*_*ght 12
用户通常会遇到三种类型的 ELF 文件——.o 文件、常规可执行文件和共享库。虽然所有这些文件都有不同的用途,但它们的内部结构文件非常相似。
所有不同 ELF 文件类型(以及 a.out 和许多其他可执行文件格式)中的一个通用概念是节的概念。节是相似类型信息的集合。每个部分代表文件的一部分。例如,可执行代码总是放在称为.text的部分中;用户初始化的所有数据变量都放在名为.data的部分中;未初始化的数据被放置在称为.bss的部分中。
实际上,可以设计一种可执行文件格式,其中所有内容都混杂在一起(如MS DOS)。但是将可执行文件分成多个部分具有重要的优势。例如,一旦您将可执行文件的可执行部分加载到内存中,这些内存位置就不需要更改。在现代机器架构上,内存管理器可以将内存的一部分标记为只读,这样任何修改只读内存位置的尝试都会导致程序死亡并转储核心。因此,不是仅仅说我们不希望特定的内存位置发生变化,我们可以指定任何修改只读内存位置的尝试都是一个致命错误,表明应用程序中存在错误。话虽如此,通常您不能单独设置每个内存字节的只读状态——相反,您可以单独设置称为页面的内存区域的保护。
鉴于我们希望可执行文件的所有可执行部分都在只读内存中,而所有可修改的内存位置(例如变量)都在可写内存中,结果证明将可执行文件的所有可执行部分归为一个部分是最有效的内存(.text 部分),以及所有可修改的数据区域一起进入另一个内存区域(以下称为.data 部分)。
用户已初始化的数据变量和用户未初始化的数据变量之间有进一步的区别。如果用户没有指定变量的初始值,那么浪费可执行文件中的空间来存储该值是没有意义的。因此,初始化的变量被分组到 .data 部分,未初始化的变量被分组到 .bss 部分,这是特殊的,因为它不占用文件中的空间——它只告诉未初始化的变量需要多少空间。
当您要求内核加载和运行一个可执行文件时,它首先查看图像标头以获取有关如何加载图像的线索。它在可执行文件中定位 .text 部分,将其加载到内存的适当部分,并将这些页面标记为只读。然后它在可执行文件中定位 .data 部分并将其加载到用户的地址空间中,这次是在读写内存中。最后,它从图像头中找到 .bss 部分的位置和大小,并将适当的内存页添加到用户的地址空间。即使用户没有指定放置在 .bss 中的变量的初始值,按照惯例,内核会将所有这些内存初始化为零。
所以你看,实际上是内核发出了将可执行文件加载到内存中的命令。作为任何此类调用的结果的文本部分被加载到只读存储器中,而数据部分被加载到读写存储器中。