【Linux学习】Linux中进程终止和进程等待

【Linux学习】Linux中进程终止和进程等待 大家好我是程序员小青蛙今天讲解进程终止和进程等待问题。一、进程退出的场景进程退出主要分为 3 种情况正常终止代码运行完毕结果正确如return 0。正常终止代码运行完毕结果不正确如return 1。异常终止代码没跑完程序被信号终止如CtrlC、kill命令。进程退出码可以通过echo $?查看$?永远记录最近一个进程的退出码。二、进程退出的 4 种方式1.main函数return返回main函数的return n本质上等同于调用exit(n)。进程退出码由return的值决定只有低 8 位有效0-255。示例return 0表示程序正常结束return 1表示程序执行出错。2. 调用exit()函数库函数#include stdlib.h void exit(int status);status进程的退出状态父进程可通过wait获取。核心特点退出前会做清理工作执行用户通过atexit/on_exit注册的清理函数。刷新所有打开的流缓冲区如printf的缓冲区。关闭所有打开的文件流。最终调用_exit()终止进程。3. 调用_exit()函数系统调用#include unistd.h void _exit(int status); 参数status定义了进程的终止状态父进程通过wait来获取该值 说明虽然status是int但是仅有低8位可以被父进程所用。所以_exit(-1)时 在终端执行$?发现返回值是255。直接终止进程不会刷新缓冲区、不执行清理函数。示例对比// 用 exit() 时缓冲区会被刷新输出 hello printf(hello); exit(0); // 用 _exit() 时缓冲区不会被刷新无输出 printf(hello); _exit(0);4. 被信号终止异常退出如CtrlCSIGINT、kill -9SIGKILL、段错误SIGSEGV。这种情况下进程退出码无意义终止原因记录在status中。三、exit()vs_exit()核心区别特性exit()_exit()类型C 标准库函数系统调用刷新缓冲区✅ 会刷新用户态缓冲区❌ 不会刷新执行清理函数✅ 执行atexit注册的函数❌ 不执行关闭文件流✅ 关闭所有打开的流❌ 直接交给内核处理适用场景正常退出程序异常情况下强制终止四、进程等待为什么必须wait必要性子进程退出父进程如果不管不顾就可能造成‘僵尸进程’的问题进而造成内存泄漏。另外进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9也无能为力因为谁也没有办法杀死一个已经死去的进程。最后父进程派给子进程的任务完成的如何我们需要知道。如子进程运行完成结果对还是不对或者是否正常退出。父进程通过进程等待的方式回收子进程资源获取子进程退出信息回收僵尸进程子进程退出后如果父进程不回收会变成僵尸进程占用系统资源。获取退出状态父进程可以通过wait知道子进程的退出码判断子进程是否正常结束。进程同步父进程可以通过wait阻塞等待子进程完成任务再继续执行。五、进程等待的两种方法1.wait()函数#include sys/types.h #include sys/wait.h pid_t wait(int *status);功能阻塞等待任意一个子进程退出回收其资源。参数status是输出型参数用于获取子进程的退出状态不关心可传NULL。返回值成功返回退出子进程的pid失败返回-1。2.waitpid()函数pid_t waitpid(pid_t pid, int *status, int options);功能等待指定的子进程退出支持非阻塞等待。参数详解pidpid 0等待pid等于指定值的子进程。pid -1等待任意子进程和wait等价。pid 0等待和调用者同组的任意子进程。pid -1等待进程组 ID 等于|pid|的任意子进程。status同wait()获取退出状态。options0阻塞等待默认行为。WNOHANG非阻塞等待若子进程未退出函数立即返回0。如果子进程已经退出调用wait/waitpid时wait/waitpid会立即返回并且释放资源获得子进程退出信息。如果在任意时刻调用wait/waitpid子进程存在且正常运行则进程可能阻塞。如果不存在该子进程则立即出错返回。六、解析status判断子进程退出原因wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。如果传递NULL表示不关心子进程的退出状态信息。否则操作系统会根据该参数将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特位分析一下这个16位st是一个 16 位的状态数字Linux 中wait(st)拿到的st是一个int 整数但真正有用的只有低 16 位15 14 13 12 11 10 9 8 │ 7 │ 6 5 4 3 2 1 0 退出码(8位) │core│ 信号号(7位)结构固定死了bit 0~6低 7 位终止信号编号bit 7core dump 标志bit 8~15高 8 位进程退出码exit code第一句st 0x7F→ 取低 7 位信号编号1. 0x7F 是什么二进制0111 1111刚好7 个 1。2. 按位与 作用st 0x7F→ 只保留最低 7 位→ 高位全部清零3. 这 7 位存的是什么如果进程被信号杀死这 7 位就是信号编号9 → SIGKILL15 → SIGTERM2 → SIGINT所以st 0x7F → 拿到杀死子进程的信号编号第二句(st 8) 0xFF→ 取高 8 位退出码1.st 8把 16 位数字向右移动 8 位。移动前高8位(退出码) 低8位(信号core) [15.............8][7.............0]右移 8 位后原来的高8位 来到了低8位 [15.............8] → 低8位2. 0xFF只保留低 8 位也就是退出码。3. 最终结果(st 8) 0xFF → 拿到子进程的 exit(10) 里的 10status是一个int类型其低 16 位包含了子进程的退出状态信息正常终止低 8 位为0高 8 位为退出码通过WEXITSTATUS(status)获取。异常终止低 7 位为终止信号通过status 0x7F获取第 8 位为core dump标志。测试代码#include sys/wait.h #include stdio.h #include stdlib.h #include string.h #include errno.h int main( void { pid_t pid; if ( (pidfork()) -1 ) perror(fork),exit(1); if ( pid 0 ){ sleep(20); exit(10); } else { int st; int ret wait(st); if ( ret 0 ( st 0X7F ) 0 ){ //正常退出 printf(child exit code:%d\n, (st8)0XFF); } else if( ret 0 ) {//异常退出 printf(sig code : %d\n, st0X7F ); } } }关键点st 0x7F取低 7 位信号编号在这里(st8)0xFF取高 8 位退出码在这里正常退出杀死之后异常退出常用宏定义WIFEXITED(status) // 若为真表示子进程正常退出 WEXITSTATUS(status) // 获取子进程的退出码仅正常退出时有效 WIFSIGNALED(status) // 若为真表示子进程被信号终止 WTERMSIG(status) // 获取终止子进程的信号编号七、代码示例进程等待的两种方式1. 阻塞等待waitpidoptions0#include stdio.h #include unistd.h #include sys/wait.h #include sys/types.h int main() { pid_t pid fork(); if (pid 0) { perror(fork); return 1; } else if (pid 0) { // 子进程 printf(child[%d] is running...\n, getpid()); sleep(3); // 模拟任务执行 exit(10); // 子进程退出退出码为10 } else { // 父进程 printf(parent[%d] waiting for child...\n, getpid()); int status; pid_t ret waitpid(pid, status, 0); // 阻塞等待 if (ret 0) { if (WIFEXITED(status)) { printf(child exited normally, exit code: %d\n, WEXITSTATUS(status)); } else { printf(child exited abnormally, signal: %d\n, status 0x7F); } } } return 0; }int main() { pid_t pid; pid fork(); if(pid 0){ printf(%s fork error\n,__FUNCTION__); return 1; } else if( pid 0 ){ //child printf(child is run, pid is : %d\n,getpid()); sleep(5); exit(257); } else{ int status 0; pid_t ret waitpid(-1, status, 0);//阻塞式等待等待5S printf(this is test for wait\n); if( WIFEXITED(status) ret pid ){ printf(wait child 5s success, child return code is :%d.\n,WEXITSTATUS(status)); }else{ printf(wait child failed, return.\n); return 1; } } return 0; }1.fork()创建子进程pid fork();调用后变成两个进程父进程返回子进程 pid子进程返回02. 子进程做了什么else if( pid 0 ){ printf(child is run, pid is : %d\n,getpid()); sleep(5); exit(257); }打印自己的 PIDsleep(5)休眠 5 秒exit(257)退出退出码是2573. 父进程做了什么pid_t ret waitpid(-1, status, 0);-1等待任意子进程status输出型参数拿到子进程退出状态0阻塞等待子进程不退出父进程就一直等4. 判断子进程是否正常退出if( WIFEXITED(status) ret pid )WIFEXITED(status)→ 为真 →子进程正常退出不是被信号杀死ret pid→ 确保等待的就是目标子进程5. 获取退出码WEXITSTATUS(status)拿到子进程exit(n)中的n只取低 8 位最重要考点exit (257) 为什么输出 1257 的二进制257 00000001 00000001退出码只保留低 8 位所以257 0xFF 1所以程序最终输出wait child 5s success, child return code is :1.2. 非阻塞等待waitpidWNOHANG#include stdio.h #include unistd.h #include sys/wait.h #include sys/types.h int main() { pid_t pid fork(); if (pid 0) { perror(fork); return 1; } else if (pid 0) { // 子进程 printf(child[%d] is running...\n, getpid()); sleep(3); exit(10); } else { // 父进程 printf(parent[%d] waiting non-blocking...\n, getpid()); int status; pid_t ret; do { ret waitpid(pid, status, WNOHANG); // 非阻塞等待 if (ret 0) { printf(child is still running, parent do other work...\n); sleep(1); } } while (ret 0); if (ret 0) { if (WIFEXITED(status)) { printf(child exited normally, exit code: %d\n, WEXITSTATUS(status)); } } } return 0; }#include stdio.h #include unistd.h #include stdlib.h #include sys/wait.h int main() { pid_t pid; pid fork(); if(pid 0){ printf(%s fork error\n,__FUNCTION__); return 1; }else if( pid 0 ){ //child printf(child is run, pid is : %d\n,getpid()); sleep(5); exit(1); } else{ int status 0; pid_t ret 0; do { ret waitpid(-1, status, WNOHANG);//非阻塞式等待 if( ret 0 ){ printf(child is running\n); } sleep(1); }while(ret 0); if( WIFEXITED(status) ret pid ){ printf(wait child 5s success, child return code is :%d.\n,WEXITSTATUS(status)); }else{ printf(wait child failed, return.\n); return 1; } } return 0; }1. 父进程非阻塞等待子进程waitpid(-1, status, WNOHANG)WNOHANG非阻塞模式如果子进程还在运行→waitpid立即返回0如果子进程已经退出→ 返回子进程pid2. 父进程做了什么每秒轮询一次没等到就打印child is running等到了就打印退出码运行结果child is run, pid is : child is running child is running child is running child is running wait child 5s success, child return code is :1.一共打印5 次 child is running因为子进程 sleep (5)父进程每秒问一次。关键函数讲解1.waitpid(-1, status, WNOHANG)-1等待任意子进程status拿到退出状态WNOHANG非阻塞等待子进程没退出 → 返回0子进程已退出 → 返回子进程 pid2.WIFEXITED(status)判断子进程是否正常退出不是被信号杀死3.WEXITSTATUS(status)拿到子进程的退出码阻塞 vs 非阻塞总结阻塞等待wait /waitpid 0父进程一直等啥也不干直到子进程退出非阻塞等待waitpid WNOHANG父进程每隔一段时间问一次不等的时候可以干别的八、关键知识点总结进程退出方式return、exit()、_exit()、信号终止。exit()vs_exit()前者刷新缓冲区、执行清理后者直接终止。进程等待的目的回收僵尸进程、获取退出状态、进程同步。wait()vswaitpid()wait阻塞等待任意子进程waitpid支持指定进程和非阻塞等待。status解析通过WIFEXITED、WEXITSTATUS等宏判断子进程退出原因。