一、进程创建

1.fork函数

#include <unistd.h>
pid_t id = fork();
返回值:子进程返回0,父进程返回子进程id,出错返回-1

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

分配新的内存块和内核数据结构给子进程

将父进程部分数据结构内容拷贝至子进程

添加子进程到系统进程列表中

fork返回,开始调度器调度

2.写实拷贝

先由一段代码引出:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
  int gval = 0;
  printf("pid is %d, ppid is %d\n",getpid(),getppid());

  pid_t id = fork();

  if(id == 0)
  {
    while(1)
    {
      printf("我是子进程:pid is %d, ppid is %d, id is %d, gval is %d\n",getpid(),getppid(),id,gval);
      gval+=10;
      sleep(1);
    }
  }
  else
  {
    while(1)
    {
      
      printf("我是父进程:pid is %d, ppid is %d, id is %d, gval is %d\n",getpid(),getppid(),id,gval);
      gval++;
      sleep(1);
    }
  }
}

程序运行后,我们发现父进程和子进程中的gval互不影响。在开始时,如果父子进程都不做写入,二者共用同一块物理内存,但当子进程写入时,便以写实拷贝的方式创建副本,如下图:


二、进程终止

1.进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

2.进程常见退出方法

正常终止

可使用echo $?查看退出码,退出码为0为成功退出,非0为异常

return退出

执行return n等同于执行exit(n),因为调用main函数的运行函数会将main的返回值当做exit的参数

_exit函数
#include <unistd.h>
void _exit(int syatus);
参数:status定义了进程的终止状态,父进程通过wait来获取该值
exit函数
#include <unistd.h>
void exit(int status);

异常终止

ctrl + c


三、进程等待

1.为什么需要进程等待?

你想让父进程创建子进程执行任务,但如果父进程不做任何处理直接退出,或者一直运行却不管子进程的状态,就会引发一系列问题。进程等待的核心必要性主要体现在这三个方面:

回收子进程资源,避免僵尸进程

子进程运行结束后,内核会保留它的退出状态(比如退出码、终止信号)等信息,此时子进程会变成僵尸进程(Zombie Process)—— 已经终止但资源未被回收的进程。

  • 僵尸进程会占用系统的进程 ID(PID)、内存等资源;
  • 系统的 PID 数量是有限的,大量僵尸进程会导致系统无法创建新进程;
  • 只有父进程通过进程等待获取子进程的退出状态,内核才会彻底释放子进程的所有资源。

获取子进程的运行结果

你创建子进程的核心目的通常是让它完成特定任务,通过进程等待可以获取子进程的关键运行信息:

  • 子进程的退出码(exit code):判断任务是否执行成功(比如 0 表示成功,非 0 表示失败);
  • 子进程的终止原因:是正常退出(调用 exit/return)还是被信号终止(比如 kill -9)。

控制进程执行顺序

在很多场景下,你需要让父进程等待子进程完成任务后再继续执行(比如子进程生成数据文件,父进程需要读取该文件),进程等待可以精准控制父子进程的执行时序。

2.进程等待的核心方法

在 Linux 系统中,父进程主要通过wait()waitpid()两个系统调用实现进程等待,二者的核心功能一致,但waitpid()更灵活。

基础方法:wait ()

wait()是最基础的进程等待函数,功能是阻塞等待任意一个子进程退出,并回收其资源。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
参数与返回值
  • wstatus:输出型参数,用于存储子进程的退出状态(退出码、终止信号等),传 NULL 表示不关心退出状态;
  • 返回值:成功返回被回收子进程的 PID,失败返回 - 1(比如没有子进程)。

灵活方法:waitpid ()

waitpid()可以指定等待某个子进程,还能实现非阻塞等待,是实际开发中更常用的方式。

pid_t waitpid(pid_t pid, int *wstatus, int options);
参数详解
参数 取值说明
pid - pid > 0:等待 PID 为该值的子进程- pid = -1:等待任意子进程(等价于 wait ())- pid = 0:等待同组的任意子进程- pid < -1:等待组 ID 为 pid 绝对值的任意子进程
wstatus 同 wait (),存储子进程退出状态
options - 0:阻塞等待(默认)- WNOHANG:非阻塞等待,若没有子进程退出则立即返回 0
返回值
  • 成功:返回被回收子进程的 PID;
  • 非阻塞模式下无子进程退出:返回 0;
  • 失败:返回 - 1(并设置 errno)。

退出状态解析宏

wstatus存储的状态是二进制数据,无法直接读取,Linux 提供了专用宏来解析:

功能
WIFEXITED(status) 判断子进程是否正常退出(是返回真)
WEXITSTATUS(status) 若 WIFEXITED 为真,获取子进程的退出码(范围 0-255)
WIFSIGNALED(status) 判断子进程是否被信号终止(是返回真)
WTERMSIG(status) 若 WIFSIGNALED 为真,获取终止子进程的信号编号

