(子)进程管理
父进程还没执行完毕,子进程提前退出了,这种情况下会进入僵尸态。僵尸态会释放绝大部分资源,但在内核会保留一条用于让父进程 wait() 的记录。0,等待进程ID为pid的特定子进程。等待I/O、sleep、wait。无法停止后台运行的程序。0是结束的子进程PID。磁盘I/O、硬件操作。可能很长(依赖硬件)
·
文章目录
进程状态

- 就绪态:万事俱备,只欠东风,这里的东风指的是CPU
- 运行态:指的是正在干活的进程
- 浅度睡眠(S):可中断睡眠,可被信号唤醒:收到信号(如SIGINT、SIGTERM)会立即唤醒
- 常见场景:
- 等待用户输入(read())
- 等待网络数据(recv())
- sleep()函数调用
- 等待子进程结束(wait())
- 常见场景:
- 深度睡眠(D):不可中断睡眠,不能被信号唤醒,即使是kill -9也无法终止,通常很短暂,但可能长时间阻塞
- 常见场景:
- 磁盘I/O操作
- 某些网络文件系统操作
- 等待某些内核操作完成
- 常见场景:
| 特性 | 浅度睡眠 (S) | 深度睡眠 (D) |
|---|---|---|
| 状态标志 | S | D |
| 可中断性 | ✓ 可被信号中断 | ✗ 不可被信号中断 |
| kill -9 | 可以杀死 | 无法立即杀死 |
| 常见原因 | 等待I/O、sleep、wait | 磁盘I/O、硬件操作 |
| 持续时间 | 通常较短 | 可能很长(依赖硬件) |
| 系统负载 | 不计入负载 | 会计入负载平均 |
| 解决方法 | 发信号、等待 | 修复硬件/重启 |
- 停止态:进程处于暂停状态,被称为停止态
- 僵尸态:父进程还没执行完毕,子进程提前退出了,这种情况下会进入僵尸态。僵尸态会释放绝大部分资源,但在内核会保留一条用于让父进程 wait() 的记录
进程状态实验
- 快捷键:
Ctrl+c: 终止当前运行的前台进程Ctrl+z: 停止当前运行的前台进程
- 命令:
fg命令:将进程恢复到前台继续运行bg命令:将进程恢复到后台继续运行
第一步:准备一个简单的C程序
- 代码如下:
#include <stdio.h>
void delay(int i) {
int t;
while(i--) {
t = 1000*1000;
while(t--);
}
}
int main(int argc, const char *argv[])
{
while(1) {
printf("1");
fflush(stdout);
delay(1000);
}
return 0;
}
- gcc编译生成一个
test的可执行程序文件:
gcc test.c -o test -Wall
第二步:运行test,并监控这个进程
- 可以后台方式运行:
$ ./test &
[1] 3351
- 另外打开一下终端,运行
top -p 3351(3351进程号) - 调整窗口后看到类似下面的界面:

第三步:观察进程的运行状态
fg将进程恢复到前台继续运行,Ctrl+z停止进程的运行,此是看到的应该是进程状态应该是T

备注:
Ctrl+z无法停止后台运行的程序
- 再将输入
fg命令将进程恢复到前台继续运行,输入Ctrl+c终止进程,此时进程消失

top命令可以按
q退出
第四步:准备另一下进程观察阻塞态的进程
- 刚才的代码修改为:
#include <unistd.h>
int main(int argc, const char *argv[])
{
while(1)sleep(1);
return 0;
}
- 再次编译:
gcc test.c -o test -Wall
- 后台运行,并且用top命令进行监控
$ ./test &
[1] 3410
$ top -p 3410

