视频字幕
在Linux系统中,可执行文件的加载过程是由内核通过execve系统调用完成的。这个过程始于用户在命令行输入命令或通过图形界面点击图标。当用户执行一个程序时,Shell或父进程会接收这个请求,然后调用execve系统调用,将控制权交给内核。内核负责将可执行文件加载到内存中并开始执行。
当内核接收到execve系统调用后,会开始解析可执行文件。在Linux中,可执行文件通常采用ELF格式。内核首先读取ELF文件头部,识别文件类型并验证其有效性。然后解析Program Header Table,了解各个段在文件中的位置和它们应该被加载到内存中的虚拟地址。接下来,内核为新进程创建虚拟地址空间,并使用mmap系统调用将可执行文件的各个段映射到指定位置。代码段通常被标记为只读和可执行,数据段被标记为可读写,BSS段(未初始化的全局变量)被创建并清零。此外,内核还会为堆和栈分配内存区域。这种映射通常是惰性的,只有当程序实际访问某个地址时,对应的文件页才会被加载到物理内存中。
如果可执行文件是动态链接的,即依赖于共享库,内核会首先加载动态链接器,通常是/lib/ld-linux.so。动态链接器本身也是一个共享库,它会被映射到进程的虚拟地址空间中。内核将控制权转移给动态链接器后,链接器开始工作。它首先查找程序所需的所有共享库,如libc.so、libm.so等,并将它们映射到进程的虚拟地址空间。然后,链接器进行符号解析和重定位工作。符号解析是指找到程序中引用的外部函数和变量在共享库中的实际位置。重定位则是修改程序中的代码,使其能够正确地调用或访问这些外部符号。完成这些工作后,动态链接器会执行各个共享库的初始化代码,最后将控制权转移给程序的真正入口点,开始执行用户程序。
完成动态链接后,内核将CPU的指令指针设置为程序的入口点,开始执行程序指令。Linux采用惰性加载或按需分页机制,这意味着程序的代码和数据并不会一次性全部加载到物理内存中,而是建立虚拟地址到文件的映射关系。当程序首次访问某个虚拟地址时,会触发缺页中断,此时内核才会将对应的页面从磁盘加载到物理内存中。这种机制大大提高了内存使用效率,使得程序启动更快,内存占用更少。此外,Linux还采用了内存共享机制,多个进程可以共享同一个物理内存页,特别是对于共享库的代码段。写时复制是另一个重要的优化技术,当多个进程共享同一个物理页面时,只有当某个进程需要修改该页面内容时,才会创建该页面的副本,这在fork系统调用中特别有用。
总结一下Linux中可执行文件的加载过程:首先,用户通过命令行或图形界面发起程序执行请求,Shell或父进程调用execve系统调用。内核接管后,解析ELF文件头,识别文件类型并验证其有效性。然后,内核为新进程创建虚拟地址空间,并将可执行文件的各个段映射到指定位置,同时设置适当的内存访问权限。对于动态链接的程序,内核会加载动态链接器,链接器负责查找并加载所需的共享库,进行符号解析和重定位。Linux采用惰性加载和写时复制等机制优化内存使用,只有当程序实际访问某个地址时,对应的页面才会被加载到物理内存中。最后,内核设置程序入口点,开始执行用户程序。这整个过程体现了Linux内存管理的高效性和灵活性,使得程序能够快速启动并高效运行。