潘恒 原创做品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000linux
1、实验内容shell
1.预处理、编译和连接 实践函数
ELF头部在文件的开始,描述文件的整体格式,保存了路线图,描述该文件的组织状况,即生成该文件系统的字的大小和字节顺序 段头部表用来描述ELF可执行文件与连续的存储段之间的映射关系。节头表包含了描述文件节区的信息,每一个节区在表中都有一个项,给出节区的名称、节区大小这类内心。用于连接的目标文件(可重定向文件)必须包含节区头部表,而可执行文件能够没有。spa
2.可执行程序的执行环境操作系统
命令行参数和shell环境,通常咱们执行一个程序的Shell环境,咱们的实验直接使用execve系统调用。命令行
$ ls -l /usr/bin列出/usr/bin下的目录信息
Shell自己不限制命令行参数的个数,命令行参数的个数受限于命令自身3d
例如:指针
int main(int argc, char*argv[])
又如调试
int main(int argc, char*argv[], char *envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数code
int execve(const char *filename,char * const argv[ ],char * const envp[ ]);
库函数exec*都是execve的封装例程
3.可执行程序的装载
(1)do_execve()
(2)search_binary_handler()
(3) load_elf_binary()
(4) load_elf_interp()
2、实验步骤及截图
rm menu -rf
3、Linux系统加载可执行程序所需处理过程的理解
1. 新的可执行程序是从哪里开始执行的?
当execve()系统调用终止且进程从新恢复它在用户态执行时,执行上下文被大幅度改变,要执行的新程序已被映射到进程空间,从elf头中的程序入口点开始执行新程序。
若是这个新程序是静态连接的,那么这个程序就能够独立运行,elf头中的这个入口地址就是本程序的入口地址。
若是这个新程序是动态连接的,那么此时还须要装载共享库,elf头中的这个入口地址是动态连接器ld的入口地址。
2.为何execve系统调用返回后新的可执行程序能顺利执行?
新的可执行程序执行:
1. 须要的库函数。
2. 属于它的进程空间:代码段,数据段,内核栈,用户栈等。
3. 须要的运行参数。
4. 须要的系统资源。
若是知足以上4个条件,那么新的可执行程序就会处于可运行态,只要被调度到,就能够正常执行。
条件1:若是新进程是静态连接的,那么库函数已经在可执行程序文件中,条件知足。若是是动态连接的,新进程的入口地址是动态连接器ld的起始地址,能够完成对所需库函数的加载,也能知足条件。
条件2:execve系统调用经过大幅度修改执行上下文,将用户态堆栈清空,将老进程的进程空间替换为新进程的进程空间,新进程从老进程那里继承了所须要的进程空间,条件知足。
条件3:咱们通常在shell中,输入可执行程序所须要的参数,shell程序把这些参数用函数参数传递的方式传给给execve系统调用,而后execve系统调用以系统调用参数传递的方式传给sys_execve,最后sys_execve在初始化新程序的用户态堆栈时,将这些参数放在main函数取参数的位置上。条件知足。
条件4:若是当前系统中没有所须要的资源,那么新进程会被挂起,直到资源有了,唤醒新进程,变为可运行态,条件能够知足。
综上,新的可执行程序能够顺利执行。
3.对于静态连接的可执行程序和动态连接的可执行程序execve系统调用返回时会有什么不一样?
execve系统调用会调用sys_execve,而后sys_execve调用do_execve,而后do_execve调用do_execve_common,而后do_execve_common调用exec_binprm。
对于ELF文件格式,fmt函数指针实际会执行load_elf_binary,load_elf_binary会调用start_thread,在start_thread中经过修改内核堆栈中EIP的值,使其指向elf_entry,跳转到elf_entry执行。
对于静态连接的可执行程序,elf_entry是新程序的执行起点。对于动态连接的可执行程序,须要先加载连接器ld,
elf_entry = load_elf_interp(…)
将CPU控制权交给ld来加载依赖库,再由ld在完成加载工做后将CPU控制权还给新进程。
四.总结
在Linux中,fork是进程建立另外一个进程的惟一方法。只有第一个进程也就是被称做 init 的进程须要 手工建立 。全部其余进程都是用fork这个系统调用建立的。fork系统调用只是复制了父进程的数据和堆栈,并在这两个进程之间共享文本区。fork系统调用采用比较聪明的方式— 写时拷贝(copy-on-write) 技术,使得fork结束后并不马上复制父进程的内容,而是到了真正实用的时候才复制,这样使效率大大提升。fork函数建立了一个子进程后,子进程会调用exec族函数执行另一个程序。 多进程、多用户、虚拟存储的操做系统出现之后,可执行文件的装载过程变得很是复杂。引入了进程的虚拟地址空间;而后根据操做系统如何为程序的代码、数据、堆、栈在进程地址空间中分配,它们是如何分布的;最后以页映射的方式将程序映射进程虚拟地址空间。 动态连接是一种与静态连接程序不一样的概念,即一个单一的可执行文件模块被拆分红若干个模块,在程序运行时进行连接的一种方式。而后根据实际例子do_exece()分析了ELF装载的大体过程,中间实现了动态连接。