- 注意进程的状态是
S
僵尸进程
- 一个已经终止执行,但其退出状态还没有被父进程读取的进程,即父进程没调用 wait()
- 就像人死了但没下葬z
正常进程 → 结束执行 → 变成僵尸 → 父进程读取状态 → 完全消失
(已死) (没埋) (收尸) (埋了)
- 制造僵尸进程
$ ./test &
[1] 3809
$ ps
PID TTY TIME CMD
2507 pts/0 00:00:00 bash
3809 pts/0 00:00:00 test
3810 pts/0 00:00:00 test <defunct> // 僵尸进程
3811 pts/0 00:00:00 ps
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
switch( fork() ) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
return 1;
break;
default:
}
while(1) sleep(1);
}
特点
- 释放了内存
- 关闭了打开的文件
- 停止了CPU使用
- 无法被kill杀死(因为僵尸进程已经死了,无法再"杀死"一次)
- 但仍保留最小信息:
- 在进程表中占有一个条目
- 保留进程ID(PID)
- 保留退出状态码
- 保留资源使用统计信息
作用
- 僵尸进程是一个特性,不是bug
- 保存退出信息:让父进程知道子进程是怎么结束的
- 保留PID:防止PID被立即重用导致混乱
- 资源统计:记录进程的资源使用情况
危害
- 占用有限的进程号
cat /proc/sys/kernel/pid_max- 系统最大进程数,通常 32768
- 如果僵尸进程太多,可能耗尽PID
- 浪费系统资源
- 每个僵尸进程占用一个进程表条目
- 进程表大小有限(通常几千个)
- 影响系统性能
- 大量僵尸进程会使 ps、top 等命令变慢
- 可能影响新进程的创建
wait()函数
函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
参数
- status 包含的信息可以通过宏提取
int status;
pid_t pid = wait(&status);
返回值
- 成功:
- 结束的子进程PID (>0)
- 失败:
- -1,调用失败设置errno
errno == ECHILD// 调用进程没有子进程errno == EINTR// 调用被信号中断
- -1,调用失败设置errno
作用
- 阻塞等待子进程结束
- 获取子进程退出状态
- 回收子进程资源(防止僵尸进程)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程开始工作...\n");
sleep(3); // 模拟工作3秒
printf("子进程结束\n");
exit(42); // 以状态码42退出
} else {
// 父进程
printf("父进程等待子进程...\n");
int status;
pid_t child_pid = wait(&status); // 阻塞在这里
printf("等到了!子进程 PID=%d 结束\n", child_pid);
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
常用状态检查宏
// 判断退出方式
WIFEXITED(status) // 子进程正常退出返回真
WIFSIGNALED(status) // 子进程被信号终止返回真
WIFSTOPPED(status) // 子进程被信号暂停返回真
WIFCONTINUED(status) // 子进程从暂停恢复返回真
// 获取具体信息
WEXITSTATUS(status) // 获取正常退出时的退出码(0-255)
WTERMSIG(status) // 获取导致终止的信号编号
WSTOPSIG(status) // 获取导致暂停的信号编号
WCOREDUMP(status) // 检查是否生成core dump文件
正常退出
WIFEXITED(status)- 如果子进程正常终止,则返回
true
- 如果子进程正常终止,则返回
WEXITSTATUS(status)- 返回 Child 的退出状态
- 包括子节点在调用
exit()或_exit()时指定的wstatus参数的最低有效 8 位 - 作为
return语句的参数指定的返回值
if (WIFEXITED(status)) {
printf("正常退出,退出码: %d\n", WEXITSTATUS(status));
}
被信号终止
WIFSIGNALED(wstatus)- 如果子进程被 信号 终止,则返回
true
- 如果子进程被 信号 终止,则返回
WTERMSIG(wstatus)- 返回导致子进程终止的信号的编号
if (WIFSIGNALED(status)) {
printf("被信号杀死,信号: %d\n", WTERMSIG(status));
#ifdef WCOREDUMP
if (WCOREDUMP(status)) {
printf("产生了core dump文件\n");
}
#endif
}
WIFSTOPPED(status)- 被信号暂停
if (WIFSTOPPED(status)) {
printf("被信号暂停,信号: %d\n", WSTOPSIG(status));
}
WIFCONTINUED(status)- 从暂停状态恢复
if (WIFCONTINUED(status)) {
printf("从暂停状态恢复\n");
}
应用场景
获取子进程退出状态
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程:模拟不同退出方式
printf("子进程 PID=%d 启动\n", getpid());
// 测试不同场景(取消注释一个):
// 场景1:正常退出
// exit(100);
// 场景2:除零错误(产生SIGFPE信号)
// int a = 1 / 0;
// 场景3:段错误(产生SIGSEGV信号)
// int *p = NULL;
// *p = 42;
// 场景4:自己发送信号
// kill(getpid(), SIGTERM);
sleep(1);
exit(100);
} else {
// 父进程
int status;
wait(&status);
printf("\n=== 子进程状态分析 ===\n");
printf("子进程 PID: %d\n", pid);
if (WIFEXITED(status)) {
printf("退出方式: 正常退出\n");
printf("退出码: %d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
printf("退出方式: 被信号终止\n");
printf("信号编号: %d\n", WTERMSIG(status));
printf("信号名称: %s\n", strsignal(WTERMSIG(status)));
#ifdef WCOREDUMP
if (WCOREDUMP(status)) {
printf("生成core文件: 是\n");
}
#endif
}
else if (WIFSTOPPED(status)) {
printf("退出方式: 被信号暂停\n");
printf("暂停信号: %d\n", WSTOPSIG(status));
}
}
return 0;
}
等待多个子进程
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
int num_children = 5;
pid_t children[5];
// 创建5个子进程
for (int i = 0; i < num_children; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d (PID=%d) 开始\n", i, getpid());
sleep(i + 1); // 第i个子进程睡眠i+1秒
printf("子进程 %d 结束\n", i);
exit(i * 10); // 返回不同的退出码
} else {
// 父进程记录子进程PID
children[i] = pid;
}
}
// 父进程等待所有子进程
printf("\n父进程 (PID=%d) 开始等待...\n", getpid());
int completed = 0;
while (completed < num_children) {
int status;
pid_t child_pid = wait(&status); // 等待任意子进程
// 找出是哪个子进程结束了
for (int i = 0; i < num_children; i++) {
if (children[i] == child_pid) {
printf("子进程 %d (PID=%d) 结束,退出码: %d\n",
i, child_pid, WEXITSTATUS(status));
children[i] = -1; // 标记为已结束
break;
}
}
completed++;
}
printf("所有子进程都已回收\n");
return 0;
}
等待全部子进程结束
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
int time = 4; //进程数量,可以随意调整
pid_t pid = 1;
while(time-- && pid > 0) {
switch( (pid = fork()) ){
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
printf("做点别的事情\n");
sleep(1);
exit(EXIT_FAILURE);
break;
default:
}
}
//....
while( wait(NULL) > 0) {
perror("wait");
}
return 0;
}
waitpid()函数
函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数
- pid:
-
0,等待进程ID为pid的特定子进程
- 0,等待与调用进程同一进程组的任意子进程
- -1,等待任意子进程,等价于 wait(&status)
- < -1,等待进程组ID等于pid的所有子进程
-
- wstatus:
- 同wait函数
// 和wait()的status参数完全一样
int status;
pid_t result = waitpid(pid, &status, options);
// 使用相同的宏提取信息
if (WIFEXITED(status)) {
printf("退出码: %d\n", WEXITSTATUS(status));
}
- options:
- 0 ,默认选项,阻塞等待,子进程不结束就卡住
WNOHANG非阻塞,立即返回,不等待WUNTRACED返回停止的子进程状态信息WCONTINUED返回恢复执行的子进程的状态信息
// 阻塞等待任意子进程结束(默认)
waitpid(-1, &status, 0);
// 非阻塞检查(立即返回)
waitpid(-1, &status, WNOHANG);
// 非阻塞,同时监控停止和恢复状态
waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
返回值
-
0是结束的子进程PID
- 0,仅当options包含WNOHANG,没有子进程状态改变
- -1,调用失败设置errno
- ECHILD // 指定的pid没有对应的子进程
- EINTR // 调用被信号中断
- EINVAL // 无效的options参数
int status;
pid_t result;
// 场景1:阻塞等待,等到子进程结束
result = waitpid(pid, &status, 0);
// result > 0: 子进程PID
// result = -1: 错误
// 场景2:非阻塞检查
result = waitpid(pid, &status, WNOHANG);
// result > 0: 有子进程结束,返回PID
// result = 0: 子进程还在运行,没结束
// result = -1: 错误
// 场景3:等待特定进程组
result = waitpid(-1234, &status, 0); // 等待PGID=1234的所有子进程
// result > 0: 返回结束的子进程PID(可能是组内任何一个)
作用
- 加强版wait()函数
- 等待指定子进程
- 非阻塞等待(WNOHANG选项)
- 等待进程组(pid < -1)
wait(&wstatus)的调用与waitpid(-1, &wstatus, 0)等价
应用场景
等待指定子进程
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t child1, child2;
child1 = fork();
if (child1 == 0) {
// 第一个子进程
printf("子进程1 (PID=%d) 启动\n", getpid());
sleep(2);
exit(10);
}
child2 = fork();
if (child2 == 0) {
// 第二个子进程
printf("子进程2 (PID=%d) 启动\n", getpid());
sleep(4);
exit(20);
}
// 父进程:先等待子进程2
printf("父进程先等待子进程2 (PID=%d)\n", child2);
int status;
pid_t result = waitpid(child2, &status, 0);
if (result == child2) {
printf("子进程2 结束,退出码: %d\n", WEXITSTATUS(status));
}
// 再等待子进程1
printf("父进程再等待子进程1 (PID=%d)\n", child1);
waitpid(child1, &status, 0);
printf("子进程1 结束,退出码: %d\n", WEXITSTATUS(status));
return 0;
}
等待任意子进程
// 等待任意子进程,等价于 wait(&status)
waitpid(-1, &status, 0);
非阻塞等待
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程长时间工作
printf("子进程开始长时间工作...\n");
sleep(10);
printf("子进程结束\n");
exit(100);
} else {
// 父进程:非阻塞轮询
printf("父进程开始非阻塞等待\n");
int status;
int attempts = 0;
while (1) {
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
// 子进程还没结束
attempts++;
printf("轮询第 %d 次: 子进程还在运行\n", attempts);
sleep(1); // 做其他工作
// 模拟父进程的工作
if (attempts % 3 == 0) {
printf("父进程正在处理其他任务...\n");
}
}
else if (result == pid) {
// 子进程结束
printf("子进程结束!退出码: %d\n",
WEXITSTATUS(status));
printf("总共轮询了 %d 次\n", attempts);
break;
}
else {
// 错误
perror("waitpid");
break;
}
}
}
return 0;
}
检查是否有子进程结束
int status;
pid_t result = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
if (result == 0) {
// 没有子进程状态改变
} else if (result > 0) {
if (WIFEXITED(status)) {
// 子进程正常退出
} else if (WIFSTOPPED(status)) {
// 子进程被暂停
} else if (WIFCONTINUED(status)) {
// 子进程从暂停恢复
} else if (WIFSIGNALED(status)) {
// 子进程被信号杀死
}
}
处理所有结束的子进程
// 使用循环回收所有僵尸进程
while (waitpid(-1, &status, WNOHANG) > 0) {
// 处理每个结束的子进程
}
有 fork 就要有 wait,不然会产生僵尸进程
- wait() 是父进程的"收尸官"
- 阻塞等待子进程结束
- 回收资源防止僵尸进程
- 获取状态了解子进程"死因"
更多推荐

所有评论(0)