资讯动态

进程控制:四关打通“创建-终止-等待-替换”完整链路

发布时间:2026/8/24 14:02:32 来源:尧图企业网站定制
前言上一章我们知道了虚拟地址空间长什么样子那么这一章我们动态的来介绍进程是如何创建的如何退出的如何回收的。一、进程创建---fork()和vfork()1、代码#include stdio.h#include unistd.h#include stdlib.hint main(void){printf(Before: pid is %d\n, getpid());pid_t pid fork();if (pid -1) {perror(fork);exit(1);}printf(After: pid is %d, fork return %d\n, getpid(), pid);sleep(1);return 0;}运行上述代码可以发现befor会打印一次而after会打印两次。这是因为fork之后会创建子进程当fork之后父子进程会运行fork之后的代码所以会打印两次after而befor在fork创建之前所以说只会有父进程去执行。(注如果是多核CPU的话父子进程可以同时运行单核的话交替运行可能父进程先可能子进程先)2、fork()的返回值为什么不一样子进程返回为0子进程没有fork前没有子进程0可以是一个“非ID”的标记让它知道自己是子进程。父进程返回得到子进程的PID父进程可以在后续wait回收子进程。返回为-1代表进程创建失败。3、写时拷贝(COW)父进程的物理页不复制而是让父子进程共享物理页。页表的权限为只读。当任何一方想要写入就会触发缺页异常--复制页表--更新页表--恢复写权限。4、vfork()这是一个历史遗留的问题fork写时拷贝会完整的拷贝页表此时效率就不会太高所以vfork就是在这样的背景下创建的。特点子进程创建之后不会复制父进程的页表而是和父进程共享页表。父进程先被阻塞先让子进程运行知道子进程调用exec或exit。子进程修改数据会影响父进程。int main(){int glob10;printf(glob%d\n,glob);pid_t idvfork();if(id0){glob20;printf(子进程的glob:%d\n,glob);exit(0);}else{wait(NULL);printf(父进程的glob:%d\n,glob);}return 0;}可以看到子进程修改影响了子进程。注意(危害)1、子进程修改会影响父进程。2、如果子进程return则会销毁子进程栈帧而父子进程是共享的此时父进程的栈帧也会被销毁。二、进程终止——你是正常退出还是被“杀”1、三种清理方式(1)return n; 相当于exit(n);(2) exit(n) 库函数会刷新缓冲区最后调用_exit(n);(3)_exit(n) 系统调用接口直接进入系统内核不会刷新缓冲区int main(){printf(safe\n);exit(0);}////////////////////////////////////////////////////////////////////////////////////////////////////////////int main(){printf(safe); (注不能带\n否则是行刷新就会打印);_exit(0);}可以发现打印信息符合上述谈论。2、退出码和$退出码为0:退出成功。退出码非0:退出失败。退出码128信号编号是被信号终止。返回10.int main(){exit(10);}返回-1 (只有低8位有效)int main(){exit(-1);}/////////////////////////////////////////////////////////////////////////////////////理解信号终止返回码int main(){printf(pid:%d\n,getpid());sleep(10);}可以看到返回的是128信号编号。总结这个是操作系统通过退出码向外界通报这个进程的情况。三:进程等待——回收子进程获取“成绩单”1、为什么必须等待子进程退出父进程没有回收就会产生僵尸进程。kill-9杀不死这个进程因为僵尸进程已经死了杀不了。造成资源浪费。2、wait(阻塞等待)和waitpid(轮询等待)的区别pid_t wait(int *status);pid_t waitpid(int pid,int *status,int options); options最重要的是WNOHANG返回值wait的返回值: 0返回的是子进程的pid代表着回收成功。-1意味着回收失败。waitpid的返回值: 0返回的是子进程的pid代表着回收成功。0意味着子进程还活着。-1意味着回收失败。wait是等待任意一个子进程退出waitpid可以等待pid指定的子进程。wait只能阻塞等待waitpid可以通过options实现非阻塞轮询waitpid的pid参数pid 值含义pid 0等待 PID 等于 pid 的特定子进程pid 0等待同进程组的任意子进程pid -1等待任意子进程等同于 wait ()pid -1等待进程组 ID abs (pid) 的任意子进程阻塞版本--此时父进程只能等待。int main(void){pid_t pid fork();if (pid 0) {sleep(20);exit(10);} else {int status;wait(status);if (WIFEXITED(status)) // 低7位为0 → 正常退出printf(exit code: %d\n, WEXITSTATUS(status));elseprintf(signal: %d\n, WTERMSIG(status));}return 0;}非阻塞版本--父进程打印int main(void){pid_t pid fork();if (pid 0) {sleep(10);exit(10);} else {int status;while(1){pid_t retwaitpid(pid,status,WNOHANG);if(ret0){sleep(1);printf(child is running\n);}else if(ret0)//可以在这里用宏解析退出码等信息{printf(child is exit\n);break;}else{perror(watipid error);break;}}}return 0;}3、status位图重点关注第十六位高十六位不考虑。0~6位是终止信号7~15号是退出码。情况如果说正常退出终止信号为0退出码正常如果说被信号杀死则终止信号为信号编号退出码为0.宏情况判断正常退出还是终止宏功能底层位运算表达式WIFEXITED(status)是否正常退出(status 0x7F) 0WEXITSTATUS(status)提取退出码(status 8) 0xFFWIFSIGNALED(status)是否被信号终止(status 0x7F) ! 0WTERMSIG(status)提取信号编号status 0x7F注:这里的宏和上边的waitwaitpid的返回值可能会弄混wait的返回值代表着是否回收成功而这里的宏是回收成功之后去解析退出码以及终止信号用的不要搞混。四、程序替换——exec 让子进程“脱胎换骨”1、原理exec将进程的用户空间代码和数据与另一个进程交换。特点 不创建新进程(PID不变)。直接覆盖当前进程的mm_struct内容。调用成功之后不会返回。2、六个函数函数参数路径环境execl列表全路径继承execlp列表PATH搜索继承execle列表全路径自定义execv数组全路径继承execvp数组PATH搜索继承execve数组全路径自定义真系统调用记忆口诀llist(参数以列表形式传最后是NULL)vvector(参数放在数组里)pPATH(回去PATH搜索)eenv(自己提供环境变量)相关代码实验#include unistd.h#include stdio.h#include stdlib.hint main(void){char *const argv[] {ps, -ef, NULL};char *const envp[] {PATH/bin:/usr/bin, TERMconsole, NULL};// 依次测试下面六种execl(/bin/ps, ps, -ef, NULL);// execlp(ps, ps, -ef, NULL);// execle(ps, ps, -ef, NULL, envp);// execv(/bin/ps, argv);// execvp(ps, argv);// execve(/bin/ps, argv, envp);perror(exec error); // 只有exec失败才会走到这return 1;}3、代码实验(着重讲execlp其他的都差不多)int main(){pid_t idfork();if(id0){execlp(echo,echo,我是子进程,\n,NULL);perror(exec error);}wait(NULL);printf(我是父进程\n);return 0;}可以注意到上文应该打印出两个我是父进程和一个我是子进程但是却只打印出了一个我是父进程所以说明发生进程替换的时候会将后续共享代码和数据会发生替换。所以对于子进程而言后边的代码全部都会被覆盖所以只会打印一次我是父进程。下边的实验可以验证程序替换不会创建新进程int main(){pid_t idfork();if(id0){printf(子进程的pid:%d\n,getpid());execlp(bash,bash,-c,echo exec之后的进程pid:$$,NULL);perror(exec error);}return 0;}扩展execlp(bash,bash,-c,ls -l;echo 我是一个bash进程,NULL);可以去做更多的命令可以去试试。这个是把子进程变成一个bash解释器可以动手尝试一下。后续将写一个迷你版的shell通过这个可以将最近讲的可以串联起来喜欢的话可以点点赞关注关注。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价