
作者:熊轶翔@熊仙僧,中国科学院软件研究所智能软件研究中心
在上一章我们学习了进程的创建过程,但是新进程在被创建之后还如果要运行新的代码,这个时候就轮到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]:
- 调用
unshare_files()为进程复制一份文件表; - 调用
kzalloc()创建一个linux_binprm结构体实例bprm(此结构体用于管理一些与载入的二进制文件相关的参数); - 调用
do_open_execat查找并打开二进制文件; - 调用
sched_exec()找到最小负载的CPU,用来执行该二进制文件; - 根据获取的信息,填充
bprm中的file、filename、interp成员; - 调用
bprm_mm_init()创建进程的内存地址空间,为新程序初始化内存管理.并调用init_new_context()检查当前进程是否使用自定义的局部描述符表;如果是,那么分配和准备一个新的LDT; - 填充
bprm中的argc、envc成员; - 调用
prepare_binprm()检查该二进制文件的可执行权限;最后,kernel_read()读取二进制文件的头128字节(这些字节用于识别二进制文件的格式及其他信息,后续会使用到); - 调用
copy_strings_kernel()从内核空间获取二进制文件的路径名称; - 调用
copy_string()从用户空间拷贝环境变量和命令行参数; - 至此,二进制文件已经被打开,
bprm中也记录了重要信息, 内核开始调用exec_binprm()执行可执行程序; - 释放
linux_binprm数据结构,返回从该文件可执行格式的load_binary中获得的代码。
__do_execve_file()函数代码长度较长,故不在此贴出,用下图来配合说明其大体的流程[3]:

说明.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