尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux 进程处理中:execve 与 fork exec 模型

Linux 进程处理中:execve 与 fork exec 模型 Linux 进程处理中execve 与 fork exec 模型课程尚硅谷《嵌入式 Linux 应用层开发》第 3 章进程处理依据2026-09-23 18:07 录音转写课程 PDF 的 PDF 第 70—74 页。本节边界主体是execve单独使用以及fork execve组合PDF 第 74 页末尾只引出waitpid和僵尸进程完整回收方法留到下一节。1. 本节主线上一节的fork只能复制当前进程。子进程若想运行另一个程序需要再调用execve替换自己的程序映像。forkforkexecve 成功父进程运行程序 A父进程继续运行 A子进程暂时也运行 A同一个子进程改为运行程序 BPID 保持不变waitpid 回收子进程下一节完整学习先记住两个动作fork创建一个新的子进程。execve不创建进程把调用者当前运行的程序替换成另一个程序。2.execve到底做什么#includeunistd.hintexecve(constchar*path,char*constargv[],char*constenvp[]);execve根据path加载一个新的可执行文件并用新程序替换当前进程的程序映像。新的代码段、数据段、堆和栈会建立起来随后从新程序入口开始运行。2.1 它是“换程序”不是“创建新进程”调用前PID 5300 正在运行 execve_test │ │ execve(./erlou, ...) ▼ 调用后PID 5300 正在运行 erlouPID 通常保持不变因为仍是同一个进程。课程用“从一楼上到二楼”作比喻人没有换所在的程序环境换了。“跳转到另一个进程”这个课堂说法容易误解。准确说法是当前进程加载并开始执行另一个程序。2.2 成功不会返回execve成功后旧程序已被替换没有原来的下一条语句可以返回。因此execve(path,args,envs);/* 只有 execve 失败才可能运行到这里 */perror(execve);成功不返回。失败返回-1并设置errno。不要写成“成功返回 0”。保存返回值再判断也可以但没有必要能执行到错误处理代码就说明调用已经失败。3. 三个参数逐个理解3.1path要执行的文件路径constchar*path/home/atguigu/process_test/erlou;execve本身不根据PATH搜索程序。path应给出绝对路径或相对于当前工作目录的路径例如./erlou。如果想只写程序名并让系统搜索PATH可使用execvp、execlp等带字母p的库函数。常见失败errno常见原因ENOENT路径不存在或脚本解释器不存在EACCES没有执行权限、目录不可搜索等ENOEXEC文件不是可识别的可执行格式E2BIG参数和环境变量总量过大3.2argv交给新程序的命令行参数char*args[]{erlou,/* 新程序看到的 argv[0] */banzhang,/* 新程序看到的 argv[1] */NULL/* 数组必须用空指针结束 */};新程序的main会看到argc 2 argv[0] erlou argv[1] banzhang argv[2] NULLargv[0]按惯例写程序名称但内核不会强制它必须等于path。path用于找到真正要加载的文件argv[0]只是传给新程序的一段字符串。3.3envp交给新程序的环境变量char*envs[]{PATH/usr/local/bin:/usr/bin:/bin,LANGC.UTF-8,NULL};每项通常采用名称值数组最后必须是NULL。若希望新程序继承当前进程的完整环境可使用externchar**environ;execve(path,args,environ);教材手工只传一个PATH适合观察参数形式实际程序经常直接传environ否则HOME、LANG、TZ等环境变量都会缺失。Linux 允许某些非标准的空指针用法但为了可移植和清晰应始终传入以NULL结尾的argv与envp数组。4.execve单独测试4.1 被加载的新程序erlou.c#includestdio.h#includestdlib.h#includeunistd.hintmain(intargc,char*argv[]){if(argc2){fprintf(stderr,参数不足无法运行二楼程序\n);returnEXIT_FAILURE;}printf(新程序name%s, PID%ld\n,argv[1],(long)getpid());returnEXIT_SUCCESS;}4.2 替换自身的程序execve_test.c#includestdio.h#includestdlib.h#includeunistd.hexternchar**environ;intmain(void){constchar*path./erlou;char*args[]{erlou,banzhang,NULL};printf(旧程序PID%ld准备执行 execve\n,(long)getpid());fflush(stdout);execve(path,args,environ);/* 只有失败才能到达这里 */perror(execve ./erlou);returnEXIT_FAILURE;}编译和运行gcc-Wall-Wextraerlou.c-oerlou gcc-Wall-Wextraexecve_test.c-oexecve_test ./execve_test两行输出中的 PID 应相同第一行来自旧程序第二行来自新程序。5.execve后哪些东西变了哪些保留5.1 被替换的主要内容程序代码与静态数据堆和栈普通全局变量、局部变量与动态分配内容被捕获信号的处理函数等程序映像相关状态。因此旧程序中的变量不能在新程序里直接继续使用。要传普通数据应通过argv、环境变量、文件、管道、共享内存等明确机制。5.2 通常保留的主要属性PID 与 PPID当前工作目录用户与组身份的大部分基础属性umask没有设置 close-on-exec 的文件描述符被忽略的信号状态等部分进程属性。“只有 PID 保留”是课堂入门阶段的简化说法。实际保留的不止 PID但地址空间中的旧代码和普通变量会被替换。5.3 文件描述符与FD_CLOEXEC默认情况下已打开的文件描述符会穿过成功的execve让新程序继续使用。这正是 shell 实现输入输出重定向和管道的基础。不希望传给新程序的描述符应在打开时使用O_CLOEXEC或通过fcntl设置FD_CLOEXECintfdopen(secret.txt,O_RDONLY|O_CLOEXEC);成功执行execve后该 fd 会自动关闭避免资源或敏感句柄意外泄漏到新程序。6. exec 系列函数的字母怎样记execve是底层接口C 库还提供多个便捷版本字母含义例子v参数使用 vector即argv[]数组execv、execvpl参数使用 list即可变参数列表execl、execlpp使用PATH搜索可执行文件execvp、execlpe由调用者显式传入环境数组execve、execle常见选择已知完整路径 继承当前环境execv 只知道命令名 继承当前环境execvp 完整控制路径、参数和环境execve这些函数成功后都不会返回。7. 为什么实际开发常用fork execve单独调用execve会把当前程序替换掉。如果希望原程序继续运行同时启动另一个程序就先fork子进程 程序B子进程 程序A副本父进程 程序A子进程 程序B子进程 程序A副本父进程 程序APID 不变fork 创建子进程继续执行原程序execve 替换子进程程序映像从程序B的 main 开始运行waitpid 回收子进程状态父进程保留原程序子进程负责加载目标程序。这是 shell 启动外部命令、服务管理程序启动工作进程的基础模式。8. 修正后的fork execve示例#includeerrno.h#includestdio.h#includestdlib.h#includesys/types.h#includesys/wait.h#includeunistd.hexternchar**environ;intmain(void){constchar*path./erlou;char*args[]{erlou,ergou,NULL};printf(父进程 PID%ld准备创建子进程\n,(long)getpid());fflush(stdout);pid_tpidfork();if(pid-1){perror(fork);returnEXIT_FAILURE;}if(pid0){execve(path,args,environ);/* 只有 execve 失败才执行子进程避免刷新父进程复制来的流 */perror(execve ./erlou);_exit(127);}printf(父进程仍运行原程序子进程 PID%ld\n,(long)pid);intstatus;if(waitpid(pid,status,0)-1){perror(waitpid);returnEXIT_FAILURE;}if(WIFEXITED(status)){printf(子进程退出码%d\n,WEXITSTATUS(status));}elseif(WIFSIGNALED(status)){printf(子进程被信号 %d 终止\n,WTERMSIG(status));}returnEXIT_SUCCESS;}这个例子比课程演示多了waitpid。PDF 第 74 页刚开始说明父进程要回收子进程这里先给出完整、安全的骨架相关返回状态会在下一节详细学习。8.1 为什么子进程失败路径用_exit(127)fork会复制父进程的用户态stdio缓冲。子进程的execve失败后若调用exit可能再次刷新复制来的缓冲内容。使用_exit可以立即结束子进程避免重复清理父进程的流状态。调用fork前的fflush(stdout)也能减少此类问题。退出码 127 是 shell 常用于“命令无法执行”的约定应用也可以根据自己的协议选择其他非零值。9. 课程示例中需要校正的地方9.1execve(argv[0], args, envs)路径写错PDF 第 71 页和第 73 页的示例声明了目标参数数组char*args[]{/home/atguigu/process_test/erlou,name,NULL};随后却写成execve(argv[0],args,envs);这里的argv[0]是当前测试程序自己的调用名称可能导致重新执行自己。目标路径应写成execve(args[0],args,envs);或使用独立的path变量execve(/home/atguigu/process_test/erlou,args,envs);9.2execve不会自动关闭所有资源录音中有“跳转后前面的资源自动关闭”的简化说法。普通内存映像会被替换但没有设置FD_CLOEXEC的文件描述符默认继续保持打开。设计fork exec时要有意识地关闭或保留描述符。9.3 不能用sleep保证父进程活着录音为了看到正确 PPID让父进程暂时睡眠。这只能帮助观察不能作为正式同步。正确做法是父进程调用waitpid等待指定子进程。9.4 使用pid_t不要写内部类型__pid_t__pid_t是实现内部名称。应用程序应包含sys/types.h并使用公开类型pid_t。9.5 错误信息应使用perror只打印“上二楼失败”看不到失败原因。perror(execve)会显示No such file or directory、Permission denied等具体诊断。10. Makefile 自动变量对照教材第 71—73 页使用了几个 Make 自动变量写法含义$当前规则的目标文件名$第一个依赖文件$^所有依赖文件去除重复项$(firstword $^)依赖列表的第一个词$(lastword $^)依赖列表的最后一个词$(word 2,$^)依赖列表的第二个词可使用更清楚且不会忽略错误的写法CC : gcc CFLAGS : -Wall -Wextra -O2 erlou: erlou.c $(CC) $(CFLAGS) -o $ $ fork_execve_test: fork_execve_test.c erlou $(CC) $(CFLAGS) -o $ $ run: fork_execve_test ./fork_execve_test clean: rm -f erlou fork_execve_test教材命令前的-会让 Make 忽略命令失败。练习和真实项目中通常不应在编译、运行命令前加-否则错误可能被掩盖。11. 第 74 页引出的子进程回收子进程结束后内核要保留少量退出信息供父进程取得退出状态。父进程若仍存活却一直不调用wait/waitpid已结束的子进程会保持僵尸状态。需要区分僵尸进程子进程已经结束但父进程尚未回收其退出状态。孤儿进程子进程仍在运行但原父进程先结束它会被init或最近的子收割器接管。录音演示中父进程太早结束子进程的 PPID 发生变化这是孤儿进程被重新指定父进程的现象。它不等同于僵尸进程。父进程存活 子进程已结束 尚未 wait → 僵尸 父进程已结束 子进程仍运行 → 孤儿被重新托管下一节要学习的关键就是父进程用waitpid等待并回收子进程同时解析退出码或终止信号。12. 本节最低掌握标准继续学习前能够说明下面几点即可fork创建进程execve替换程序两者作用不同。execve成功后不返回失败返回-1并设置errno。path是实际目标路径argv[0]只是传给新程序的惯例名称。argv和envp都是指针数组最后必须有NULL。execve前后 PID 不变旧代码、堆、栈和普通变量被替换。未设置 close-on-exec 的 fd 默认能传给新程序。常用流程是子进程execve父进程waitpid。最简记忆图fork一个进程变成父、子两个进程 exec一个进程仍是一个进程但换成另一个程序 wait父进程回收子进程的退出状态13. 资料页码与录音时间索引主题PDF 页码录音时间erlou目标程序与execve引入7000:19—03:26execve参数、返回方式与环境变量70—7103:27—09:20单独执行测试及 PID 保持7209:21—11:20fork execve组合72—7411:21—17:05waitpid、僵尸进程引入74教材页末录音本段未完整展开进一步核对资料Linux man-pagesexecve(2)、exec(3)、environ(7)、waitpid(2)。
返回列表