linux进程管理(4)---进程执行

一、目的

    本文将讲述进程如何加载并执行一个二进制文件。

    进程被创建完成后,如果需要执行自己的程序,需要做两件事情:从文件系统中读取二进制文件;识别该二进制文件的格式并加载到进程内存地址空间。

    为什么需要识别二进制文件的格式呢?因为linux支持的二进制文件格式不止一种,例如:ELFBASH等。不同的二进制文件格式,需要调用不同的解析程序识别并加载该二进制文件到内存中。

    sys_execve()负责二进制文件的读取、识别和加载;sys_execve()的实现可以在fs/exec.c文件中找到。


二、读取二进制文件

    定义structlinux_binprm结构体,记录二进制文件的重要信息:二进制头128字节、文件名、进程名、环境变量个数、命令行参数个数等。

    sys_execve()->do_execve()->do_execve_common()负责加载二进制文件:

1、调用unshare_files()为进程复制一份文件表;


2、调用kzalloc()分配一份structlinux_binprm结构体;


3、调用open_exec()查找并打开二进制文件;


4、调用sched_exec()找到最小负载的CPU,用来执行该二进制文件;


5、根据获取的信息,填充structlinux_binprm结构体中的filefilenameinterp成员;


6、调用bprm_mm_init()创建进程的内存地址空间,并调用init_new_context()检查当前进程是否使用自定义的局部描述符表;如果是,那么分配和准备一个新的LDT


7、填充structlinux_binprm结构体中的argcenvc成员;


8、调用prepare_binprm()检查该二进制文件的可执行权限;最后,kernel_read()读取二进制文件的头128字节(这些字节用于识别二进制文件的格式及其他信息,后续会使用到);


9、调用copy_strings_kernel()从内核空间获取二进制文件的路径名称;


10、调用copy_string()从用户空间拷贝环境变量和命令行参数;

    至此,二进制文件已经被打开,structlinux_binprm结构体中也记录了重要信息;下面需要识别该二进制文件的格式并最终运行该文件。


三、识别二进制文件

    每种格式的二进制文件对应一个structlinux_binprm结构体,load_binary成员负责识别该二进制文件的格式;内核使用链表组织这些structlinux_binfmt结构体,链表头是formats

    接着do_execve_common()继续往下看:

11、调用search_binary_handler()formats的链表中找到该二进制文件的处理函数,并执行该函数;(例如,ELF文件的处理函数是load_elf_binary()


12、释放structlinux_binprm结构体占用的内存空间。


四、ELF格式的二进制文件

    通常加载的二进制文件都是ELF格式的,因此下面讲述ELF格式的二进制文件处理过程。(注此处需要一些ELF文件格式的只是,可以参考ELF规范。)

1、从structlinux_binprm结构体的buf成员中拷贝ELF头到缓冲区,并检查该文件是否是可执行文件或共享文件;


2、调用elf_check_arch()检查ELF头记录的系统体系结构与当前系统的体系结构是否相同(例如,x86arm等);


3、计算ELF文件structelf32_phdr结构数组的长度(即e_phensize* e_phnum = total program header)并分配内存;


4、调用kernel_read()从二进制文件中读取programheader到内存中;


5、如果该二进制文件中包含PT_INTER类型的programheader,那么打开该programheader指向的文件(ELF程序解析器,该文件也是二进制文件)并读取该解析程序的ELF头;


6、调用flush_old_exec()复制父进程的信号处理表、用户空间等资源;至此,新进程与父进程就没有什么共享资源了;


7、调用setup_new_exec()设置新进程的名称、清空信号处理表、关闭已经打开的文件;


8、调用setup_arg_pages()分配命令行参数和环境变量的地址空间;


9、处理二进制文件中类型为PT_LOADprogramheader,装入textdata段;


10、调用set_brk()分配bss段空间;


11、根据前期获取的ELF程序解析器,加载“解析器”二进制文件;


12、调用create_elf_tables()初始化命令行参数和环境变量;


13、设置新进程的textdatabss段的起始地址和终止地址;


14、调用start_kernel()启动新进程(首先执行的是“解析器”程序);


    总之,load_elf_binary()依次处理ELFprogramheader,并为新进程分配独占的资源,最后启动新进程。


五、总结

    linux通过sys_execve()系统调用从文件系统中读取、识别并加载二进制文件;根据二进制文件中记录的文件类型,调用相应的处理函数识别、加载二进制文件到新进程的地址空间中,最后启动该新进程。至此,新进程就彻底与父进程毫无“瓜葛”了。


版权声明:

    原创作品,如非商业性转载,请注明出处;如商业性转载出版,请与作者联系。

linux进程管理(4)---进程执行,布布扣,bubuko.com

linux进程管理(4)---进程执行

上一篇:开源区块链-链(NULS ChainBox)搭建和测试


下一篇:linux下安装7z命令及7z命令的使用