Linux 学习笔记(十八):进程控制——优雅地退出与等待

一、 进程终止:exit 与 _exit 的区别

进程终止的本质是释放系统资源。正常的退出方法有 return、exit() 和 _exit()。这三个有什么区别?重点关注 exit 和 _exit:

  • exit(int status) :标准库函数。它会先执行清理函数,刷新 C 标准库的缓冲区 ,然后把数据交给内核,最后调用 _exit 真正退出。

  • _exit(int status) :系统调用。简单粗暴,直接陷入内核退出。不会刷新缓冲区。

  • 踩坑记录 :如果 printf("hello"); 后面紧跟 _exit(0);,因为不带 \n,数据还在缓冲区没刷出来,程序就死了,屏幕上什么都不会打印!

二、 进程等待:消灭僵尸进程

子进程退出后,如果父进程不管不顾,子进程就会变成"僵尸进程",占用 PCB 且 kill -9 杀不掉。解决这个问题的终极办法就是:父进程进行进程等待。

三、 等待双子星:wait 和 waitpid

  • wait(&status):阻塞等待。父进程卡在这里,直到任意一个子进程退出。

  • waitpid(pid, &status, options):精准等待。可以指定 PID。

    • options 为 0 时,也是阻塞等待。

    • options 为 WNOHANG 时,是非阻塞等待。如果子进程还在运行,立刻返回 0,父进程可以继续干别的事(轮询模式),效率更高。

四、 解析 status 退出状态

wait 和 waitpid 的 status 参数是一个输出型参数,它不能当作普通整数看,而是一个位图:

  • 高 8 位代表子进程的退出码。

  • 低 7 位代表终止信号。

    因此,我们不能直接打印 status,而需要使用系统提供的宏:

  • WIFEXITED(status):返回真,代表子进程正常退出。

  • WEXITSTATUS(status):提取子进程真实的退出码。

五、 实战代码演示

cpp 复制代码
// 父进程阻塞等待子进程
pid_t ret = wait(&status);
if (WIFEXITED(status)) {
    printf("子进程退出码: %d\n", WEXITSTATUS(status));
}

// 父进程非阻塞轮询等待
while (1) {
    ret = waitpid(id, &status, WNOHANG);
    if (ret == 0) {
        // 子进程还在运行,父进程干点别的
    } else {
        break; // 子进程退出了
    }
}

自我实战操作

~/linux_study/proc_test 目录下写代码。

实战 1:验证 exit 与 _exit 的缓冲区区别

bash

bash 复制代码
vim exit_test.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    printf("Hello Linux"); // 注意:这里没有加换行符 \n!
    
    // 1. 尝试使用 exit(0)
    exit(0); 
    
    // 2. 注释掉上面的,换成 _exit(0) 试试
    // _exit(0);
    return 0;
}
  • 实验 A :用 exit(0) 编译运行,终端会输出 Hello Linux。

  • 实验 B :改为 _exit(0) 编译运行,终端什么都不会输出 (直接回到提示符),因为数据还在缓冲区,被 _exit 直接扼杀了。

实战 2:父进程获取子进程的退出码
bash 复制代码
vim wait_test.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main() {
    pid_t id = fork();
    if (id == 0) {
        // 子进程
        printf("我是子进程[%d],我要睡2秒,然后带着退出码 66 退出...\n", getpid());
        sleep(2);
        exit(66); // 子进程退出,退出码为 66
    } else {
        // 父进程
        printf("我是父进程[%d],我要等我的子进程...\n", getpid());
        int status = 0;
        
        // 父进程在这里阻塞等待
        pid_t ret = wait(&status); 
        
        // 检查是否正常退出,并提取退出码
        if (WIFEXITED(status)) {
            printf("等待成功!子进程PID: %d,它的退出码是: %d\n", ret, WEXITSTATUS(status));
        }
    }
    return 0;
}
  • 编译运行后,父进程会卡住 2 秒,然后精准打印出子进程的退出码 66。
实战 3:非阻塞等待(WNOHANG)
  • 修改上面 wait_test.c 的父进程部分:
cpp 复制代码
  } else {
        int status = 0;
        pid_t ret = 0;
        // 循环非阻塞等待
        while (1) {
            ret = waitpid(id, &status, WNOHANG);
            if (ret == 0) {
                printf("子进程还没死,父进程去干点别的活...\n");
                sleep(1); // 假装干了一秒钟的活
            } else {
                printf("子进程死了!退出码: %d\n", WEXITSTATUS(status));
                break;
            }
        }
    }
  • 运行后发现,父进程不再卡死,而是每秒打印一次"子进程还没死",直到子进程退出。
相关推荐
叼烟扛炮1 小时前
Linux 学习笔记(二十三):深入 Ext 文件系统(下)—— 原理、缓存与软硬链接
linux·笔记·学习
时间售卖铺1 小时前
MySQL DML 与 DQL 语句详解
运维·数据库·mysql·云原生
yhb12262 小时前
网络编程select、poll、epoll函数的使用
linux
吴声子夜歌2 小时前
Docker入门与实战——常见问题总结
运维·docker·容器
是jin奥2 小时前
最新 FFmpeg 安装脚本:Linux x86_64
linux·运维·ffmpeg
2601_967980112 小时前
Python之wxPython模块的安装
linux·windows·python·wxpython·gui设计·1024程序员节
全栈技术负责人2 小时前
AI 自动化单元测试技术方案
运维·单元测试·自动化
Shadow(⊙o⊙)2 小时前
仿muduo实现高并发服务器——EventLoop.hpp初步设计(未纳入超时管理模块)
运维·服务器
深盾科技_Virbox2 小时前
换机或重装后软件许可怎么恢复:先核对哪四项
java·服务器·数据库