1.进程创建相关 API
进程创建的核心是 fork(),还有其变体 vfork(),以及用于加载新程序的 exec() 系列函数,共同完成「创建子进程 + 执行新程序」的完整流程。
1.1.fork():创建子进程(最常用)
  • 函数原型:
pid_t fork(void);
  • 功能:
    • 从当前父进程中创建一个新的子进程,子进程是父进程的「副本」(基于写时复制 COW 机制)。
    • 这是唯一返回两次的系统调用:
      • 父进程中返回:返回值为子进程的 PID(大于 0 的整数),用于父进程管理子进程。
      • 子进程中返回:返回值为 0,子进程可通过 getppid() 获取父进程 PID。
      • 失败返回:返回 -1,并设置 errno(错误码),常见错误如 PID 耗尽、内存不足。
  • 返回值:
    • >0:父进程,返回子进程 PID,可通过 wait() / waitpid() 等待子进程终止
    • 0:子进程,无实际返回值(仅标识),可执行父进程逻辑副本,或通过 exec() 加载新程序
    • -1:创建失败,可通过 perror("fork failed") 打印错误信息
  • 示例代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 1. 调用 fork() 创建子进程
    pid_t pid = fork();

    // 2. 处理返回值,区分父子进程
    if (pid == -1) {
        // 创建失败
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程执行逻辑
        printf("【子进程】PID:%d,父进程 PID:%d\n", getpid(), getppid());
        sleep(2);  // 模拟子进程执行任务
        printf("【子进程】执行完毕,即将退出\n");
        exit(EXIT_SUCCESS);  // 子进程正常终止
    } else {
        // 父进程执行逻辑
        printf("【父进程】PID:%d,创建的子进程 PID:%d\n", getpid(), pid);
        sleep(3);  // 等待子进程执行完毕(简单方式,推荐用 wait())
        printf("【父进程】执行完毕\n");
    }

    return 0;
}
  • 编译运行结果:
gcc fork_demo.c -o fork_demo
./fork_demo

# 输出:
【父进程】PID:12345,创建的子进程 PID:12346
【子进程】PID:12346,父进程 PID:12345
【子进程】执行完毕,即将退出
【父进程】执行完毕
1.2.vfork():创建轻量级子进程(已逐步废弃)
  • 函数原型:
pid_t vfork(void);
  • 功能:
    • 简化版 fork(),创建的子进程直接共享父进程的地址空间(无 COW 机制),且父进程会被阻塞,直到子进程调用 exec() 加载新程序或 exit() 终止。
  • 优势:创建速度比 fork() 更快(无需复制 PCB 部分资源)。
  • 缺点:安全性极低,子进程修改数据会直接破坏父进程数据,容易引发程序异常,目前已被 COW 优化的 fork() 替代,仅用于兼容老旧代码。
  • 注意:
    • 子进程必须调用 exec() 或 exit(),否则会导致父进程无法继续执行。
    • 禁止子进程修改父进程的全局变量、堆 / 栈数据。
1.3.exec() 系列:子进程加载新程序(替换地址空间)
exec() 不是单个 API,而是一组函数(共 6 个),核心功能是替换子进程的当前地址空间(代码段、数据段、堆、栈),加载并执行新的可执行文件,执行成功后子进程的 PID 保持不变,仅替换内部执行逻辑。
  • 函数原型(6 个变体):
#include <unistd.h>  // 必需头文件

// 1. execl():参数列表以可变参数传入,以 NULL 结尾
int execl(const char *path, const char *arg, ... /*, (char *) NULL */);

// 2. execlp():自动从 PATH 环境变量查找可执行文件,参数列表以 NULL 结尾
int execlp(const char *file, const char *arg, ... /*, (char *) NULL */);

// 3. execle():指定环境变量数组,参数列表以 NULL 结尾
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

// 4. execv():参数列表以字符串数组传入
int execv(const char *path, char *const argv[]);

// 5. execvp():自动从 PATH 环境变量查找,参数列表以字符串数组传入
int execvp(const char *file, char *const argv[]);

// 6. execve():系统调用原语(其他 5 个都是基于它的封装),指定环境变量数组
int execve(const char *path, char *const argv[], char *const envp[]);
  • 核心共性说明:
    • 成功返回:无返回值(因为子进程地址空间已被替换,原代码逻辑不再执行)。
    • 失败返回:返回 -1,并设置 errno(如文件不存在、权限不足)。
    • 关键参数:
      • path:可执行文件的绝对路径 / 相对路径(如 /bin/ls)。
      • file:可执行文件名(如 ls),execlp()/execvp() 会从 PATH 环境变量中查找。
      • arg/argv:程序运行参数,第一个参数必须是程序名本身,最后以 NULL 结尾。
  • 常用示例:execlp() 执行 ls -l 命令
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程:调用 execlp() 执行 ls -l 命令
        printf("【子进程】即将执行 ls -l 命令\n");
        int ret = execlp("ls", "ls", "-l", NULL);  // 从 PATH 查找 ls,参数以 NULL 结尾

        // 若 execlp 执行成功,以下代码不会执行
        perror("execlp failed");  // 仅失败时执行
        exit(EXIT_FAILURE);
    } else {
        // 父进程
        printf("【父进程】等待子进程执行 ls 命令\n");
        sleep(2);
        printf("【父进程】子进程执行完毕\n");
    }

    return 0;
}
  • 编译运行结果:
