进程的创建与可执行程序的加载-总结
根据前两篇博文中的实验:
现对进程的创建和可执行程序的加载过程总结如下:
一:进程地址空间的组成及相关数据结构
Linux为每个进程都维持了一个独立的虚拟地址空间,进程地址空间又被分为几个虚拟内存区域:代码段、数据段、堆段、栈段、共享库段。进程地址空间中的任何有效地址都只能位于唯一的区域,这些区域不能相互覆盖。通过mmap/munmap函数,内核可以创建/删除一个虚拟内存区域。
主要的数据结构有:
task_struct : 进程描述符结构,定义在<linux/sched.h>文件中,描述了该进程打开的文件、进程的地址空间、进程的状态等信息。
mm_struct :进程虚拟内存描述符,定义在<linux/sched.h>文件中,该结构包含了和进程地址空间相关的全部信息。
vm_area_struct:虚拟内存区域描述符,定义在<linux/mm.h>文件中,该结构体描述了指定地址空间内连续区间上的一个独立的内存范围(比如进程用户空间栈、代码段、数据段等)。
三个结构之间的关系:
task_struct 中的一个 字段 mm 指向当前进程的虚拟地址空间描述符 mm_struct , mm_struct中的字段 mmap 指向一个vm_area_struct组成的链表,其中每个vm_area_struct 都描述了当前虚拟地址空间的一个区域。
这里涉及的一些关键结构的具体信息可查看我前面的博文 :Linux进程地址空间之初探:一
二:fork 和 exec 函数及加载器的执行过程
fork( )函数通过拷贝当前进程创建一个子进程,子进程与父进程的区别仅仅在在于PID、PPID和某些资源和统计量(例如,挂起的信号,它没必要被继承),Linux中fork是通过clone( )实现的,clone( )再调用do_fork( )。
执行过程:
1)为新进程创建一个内核栈、thread_info结构和task_struct 结构,这些结构的值与当前进程的值相同。此时子进程和父进程的描述符是完全相同。
2)子进程开始使自己与父进程区别开来。进程描述符内的许多成员要被清零或设为初始值。
3)子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证不会透入运行。
4)设置task_struct中的相应flags标志,例如权限
5 ) 为新进程分配一个有效PID
6)根据传递给clone( )的参数标志进行拷贝或者共享打开的文件、文件系统信息、进程地址空间和命名空间等。
7)最后返回子进程的PID。
exec( )函数:exec函数在当前进程的上下文中加载并运行一个新的程序,只有在出错的情况下exec 函数才会返回到调用程序中,所以与fork函数调用一次返回两次不同,exec调用一次并从不返回。
exec( )执行过程:
1)删除当前进程虚拟地址空间的用户部门已经存在的区域结构。
2)加载可执行文件,用可执行文件中的内容覆盖当前进程地址空间相应区域
3)设置程序计数器即eip中的值,使它指向新的代码区的入口点,调用启动代码,启动代码设置栈,控制传给新程序的主函数。
其中加载可执行文件的执行过程如下:
1)启动加载器,加载器删除子进程现有的虚拟地址段
2)加载器根据可执行目标文件中的段头部表信息,创建一组新的代码段、数据段、堆段和栈段。新的堆、栈段初始化为零。代码段和数据段映射为可执行文件的代码段和数据段。
3)根据可执行文件 ELF 中的.interp段查找动态链接器ld.so的路径名,动态链接器实际上也是一个共享对象,加载器同样通过映射的方式将它加载到进程的地址空间。然后把控制权交给动态链接器的入口地址(与可执行文件一样,共享对象也有入口地址),当动态链接器得到控制权后,进行一系列初始化操作,然后根据可执行文件ELF中.dynamic段,这个段里保存了动态链接器所需要的相关信息,比如依赖于哪些共享对象(例如libc.so)、动态链接符号表位置、动态链接重定位表的位置、共享对象初始化代码的地址等信息,根据它们查找和加载可执行文件所依赖的共享对象,并映射到进程地址空间的共享区域中。
4)当所有动态链接工作完成以后,动态连接器会将控制权交给可执行文件的入口地址,即跳转到可执行文件的_start 启动代码并调用新程序中的main函数开始执行。
这里提到的 .interp 、.dynamic、段头部表的查看方法及具体信息可查看我上篇博文:Linux进程地址空间之初探:二
三:task_struct进程控制块,ELF文件格式与进程地址空间的联系
task_struct进程控制块中的mm字段所指向的mm_struct结构描述了进程地址空间的信息,包括代码段、数据段、堆段、栈段所在地址空间里的起始和结束地址等信息。
ELF文件格式中的 ELF头部、段头部表、.init、.text、.rodata段对应进程地址空间中的代码段,在加载可执行文件时,会把它们映射到进程地址空间中的代码段区域。
ELF文件格式中的 .data、.bss段 对应 进程地址空间中的 数据段,在加载可执行文件时,会把它们映射到进程地址空间的数据段区域。
具体的对应关系图请见 上篇博文:Linux进程地址空间之初探:二
四:动态链接库在ELF文件格式中与进程地址空间中的表现形式
动态链接库在ELF文件中对应着.dynamic段所包含的信息:包括动态链接器所需要的相关信息,比如依赖于哪些共享对象(例如libc.so)、动态链接符号表位置、动态链接重定位表的位置、共享对象初始化代码的地址等信息。
动态链接库最后被映射到进程地址空间的共享库区域段。
ELF文件格式图、可执行文件加载完后进程地址空间的状态图、及dynamic段的信息见上篇博文:Linux进程地址空间之初探:二