进程控制:操作系统核心解析
一、进程创建
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
更多推荐


所有评论(0)