gcc exec_demo.c -o exec_demo
./exec_demo

# 输出:
【父进程】等待子进程执行 ls 命令
【子进程】即将执行 ls -l 命令
total 24
-rwxr-xr-x 1 root root 8960 Oct 20 10:00 exec_demo
-rw-r--r-- 1 root root  582 Oct 20 09:58 exec_demo.c
...(其他 ls -l 输出内容)
【父进程】子进程执行完毕
2.进程等待相关 API
用于父进程等待子进程终止,回收子进程资源(避免僵尸进程),同时获取子进程的退出状态,核心是 wait() 和 waitpid()。
2.1.wait():等待任意子进程终止(阻塞式)
  • 函数原型:
pid_t wait(int *wstatus);
  • 功能:
    • 父进程调用 wait() 后会阻塞,直到任意一个子进程终止,然后回收该子进程的资源。
    • 若父进程已有子进程终止(处于僵尸态),wait() 会立即返回,回收该子进程资源。
  • 关键参数:
  • wstatus:输出参数,用于存储子进程的退出状态(如退出码、终止信号),可传入 NULL 表示不关心退出状态。
  • 返回值:
    • 成功:返回终止子进程的 PID。
    • 失败:返回 -1(如无子进程,设置 errno)。
  • 退出状态解析函数(配合 wstatus 使用)
wstatus 存储的状态无法直接读取,需通过以下宏函数解析:
  • WIFEXITED(wstatus):判断子进程是否正常终止(如 exit()、return),是则返回非 0,否则返回 0
  • WEXITSTATUS(wstatus):若 WIFEXITED为真,返回子进程的退出码(即 exit(n)中的 n,取值 0~255)
  • WIFSIGNALED(wstatus):判断子进程是否被信号终止(如 kill -9),是则返回非 0,否则返回 0
  • WTERMSIG(wstatus):若 WIFSIGNALED为真,返回终止子进程的信号编号
  • 示例:
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程:正常退出,退出码为 10
        printf("【子进程】PID:%d,即将正常退出,退出码 10\n", getpid());
        exit(10);
    } else {
        // 父进程:调用 wait() 等待子进程终止
        int wstatus;
        pid_t ret_pid = wait(&wstatus);  // 阻塞等待,获取退出状态

        if (ret_pid == -1) {
            perror("wait failed");
            exit(EXIT_FAILURE);
        }

        // 解析退出状态
        printf("【父进程】回收子进程 PID:%d\n", ret_pid);
        if (WIFEXITED(wstatus)) {
            printf("【父进程】子进程正常终止,退出码:%d\n", WEXITSTATUS(wstatus));
        } else if (WIFSIGNALED(wstatus)) {
            printf("【父进程】子进程被信号终止,信号编号:%d\n", WTERMSIG(wstatus));
        }
    }

    return 0;
}
  • 编译运行结果:
gcc wait_demo.c -o wait_demo
./wait_demo

# 输出:
【子进程】PID:12347,即将正常退出,退出码 10
【父进程】回收子进程 PID:12347
【父进程】子进程正常终止,退出码:10
2.2.waitpid():等待指定子进程终止(灵活可控)
  • 函数原型:
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 功能:比 wait() 更灵活,支持等待指定 PID 的子进程、非阻塞等待,是生产环境中的首选。
  • 关键参数:
    • pid:指定等待的子进程 PID,取值含义:
      • >0:等待 PID 等于该值的子进程(精准等待指定子进程)
      • -1:等待任意子进程(与 wait() 功能一致)
      • 0:等待与父进程同一进程组的所有子进程
      • < -1:等待进程组 ID 等于 pid 绝对值的所有子进程
    • wstatus:与 wait() 一致,存储子进程退出状态,可传入 NULL。
    • options:可选参数(可通过 | 组合),核心取值:
      • WNOHANG    非阻塞模式:若没有子进程终止,立即返回 0,不阻塞父进程
      • WUNTRACED    等待子进程暂停(状态为 T),并返回其状态
      • WCONTINUED    等待被暂停的子进程恢复运行,并返回其状态
  • 返回值:
    • 成功:返回终止 / 暂停子进程的 PID;若 WNOHANG 模式下无子进程终止,返回 0。
    • 失败:返回 -1,并设置 errno。
  • 示例代码:非阻塞等待指定子进程
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程:睡眠 3 秒后正常退出
        printf("【子进程】PID:%d,睡眠 3 秒后退出\n", getpid());
        sleep(3);
        exit(20);
    } else {
        // 父进程:非阻塞等待指定子进程
        int wstatus;
        while (1) {
            pid_t ret_pid = waitpid(pid, &wstatus, WNOHANG);  // 非阻塞模式

            if (ret_pid == -1) {
                perror("waitpid failed");
                exit(EXIT_FAILURE);
            } else if (ret_pid == 0) {
                // 子进程尚未终止,继续轮询
                printf("【父进程】子进程尚未终止,继续等待...\n");
                sleep(1);
            } else {
                // 成功回收子进程
                printf("【父进程】成功回收子进程 PID:%d\n", ret_pid);
                if (WIFEXITED(wstatus)) {
                    printf("【父进程】子进程退出码:%d\n", WEXITSTATUS(wstatus));
                }
                break;
            }
        }
    }

    return 0;
}
  • 编译运行结果:
