跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C

Linux 进程管理:创建、终止与回收全流程解析

Linux 进程管理的核心流程,涵盖进程创建(fork 函数原理及返回值)、写时拷贝机制、进程终止场景与方法(exit/_exit/return 区别)、以及进程等待(wait/waitpid 解决僵尸进程)。通过代码示例和原理分析,帮助读者理解父子进程关系、状态检测及资源回收,掌握系统编程基础。

PhpPioneer发布于 2026/3/21更新于 2026/5/115 浏览
Linux 进程管理:创建、终止与回收全流程解析

一、进程的创建

1、fork 函数初识

在 Linux 中 fork 函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

进程调用 fork,当控制转移到内核中的 fork 代码后,内核做:

  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构内容拷贝至子进程,页表代码和数据可以是完全一样的
  3. 添加子进程到系统进程列表当中

fork 返回,开始调度器调度

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    printf("pid:%d Before!\n", getpid());
    fork();
    printf("pid:%d After!\n", getpid());
    return 0;
}

fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。

注意 fork 之后,谁先执行完完全由调度器决定。

2、fork 函数返回值

子进程返回 0,父进程返回的是子进程的 pid。

3、写时拷贝

父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。

写时拷贝本质是写的时候再用,是一种延时申请,按需申请。

无论是父进程还是子进程,如果想要写入,会将父进程中可写的部分改成只读,子进程继承时也是只读状态,暂时是只读状态。针对这种情况,操作系统不做异常处理,如果想要写入数据,会将页表对应的区域重新映射,然后进行写时拷贝,这样就能访问原来可写的区域。

4、创建多个进程

#include <unistd.h>
#include <stdlib.h>
#define N 5

