在 ELF 可执行文件中,这被称为 "ELF 解释器"。在 Linux 上(例如),这是
/lib64/ld-linux-x86-64.so.2
。
这不是内核的一部分,通常使用 glibc 等。
当内核执行 ELF 可执行文件时,它必须将可执行文件映射到用户空间内存。然后从中查找一个特殊的子节,称为
INTERP
(其中包含一个字符串,即完整路径)。
然后内核将解释器映射到用户空间内存并将控制权转移给它。然后,解释器执行必要的链接/加载并启动程序。
因为 ELF 代表 "可扩展链接器格式",所以允许 ELF 文件具有许多不同的子节。
与其让内核了解所有各种扩展的负担,不如与文件配对的 ELF 解释器知道。
虽然通常在给定系统上只使用一种格式,但在系统上可以有几种不同变体的 ELF 文件,每个文件都有自己的 ELF 解释器。
这将允许(例如)在 Linux 系统上运行 BSD ELF 文件(带有其他调整/支持),因为 ELF 文件将指向 BSD ELF 解释器而不是 Linux 的。
更新:
每个进程(例如 vlc 播放器、Chrome)都有共享库 ld.so 作为其地址空间的一部分。
是的。我假设你正在查看
/proc/<pid>/maps
。这些是文件的映射(例如,类似于使用
mmap
)。这与 "加载" 有所不同,后者可以意味着符号链接。
因此,在将可执行文件(代码和数据)加载到内存后,主要是加载和映射动态链接器 (.so) 到其地址空间。
主要是内核将可执行文件(代码和数据)映射到内存后,把动态链接器(.so)映射到程序的地址空间中。
这基本上是正确的。内核还会映射其他内容,如bss段和堆栈。然后它将argc
、argv
和envp
[存放环境变量的空间]“推”到堆栈上。
接下来,通过读取文件的特殊部分确定了ld.so
的起始地址,并将其设置为恢复地址并启动线程。
到目前为止,一直是内核在做事情。内核几乎不进行符号链接。
现在,ld.so
接管了...
进一步加载共享库,映射和解析库的引用,然后调用入口函数(_start)
由于原始可执行文件(例如vlc
)已经映射到内存中,ld.so
可以查看它所需要的共享库的列表。它将这些库映射到内存中,但不一定立即链接符号。
映射很容易而且快速 - 只需一个mmap
调用。
可执行文件的起始地址[不要与ld.so
的起始地址混淆],取自ELF可执行文件的特殊部分。尽管与此起始地址相关联的符号传统上被称为_start
,但它实际上可以被命名为任何东西(例如__my_start
),因为在节数据中确定起始地址,而不是符号_start
的地址。
将符号引用与符号定义链接起来是一个耗时的过程。因此,直到实际使用该符号时,才会推迟这个过程。也就是说,如果程序有对 printf
的引用,链接器在第一次程序实际调用 printf
之前并不会尝试链接它。
这有时被称为“按需链接”或“按需链接”。在这里查看我的答案:“哪些段受复制写入的影响?”以获取更详细的解释,以及当可执行文件映射到用户空间时实际发生了什么。
如果你感兴趣,你可以运行 ldd /usr/bin/vlc
来获取它使用的共享库列表。如果你查看 readelf -a /usr/bin/vlc
的输出,你会看到这些相同的共享库。此外,你还可以获得ELF解释器的完整路径,并可以运行 readelf -a <full_path_to_interpreter>
并注意一些不同之处。你可以为任何 .so
文件重复此过程,vlc
需要这些文件。
将所有这些与 /proc/<pid>maps
等结合起来,可能有助于你的理解。
ld.so
不是内核的一部分,但由其加载。 - Eugene Sh.