3.代码实例

实例 1:基础 wait () 实现阻塞等待

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    // 创建子进程
    pid_t pid = fork();
    if (pid < 0) {
        // 子进程创建失败
        perror("fork error");
        exit(1);
    } else if (pid == 0) {
        // 子进程执行逻辑
        printf("子进程(PID: %d)开始执行,即将退出\n", getpid());
        // 子进程正常退出,退出码为10
        exit(10);
    } else {
        // 父进程执行逻辑
        int wstatus;
        // 阻塞等待任意子进程退出
        pid_t ret = wait(&wstatus);
        if (ret < 0) {
            perror("wait error");
            exit(1);
        }
        // 解析子进程退出状态
        if (WIFEXITED(wstatus)) {
            printf("父进程回收子进程(PID: %d),退出码:%d\n", ret, WEXITSTATUS(wstatus));
        } else if (WIFSIGNALED(wstatus)) {
            printf("父进程回收子进程(PID: %d),被信号%d终止\n", ret, WTERMSIG(wstatus));
        }
    }
    return 0;
}

运行结果

子进程(PID: 12345)开始执行,即将退出
父进程回收子进程(PID: 12345),退出码:10

实例 2:waitpid () 指定等待某个子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid1 = fork();
    if (pid1 < 0) {
        perror("fork pid1 error");
        exit(1);
    } else if (pid1 == 0) {
        // 子进程1
        printf("子进程1(PID: %d)执行,睡眠3秒\n", getpid());
        sleep(3); // 模拟耗时任务
        printf("子进程1退出,退出码:5\n");
        exit(5);
    }

    pid_t pid2 = fork();
    if (pid2 < 0) {
        perror("fork pid2 error");
        exit(1);
    } else if (pid2 == 0) {
        // 子进程2
        printf("子进程2(PID: %d)执行,睡眠1秒\n", getpid());
        sleep(1);
        printf("子进程2退出,退出码:8\n");
        exit(8);
    }

    // 父进程:指定等待pid2的子进程
    int wstatus;
    pid_t ret = waitpid(pid2, &wstatus, 0); // 0表示阻塞等待
    if (ret == pid2 && WIFEXITED(wstatus)) {
        printf("父进程优先回收子进程2(PID: %d),退出码:%d\n", ret, WEXITSTATUS(wstatus));
    }

    // 再等待pid1的子进程
    ret = waitpid(pid1, &wstatus, 0);
    if (ret == pid1 && WIFEXITED(wstatus)) {
        printf("父进程回收子进程1(PID: %d),退出码:%d\n", ret, WEXITSTATUS(wstatus));
    }

    return 0;
}

运行结果

子进程1(PID: 12346)执行,睡眠3秒
子进程2(PID: 12347)执行,睡眠1秒
子进程2退出,退出码:8
父进程优先回收子进程2(PID: 12347),退出码:8
子进程1退出,退出码:5
父进程回收子进程1(PID: 12346),退出码:5

实例 3:waitpid () 非阻塞等待(WNOHANG)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork error");
        exit(1);
    } else if (pid == 0) {
        // 子进程:睡眠5秒模拟耗时任务
        printf("子进程(PID: %d)开始执行,睡眠5秒\n", getpid());
        sleep(5);
        printf("子进程退出,退出码:3\n");
        exit(3);
    } else {
        // 父进程:非阻塞等待子进程
        int wstatus;
        while (1) {
            // WNOHANG:非阻塞模式
            pid_t ret = waitpid(pid, &wstatus, WNOHANG);
            if (ret == 0) {
                // 子进程还在运行,父进程可以处理其他任务
                printf("父进程:子进程还在运行,我先做自己的事...\n");
                sleep(1); // 模拟父进程的其他任务
                continue;
            } else if (ret == pid) {
                // 成功回收子进程
                if (WIFEXITED(wstatus)) {
                    printf("父进程:子进程(PID: %d)已退出,退出码:%d\n", ret, WEXITSTATUS(wstatus));
                }
                break;
            } else {
                // 等待失败
                perror("waitpid error");
                exit(1);
            }
        }
    }
    return 0;
}

运行结果

子进程(PID: 12348)开始执行,睡眠5秒
父进程:子进程还在运行,我先做自己的事...
父进程:子进程还在运行,我先做自己的事...
父进程:子进程还在运行,我先做自己的事...
父进程:子进程还在运行,我先做自己的事...
父进程:子进程还在运行,我先做自己的事...
子进程退出,退出码:3
父进程:子进程(PID: 12348)已退出,退出码:3

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