Linux系统下,一个应用从启动到执行main函数经历了什么:
- 加载器(loader):用户在终端启动一个程序时候,shell调用execve,执行程序的启动。
- 内核态操作:execve做了以下几个事情,(1)找到执行文件(2)读取ELF的文件头,各个段的头信息等,就知道程序的入口地址。
- 创建进程环境:(1)内核位进程分配新的资源,如进程控制块(PCB),内存空间等 (2)加载程序段:根据ELF文件的程序头,段头,内核将可执行文件的各个段加载到新的地址空间。(3)设置堆栈:为进程分配用户栈,并将命令行参数和环境变量拷贝到空户栈上,(4)动态连接器:如果程序是动态链接的,内核会加载elf文件中的动态链接器地址,并将控制权给它。
- 动态链接器:(1)解析符号:解析并加载所需要的共享库,满足所有未定义的符号。(2)重定位:修改程序中的地址引用,使其找到正确的内存地址。(3)初始化构造函数:执行共享库中的初始化代码(.init中的代码),通常是调用构造函数
- 用户态准备:在动态链接器完成工作后,程序的控制权会传递给程序的入口点(—_start)
- CRT(C运行时)初始化:程序的入口点_start通常时由c运行时库提供的启动代码。它完成一下操作: (1)堆栈和全局数据的初始化:设置堆栈指针,初始化全局变量和静态变量。(2)调用程序初始化函数:执行编译器生成的初始化函数(.init_array数组中的函数)。 (3)准备参数:将命令行参数和环境变量传递给main函数。
- 调用main
主要有两个比较重要的部分:
1.进程的创建
当一个进程通过for系统调用创建的时候,实际上他是当前进程的一个副本以及大部分资源的复制。当时进程的堆栈不是完全复制的,而是继承了父进程堆栈状态,但会为新进程创建一个新的堆栈顶部, 这是因为fork之后通常紧接着“exec”函数来替换进程执行的上下文,此时进程会加载一个新的程序。
进程创建时候的资源分配
在fork时,新进程继承了以下资源:
打开的文件描述符,环境变量,信号处理程序,进程ID,父进程ID,进程组ID,会话ID, 当前的工作目录,根目录,文件权限掩码,资源限制,终端控制,内存映射。
2.后续的堆栈设置
当fork之后使用exec来执行一个新的程序时候会发生以下状况:
-新程序的二进制文件将被加载到内存中覆盖原来的进程文本段,数据段和bss段
-系统调用会设置新城西的入口地址(_start),并准备好堆栈
-传给exec的参数和环境变量放在堆栈上,便以新的城西访问他们
-新程序的堆栈将被初始化,通常是一个比较高的虚拟地址,向下增长