execve族系统调用用于运行一个新的进程,并且替换当前进程的内存映像(代码,数据,栈)为新进程的内存映像。这个系统调用会在指定的进程空间内加载一个新的程序,同时将参数和环境变量传递给新程序。这个调用函数可以用于实现重载进程代码、切换用户权限、启动新的进程等功能。 execvp和execv是execve族的两个变体,它们允许你在新进程中运行可执行程序文件。 execvp使用PATH环境变量执行程序,而execv需要指定完整路径。 execve族的调用可以让用户免于开发自己的shell或者cli程序,用现有的程序交互式地对系统进行操作,甚至可以在shell中启动其他进程。 下面我们来跟踪一下Linux内核是如何实现该族函数的 内核中的定义 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); } SYSCALL_DEFINE5(execveat, int, fd, const char __user *, filename, const char __user *const __user *, argv, const char __user *const __user *, envp, int, flags) { int lookup_flags = (flags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0; return do_execveat(fd, getname_flags(filename, lookup_flags, NULL), argv, envp, flags); } #ifdef CONFIG_COMPAT COMPAT_SYSCALL_DEFINE3(execve, const char __user *, filename, const compat_uptr_t __user *, argv, const compat_uptr_t __user *, envp) { return compat_do_execve(getname(filename), argv, envp); } COMPAT_SYSCALL_DEFINE5(execveat, int, fd, const char __user *, filename, const compat_uptr_t __user *, argv, const compat_uptr_t __user *, envp, int, flags) { int lookup_flags = (flags & AT_EMPTY_PATH) ? LOOKUP_EMPTY : 0; return compat_do_execveat(fd, getname_flags(filename, lookup_flags, NULL), argv, envp, flags); } #endif 通过不同flag设置不同参数,但是它们最后会调用到一个函数中do_execveat_common /* * sys_execve() executes a new program. */ static int do_execveat_common(int fd, struct filename *filename, struct user_arg_ptr argv, struct user_arg_ptr envp, int flags) { char *pathbuf = NULL; struct linux_binprm *bprm; struct file *file; struct files_struct *displaced; int retval; if (IS_ERR(filename)) return PTR_ERR(filename); /* * We move the actual failure in case of RLIMIT_NPROC excess from * set*uid() to execve() because too many poorly written programs * don't check setuid() return code. Here we additionally recheck * whether NPROC limit is still exceeded. */ if ((current->flags & PF_NPROC_EXCEEDED) && atomic_read(¤t_user()->processes) > rlimit(RLIMIT_NPROC)) { retval = -EAGAIN; goto out_ret; } /* We're below the limit (still or again), so we don't want to make * further execve() calls fail. */ current->flags &= ~PF_NPROC_EXCEEDED; retval = unshare_files(&displaced); if (retval) goto out_ret; retval = -ENOMEM; bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); if (!bprm) goto out_files; retval = prepare_bprm_creds(bprm); if (retval) goto out_free; check_unsafe_exec(bprm); current->in_execve = 1; file = do_open_execat(fd, filename, flags); retval = PTR_ERR(file); if (IS_ERR(file)) goto out_unmark; sched_exec(); bprm->file = file; if (fd == AT_FDCWD || filename->name[0] == '/') { bprm->filename = filename->name; } else { if (filename->name[0] == '\0') pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd); else pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s", fd, filename->name); if (!pathbuf) { retval = -ENOMEM; goto out_unmark; } /* * Record that a name derived from an O_CLOEXEC fd will be * inaccessible after exec. Relies on having exclusive access to * current->files (due to unshare_files above). */ if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt))) bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE; bprm->filename = pathbuf; } bprm->interp = bprm->filename; retval = bprm_mm_init(bprm); if (retval) goto out_unmark; bprm->argc = count(argv, MAX_ARG_STRINGS); if ((retval = bprm->argc) < 0) goto out; bprm->envc = count(envp, MAX_ARG_STRINGS); if ((retval = bprm->envc) < 0) goto out; retval = prepare_binprm(bprm); if (retval < 0) goto out; retval = copy_strings_kernel(1, &bprm->filename, bprm); if (retval < 0) goto out; bprm->exec = bprm->p; retval = copy_strings(bprm->envc, envp, bprm); if (retval < 0) goto out; retval = copy_strings(bprm->argc, argv, bprm); if (retval < 0) goto out; would_dump(bprm, bprm->file); retval = exec_binprm(bprm); if (retval < 0) goto out; /* execve succeeded */ current->fs->in_exec = 0; current->in_execve = 0; membarrier_execve(current); acct_update_integrals(current); task_numa_free(current); free_bprm(bprm); kfree(pathbuf); putname(filename); if (displaced) put_files_struct(displaced); return retval; out: if (bprm->mm) { acct_arg_size(bprm, 0); mmput(bprm->mm); } out_unmark: current->fs->in_exec = 0; current->in_execve = 0; out_free: free_bprm(bprm); kfree(pathbuf); out_files: if (displaced) reset_files_struct(displaced); out_ret: putname(filename); return retval; } 简单叙述一下调用流程: file = do_open_execat(fd, filename, flags); //打开文件 retval = bprm_mm_init(bprm);//初始化二进制elf加载器 retval = prepare_binprm(bprm);//填充bin的binprm参数,权限检查,读取文件头的128个字节 retval = exec_binprm(bprm); //执行可执行文件 retval= search_binary_handler(bprm);//加载适合的elf加载器 retval = fmt->load_binary(bprm); //调用加载回调加载函数load_binary指向了 binfmt_aout.c中load_aout_binary函数 通过函数指针load_binary调用了elf加载器的回调函数 retval = load_aout_binary(struct linux_binprm * bprm); //加载器回调函数 通过设置一下几步设置参数以后,拉起了一个新的进程 retval = flush_old_exec(bprm);//清空旧的进程空间,释放相关资源 setup_new_exec(bprm);//设置进程名字,读取进程代码段 start_thread(regs, ex.a_entry, current->mm->start_stack);//开启一个进程 //arm 32 #define start_thread(regs,pc,sp) ({ memset(regs->uregs, 0, sizeof(regs->uregs)); if (current->personality & ADDR_LIMIT_32BIT) regs->ARM_cpsr = USR_MODE; else regs->ARM_cpsr = USR26_MODE; if (elf_hwcap & HWCAP_THUMB && pc & 1) regs->ARM_cpsr |= PSR_T_BIT; regs->ARM_cpsr |= PSR_ENDSTATE; regs->ARM_pc = pc & ~1; /* pc */ regs->ARM_sp = sp; /* sp */ nommu_start_thread(regs); }) //arm 64 static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) { memset(regs, 0, sizeof(*regs)); forget_syscall(regs); regs->pc = pc; } static inline void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) { start_thread_common(regs, pc); regs->pstate = PSR_MODE_EL0t; regs->sp = sp; } 由以上代码可知,execve族系统调用主要是通过elf加载器解释加载到内存,再通过修改arm的pc sp指针,使得该elf可执行文件得以运行。 |
|