prepare执行完释放_第五十三期-进程的执行

5b466c5b677f95b54e509910e8fe7b5d.png

作者:熊轶翔@熊仙僧,中国科学院软件研究所智能软件研究中心

在上一章我们学习了进程的创建过程,但是新进程在被创建之后还如果要运行新的代码,这个时候就轮到execve()系统调用出场了,他的主要作用就是将目标代码读入进程内存空间中的代码段。

execve()函数详解

execve()函数的入口是sys_execve()系统调用,原型如下:(路径:/kernel-4.19/include/linux/syscalls.h)

asmlinkage long sys_execve(const char __user *filename,     //需要执行的文件的绝对路径
        const char __user *const __user *argv,              //传入系统调用的参数
        const char __user *const __user *envp);             //环境变量

第一个参数是要被执行的程序的路径,第二个参数则向程序传递了命令行参数,第三个参数则向程序传递环境变量[1]。

当进入sys_execve()系统调用时,在中断处理程序中调用了do_execve()[2]:(路径:/kernel-4.19/fs/exec.c)

SYSCALL_DEFINE3(execve,
        const char __user *, filename,
        const char __user *const __user *, argv,
        const char __user *const __user *, envp)
{
    return do_execve(getname(filename), argv, envp);
}

在进入do_execve()之后,完成了一系列参数赋值,接着又调用了do_execveat_common():(路径:/kernel-4.19/fs/exec.c)

int do_execve(struct filename *filename,
    const char __user *const __user *__argv,
    const char __user *const __user *__envp)
{
    struct user_arg_ptr argv = { .ptr.native = __argv };
    struct user_arg_ptr envp = { .ptr.native = __envp };
    return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}

do_execveat_common里面又是直接调用的__do_execve_file,主要动作如下[4]:

  1. 调用unshare_files()为进程复制一份文件表;
  2. 调用kzalloc()创建一个linux_binprm结构体实例bprm(此结构体用于管理一些与载入的二进制文件相关的参数);
  3. 调用do_open_execat查找并打开二进制文件;
  4. 调用sched_exec()找到最小负载的CPU,用来执行该二进制文件;
  5. 根据获取的信息,填充bprm中的filefilenameinterp成员;
  6. 调用bprm_mm_init()创建进程的内存地址空间,为新程序初始化内存管理.并调用init_new_context()检查当前进程是否使用自定义的局部描述符表;如果是,那么分配和准备一个新的LDT;
  7. 填充bprm中的argcenvc成员;
  8. 调用prepare_binprm()检查该二进制文件的可执行权限;最后,kernel_read()读取二进制文件的头128字节(这些字节用于识别二进制文件的格式及其他信息,后续会使用到);
  9. 调用copy_strings_kernel()从内核空间获取二进制文件的路径名称;
  10. 调用copy_string()从用户空间拷贝环境变量和命令行参数;
  11. 至此,二进制文件已经被打开,bprm中也记录了重要信息, 内核开始调用exec_binprm()执行可执行程序;
  12. 释放linux_binprm数据结构,返回从该文件可执行格式的load_binary中获得的代码。

__do_execve_file()函数代码长度较长,故不在此贴出,用下图来配合说明其大体的流程[3]:

0c7e70f4297d553f87d1a9d1f1d639c3.png

说明.png)

在这里面涉及到一个重要的结构体linux_binprm,用来保存要执行的文件相关的信息,部分代码如下:(路径:/kernel-4.19/include/linux/binfmts.h)

struct linux_binprm {
    char buf[BINPRM_BUF_SIZE];      //保存可执行文件的头128字节
#ifdef CONFIG_MMU
    struct vm_area_struct *vma;     //指向虚拟内存页面
    unsigned long vma_pages;        //虚拟页面计数
#else
# define MAX_ARG_PAGES  32
    struct page *page[MAX_ARG_PAGES];   //存放二进制文件的页面
#endif
    struct mm_struct *mm;       //当前进程的mm_struct实例
            unsigned long p;     //当前内存页最高地址
...
#endif
    unsigned int recursion_depth; /* only for search_binary_handler() */
    struct file * file;     //要执行的文件
    struct cred *cred;  /* new credentials */
    int unsafe;     /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
    unsigned int per_clear; /* bits to clear in current->personality */
    int argc, envc;         //命令行参数和环境变量数目
    const char * filename;  //要执行的文件的名称 */
    const char * interp;    //要执行的文件的真实名称,通常和filename相同
...
}     

总结

本章我们学习了excve()的调用过程:sys_execve()->do_execve()->do_execveat_common()->__do_execve_file(),在最后一个函数中完成了大部分功能,在此之后进程就可以执行新的程序了。进程执行完毕之后将会结束其生命周期,这部分将在下一章讲解。


参考:

[1] http:// blog.sina.com.cn/s/blog _4ba5b45e0102e3to.html
[2] https:// blog.csdn.net/a36334492 3/article/details/45063137
[3] https:// my.oschina.net/u/385778 2/blog/1854572
[4] https:// cloud.tencent.com/devel oper/article/1351963
[5] https:// cloud.tencent.com/devel oper/article/1451788

版权声明:本文为weixin_39907658原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。