gcc waitpid_demo.c -o waitpid_demo
./waitpid_demo

# 输出:
【父进程】子进程尚未终止,继续等待...
【子进程】PID:12348,睡眠 3 秒后退出
【父进程】子进程尚未终止,继续等待...
【父进程】子进程尚未终止,继续等待...
【父进程】成功回收子进程 PID:12348
【父进程】子进程退出码:20
3.进程终止相关 API
用于进程主动终止运行,释放资源,核心是 exit()(用户态,带清理操作)和 _exit()(内核态,直接终止)。
3.1.exit():用户态正常终止(带资源清理)
  • 函数原型:
void exit(int status);
  • 功能:进程主动正常终止,执行用户态清理操作后,调用内核态 _exit() 终止进程:
    • 刷新并关闭所有打开的标准 I/O 流(如 stdout、stderr),写入缓冲区数据。
    • 调用通过 atexit() 或 on_exit() 注册的退出处理函数。
    • 释放进程占用的用户态资源(堆、栈、全局变量等)。
    • 调用 _exit() 进入内核态,回收内核资源(PCB、文件描述符等),并通知父进程。
  • 参数:
    • status:进程退出码(0 表示正常,非 0 表示异常),父进程可通过 WEXITSTATUS() 获取。
3.2._exit():内核态直接终止(无清理操作)
  • 函数原型:
void _exit(int status);
  • 功能说明:
    • 直接进入内核态终止进程,不执行任何用户态清理操作,仅回收内核资源。
    • 适用于子进程中 exec() 执行失败后,或无需清理缓冲区的场景,避免数据冗余。
  • exit() 与 _exit() 的核心区别:
    • 所属层级:exit()是用户态(C 标准库封装),_exit()是内核态(系统调用原语)
    • 清理操作:exit()刷新 I/O 缓冲区、调用退出处理函数,_exit()无任何用户态清理操作
    • 适用场景:exit()适用于普通进程正常终止,_exit()适用于子进程、无需清理缓冲区的场景
  • 示例代码:对比缓冲区刷新差异
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    printf("测试 exit() 与 _exit() 的缓冲区差异");  // 无 \n,数据留在缓冲区

    // 注释下方一行,切换 exit() 和 _exit() 查看差异
    exit(0);  // 会刷新缓冲区,输出上述字符串
    // _exit(0);  // 不刷新缓冲区,无上述字符串输出

    return 0;
}
4.其他常用进程管理 API
4.1.getpid()/getppid():获取进程 PID / 父进程 PID
pid_t getpid(void);         // 返回当前进程的 PID
pid_t getppid(void);       // 返回当前进程的父进程 PID
4.2.kill():向进程发送信号(终止 / 暂停 / 重启等)
int kill(pid_t pid, int sig);
  • 功能:向指定 PID 的进程发送 sig 信号(如 SIGKILL(9)强制终止,SIGTERM(15)正常终止)。
  • 示例:kill(pid, 9);强制终止 PID 为 pid 的进程。
5.核心总结
  • 进程创建:核心 fork()(COW 机制高效复制)+ exec() 系列(加载新程序),fork() 唯一返回两次,exec() 成功无返回。
  • 进程等待:wait() 阻塞等待任意子进程,waitpid() 支持指定子进程和非阻塞模式,是避免僵尸进程的关键,需配合退出状态宏解析。
  • 进程终止:exit()(用户态带清理)推荐优先使用,_exit()(内核态直接终止)适用于特殊场景。
  • 核心流程:fork() 创建 → exec() 加载新程序 → exit() 终止 → 父进程 waitpid() 回收,构成 Linux 进程管理的完整闭环。
  • 这些 API 是 Linux 后端开发、运维工具开发的基础,掌握后可实现自定义进程管理逻辑(如守护进程、多进程服务)。
      Logo

      AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

      更多推荐