void RunChild() {
    int cnt = 5;
    while(cnt) {
        printf("I am child:%d,ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}

int main() {
    for(int i = 0; i < N; i++) {
        pid_t id = fork();
        if(id == 0) {
            RunChild();
            exit(0);
        }
    }
    sleep(1000);
    return 0;
}

5 次循环结束后,父进程没有结束,子进程终止成为僵尸进程。父子进程谁先运行由调度器决定。

二、进程终止

1、进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

结果是否正确采用进程的退出码来进行判定。

2、进程常见退出方法

成功只有 1 种可能,但失败有多个理由。

(1)正常终止(可以通过 echo $? 查看进程退出码):

echo $?

表示最近一次进程退出时的退出码。可以通过观察退出码来判断进程是否正常结束。

  1. 从 main 返回

在 C 语言中,程序返回 0 中的 0 表示进程的退出码,表征程序的运行结果是否正确,0->success。main 函数的返回值的本质表示进程运行完成时是否是正确的结果,如果不是,可以使用不同的数字表示不同的出错原因。

进程中断父进程会关心程序的运行情况,用户可以根据错误码来找出程序中的错误。

可以改变 return 的返回值

第二次调用 echo $? 返回值成为 0。当第 2 次输出时,程序变成了 echo 命令,echo 上次执行是正确的,所以退出码为 0。

strerror

将错误码转换成错误码描述。

示例 1

系统提供的错误码和错误码描述是有对应关系的。错误码用来表征错误原因,错误码描述展现更详细的错误信息。

示例 2

可以自己定义错误码。

errno

最近一次的错误码。

示例

3、代码异常

本质可能就是代码没有跑完。进程的退出码无意义,不关心退出码了。进程出现了异常,本质是进程收到了对应的信号。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main() {
    int* p = NULL;
    *p = 100;
    return 0;
}

访问野指针,进程抛出异常显示段错误,对应第 11 号。

验证

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main() {
    while(1) {
        printf("Hello world,pid:%d\n", getpid());
        sleep(1);
    }
    return 0;
}

第 11 个信号是进程出现了段错误。

4、exit 和 return 的区别

return

exit

exit 在任意地方被调用,都表示调用进程直接退出,return 只表示当前函数返回,没有退出进程。

5、_exit

终止进程。

6、exit 和 _exit 的区别

exit

_exit

exit 在结束之后还做了以下工作:

  1. 执行用户通过 atexit 或 on_exit 定义的清理函数
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用 _exit

exit 是库函数,_exit 是系统调用。 printf 函数先把数据写入缓冲区中,合适的时候进行刷新。这个缓冲区绝对不在内核中。如果缓冲区在内核中,exit 和 _exit 都会刷新,但实际 _exit 没有刷新缓冲区。

三、进程等待

1、必要性

  1. 子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
  2. 僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏问题。
  3. 需要得到子进程的退出情况,即知道布置给子进程的任务子进程的任务完成的怎么样,是可以选择的。

2、定义

通过系统调用 wait/waitpid,来进行对子进程进行状态检测与回收的功能。

3、回收

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main() {
    pid_t id = fork();
    if(id < 0) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(0);
    } else {
        while(1) {
            printf("I am father,pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
    }
    return 0;
}

子进程退出后一直成为僵尸状态。父进程通过调用 wait/waitpid 来进行僵尸进程回收问题。

4、wait

wait 是系统调用接口,等待进程直到进程的状态发生改变。

1 个进程

#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) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(0);
    } else {
        int cnt = 10;
        while(cnt) {
            printf("I am father,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        pid_t ret = wait(NULL);
        if(ret == id) {
            printf("wait success,ret:%d\n", ret);
        }
    }
    return 0;
}

当父进程的循环结束之后,子进程被回收。 在子进程成为僵尸状态以后,父进程等待是必须的。wait 等待任意一个子进程退出。

多个进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#define N 10

void RunChild() {
    int cnt = 5;
    while(cnt) {
        printf("I am child,pid:%d,ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}

int main() {
    for(int i = 0; i < N; i++) {
        pid_t id = fork();
        if(id == 0) {
            RunChild();
            exit(0);
        }
        printf("create child process:%d success\n", id);
    }
    sleep(10);
    for(int i = 0; i < N; i++) {
        pid_t id = wait(NULL);
        if(id > 0) {
            printf("wait %d success\n", id);
        }
    }
    sleep(5);
    return 0;
}

wait 当任意一个子进程退出的时候,wait 回收子进程。 如果任意一个子进程不退出,父进程默认在 wait 的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态。

5、waitpid

当 waitpid 回收子进程时返回的是子进程的 pid。

status

#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) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(1);
    } else {
        int cnt = 10;
        while(cnt) {
            printf("I am father,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret == id) {
            printf("wait success,ret:%d,status:%d\n", ret, status);
        }
    }
    return 0;
}

父进程等待,期望获得子进程的代码是否异常,如果没有异常,结果对吗,不对是因为什么。

int 类型总共有 32 个比特位,目前只考虑低 16 位。

上面代码中,status 是 256 的原因是因为子进程的 exit 为 1 即退出码为 1,00000000 00000000 00000001 00000000,化为十进制就是 2^8 为 256。

如果低 7 位是否为 0,如果为 0 则进程没有收到信号,则代码没有异常。

上面的代码中如果 status 为全局变量,因为父子进程具有独立性,父进程无法得到子进程的数据。父进程要拿子进程的状态数据,只能通过 wait 等系统调用来得到子进程的代码和数据。

验证

#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) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(11);
    } else {
        int cnt = 10;
        while(cnt) {
            printf("I am father,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret == id) {
            printf("wait success,ret:%d,exit sig:%d,exit code:%d\n", ret, status&0x7F, (status>>8)&0xFF);
        }
    }
    return 0;
}

6、原理

waitpid 是操作系统提供的接口,子进程退出时会将接收的信号以及 main 函数的返回值返回到 status 中,父进程通过 waitpid 得到子进程的相关信息来回收子进程。 父进程在等待时只能等待自己的子进程,不能等待其余进程,否则会等待失败。

7、WIFEXITED 和 WEXITSTATUS

可以使用 WIFEXITED 和 WEXITSTATUS 来检测进程是否正常退出。

1 个进程

#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) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(11);
    } else {
        int cnt = 10;
        while(cnt) {
            printf("I am father,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret == id) {
            if(WIFEXITED(status)) {
                printf("process success,code exit:%d\n", WEXITSTATUS(status));
            } else {
                printf("process fail\n");
            }
        }
    }
    return 0;
}

多个进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#define N 10

void RunChild() {
    int cnt = 5;
    while(cnt) {
        printf("I am child,pid:%d,ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}

int main() {
    for(int i = 0; i < N; i++) {
        pid_t id = fork();
        if(id == 0) {
            RunChild();
            exit(i);
        }
        printf("create child process:%d success\n", id);
    }
    sleep(10);
    for(int i = 0; i < N; i++) {
        int status = 0;
        pid_t id = waitpid(-1, &status, 0);
        if(id > 0) {
            printf("wait %d success,exit code:%d\n", id, WEXITSTATUS(status));
        }
    }
    sleep(5);
    return 0;
}

多个子进程被父进程回收。

Linux 的进程也是一棵多叉树结构,父进程只对直系的子进程直接负责。

8、options

阻塞方式,当 options 为 0 的时候为阻塞方式。waitpid 会导致父进程进入阻塞状态。

(1)WNOHANG

在等待过程中采用非阻塞等待。

(2)非阻塞轮询

非阻塞轮询是一种在程序中定期检查某个状态或资源是否就绪的机制,其核心特点是不会阻塞当前程序的执行流程。 每次检查操作不会'卡住'程序,如果目标未就绪,检查会立即返回,允许程序继续执行其他任务,而不是一直等待到目标就绪。

(3)代码演示
#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) {
        perror("fork");
        return 1;
    } else if(id == 0) {
        int cnt = 5;
        while(cnt) {
            printf("I am child,pid:%d,ppid:%d,cnt:%d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(11);
    } else {
        int status = 0;
        while(1) {
            pid_t ret = waitpid(id, &status, WNOHANG);
            if(ret > 0) {
                if(WIFEXITED(status)) {
                    printf("process success,code exit:%d\n", WEXITSTATUS(status));
                } else {
                    printf("process fail\n");
                }
                break;
            } else if(ret < 0) {
                printf("wait fail\n");
                break;
            } else {
                printf("子进程还没有退出,再等等...\n");
            }
            sleep(1);
        }
    }
    sleep(3);
    return 0;
}

注意 在 while 循环中必须添加 sleep(1) 这一语句。原因是,若缺少这个睡眠操作,程序会进入不间断的轮询状态,持续不断地查询子进程是否退出。这种高频次的查询会导致 CPU 资源被大量占用,进而可能造成程序运行出现卡顿现象。

通过进程等待可以保证父进程是多进程当中最后一个退出的进程。 父进程可以在等待子进程返回时做一些简单级的任务。但是父进程的核心是等待子进程返回,即延迟回收子进程,统一回收子进程。

四、总结

本文带你掌握了 fork 原理、写时拷贝、进程终止方式,以及 wait/waitpid 回收僵尸进程的方法 - - - 这些是 Linux 系统编程的基础,为后续进程通信、线程管理铺路。建议多实操修改代码加深理解。

目录

  1. 一、进程的创建
  2. 1、fork 函数初识
  3. 2、fork 函数返回值
  4. 3、写时拷贝
  5. 4、创建多个进程
  6. 二、进程终止
  7. 1、进程退出场景
  8. 2、进程常见退出方法
  9. 3、代码异常
  10. 4、exit 和 return 的区别
  11. 5、_exit
  12. 6、exit 和 _exit 的区别
  13. 三、进程等待
  14. 1、必要性
  15. 2、定义
  16. 3、回收
  17. 4、wait
  18. 5、waitpid
  19. 6、原理
  20. 7、WIFEXITED 和 WEXITSTATUS
  21. 8、options
  22. (1)WNOHANG
  23. (2)非阻塞轮询
  24. (3)代码演示
  25. 四、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 直流无刷电机 FOC 控制算法详解
  • Erupt 低代码框架基于 Java 注解的企业级应用开发方案
  • 近五年体内微/纳米机器人赋能肿瘤精准治疗综述:聚焦胶质母细胞瘤
  • Go Channel 深入解析
  • C++ 多线程进阶:互斥锁与竞态条件解析
  • llama.cpp 性能基准测试与参数调优实战指南
  • Buzz 离线语音转文字工具:Whisper 模型集成与使用指南
  • MediaPipe 与 ROS 集成:机器人动作交互系统部署
  • HarmonyOS6 RcInput 组件核心架构与类型系统设计
  • 2026 年 3 月全球 AI 前沿动态与技术突破
  • Java 高性能开发实战:Redis 7 持久化机制详解
  • 从零开始学 AI 绘画:麦橘超然部署与实战指南
  • Git 实战:如何精准合并指定分支的特定提交
  • ComfyUI Manager 完整安装与配置指南
  • OpenClaw TTS 语音合成技术详解与实战配置
  • 二分查找进阶:如何精准定位目标值的边界
  • C++ 构造数据类型知识点梳理
  • C++ 模板机制与 string 类详解
  • MacOS 快速部署 Open WebUI 的 Docker 方案
  • 力扣 Hot100 普通数组题目 Python 解法

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online