欢迎光临
我们一直在努力

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

大家好,我是程序员小青蛙,今天讲解进程终止和进程等待问题。

一、进程退出的场景

进程退出主要分为 3 种情况:

  • 正常终止:代码运行完毕,结果正确(如 return 0)。
  • 正常终止:代码运行完毕,结果不正确(如 return 1)。
  • 异常终止:代码没跑完,程序被信号终止(如 Ctrl+C、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. 被信号终止(异常退出)

    • 如 Ctrl+C(SIGINT)、kill -9(SIGKILL)、段错误(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);

    • 功能:等待指定的子进程退出,支持非阻塞等待。
    • 参数详解:
      • pid:
        • pid > 0:等待 pid 等于指定值的子进程。
        • pid = -1:等待任意子进程(和 wait 等价)。
        • pid = 0:等待和调用者同组的任意子进程。
        • pid < -1:等待进程组 ID 等于 |pid| 的任意子进程。
      • status:同 wait(),获取退出状态。
      • options:
        • 0:阻塞等待(默认行为)。
        • 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 7:core dump 标志
    • bit 8~15(高 8 位):进程退出码(exit code)

    第一句:st & 0x7F → 取低 7 位(信号编号)

    1. 0x7F 是什么?

    二进制:

    0111 1111

    刚好 7 个 1。

    2. 按位与 & 作用

    st & 0x7F → 只保留 最低 7 位 → 高位全部清零

    3. 这 7 位存的是什么?

    如果进程被信号杀死,这 7 位就是信号编号:

    • 9 → SIGKILL
    • 15 → SIGTERM
    • 2 → 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) 里的 10

    status 是一个 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 ( (pid=fork()) == -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", (st>>8)&0XFF);
    } else if( ret > 0 ) {//异常退出
    printf("sig code : %d\\n", st&0X7F );
    }
    }
    }

    关键点:

    • st & 0x7F:取低 7 位,信号编号在这里
    • (st>>8)&0xFF:取高 8 位,退出码在这里

    正常退出

    杀死之后异常退出

    常用宏定义

    WIFEXITED(status) // 若为真,表示子进程正常退出
    WEXITSTATUS(status) // 获取子进程的退出码(仅正常退出时有效)
    WIFSIGNALED(status) // 若为真,表示子进程被信号终止
    WTERMSIG(status) // 获取终止子进程的信号编号


    七、代码示例:进程等待的两种方式

    1. 阻塞等待(waitpid + options=0)

    #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
    • 子进程返回 0

    2. 子进程做了什么?

    else if( pid == 0 ){
    printf("child is run, pid is : %d\\n",getpid());
    sleep(5);
    exit(257);
    }

    • 打印自己的 PID
    • sleep(5):休眠 5 秒
    • exit(257):退出,退出码是 257

    3. 父进程做了什么?

    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) 为什么输出 1?

    • 257 的二进制:

      257 = 00000001 00000001

    • 退出码只保留低 8 位
    • 所以:

      257 & 0xFF = 1

    所以程序最终输出:

    wait child 5s success, child return code is :1.

    2. 非阻塞等待(waitpid + WNOHANG)

    #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
      • 如果子进程已经退出 → 返回子进程 pid

    2. 父进程做了什么?

    • 每秒轮询一次
    • 没等到就打印: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
      • 子进程已退出 → 返回 子进程 pid

    2. WIFEXITED(status)

    判断子进程是否正常退出(不是被信号杀死)

    3. WEXITSTATUS(status)

    拿到子进程的退出码


    阻塞 vs 非阻塞(总结)

    • 阻塞等待(wait /waitpid 0) 父进程一直等,啥也不干,直到子进程退出

    • 非阻塞等待(waitpid WNOHANG) 父进程每隔一段时间问一次,不等的时候可以干别的


    八、关键知识点总结

  • 进程退出方式:return、exit()、_exit()、信号终止。
  • exit() vs _exit():前者刷新缓冲区、执行清理;后者直接终止。
  • 进程等待的目的:回收僵尸进程、获取退出状态、进程同步。
  • wait() vs waitpid():wait 阻塞等待任意子进程;waitpid 支持指定进程和非阻塞等待。
  • status 解析:通过 WIFEXITED、WEXITSTATUS 等宏判断子进程退出原因。
  • 赞(0)
    未经允许不得转载:171主机测评 » 【Linux学习】Linux中进程终止和进程等待
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址