手把手教你写一个多进程控制器:fork + kill + wait 完整实战
手把手教你写一个多进程控制器:fork + kill + wait 完整实战
💡 本文带你逐行剖析一个经典的 Unix 多进程管理程序 —— 创建多个子进程、键盘控制杀死指定进程、优雅回收资源。涵盖
fork()、kill()、wait()核心机制,适合系统编程初学者和进阶者!
1️⃣ 程序功能预览
运行效果如下:
Bash
$ ./a.out 3
This is process No.0 and its pid is 12345
This is process No.1 and its pid is 12346
This is process No.2 and its pid is 12347
...
然后你可以:
- 按
2+ 回车 → 杀死 2 号子进程 - 按
q+ 回车 → 杀死所有剩余进程并退出
输出:
已杀死子进程 2 (PID: 12347)
所有子进程已终止,程序结束
👉 这是一个“进程上帝模拟器”—— 你创建生命,赋予编号,监听心跳,赐予死亡,最后体面安葬。
2️⃣ 完整源码展示
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/wait.h>
#define MAX_CHILD_NUMBER 10
#define SLEEP_INTERVAL 2
int proc_number = 0;
void do_something();
int main(int argc, char* argv[]) {
int child_proc_number = MAX_CHILD_NUMBER;
int i, ch;
pid_t child_pid;
pid_t pid[10] = {0};
if (argc > 1) {
child_proc_number = atoi(argv[1]);
child_proc_number = (child_proc_number > 10) ? 10 : child_proc_number;
}
for (i = 0; i < child_proc_number; i++) {
child_pid = fork();
if (child_pid < 0) {
perror("fork failed");
exit(1);
} else if (child_pid == 0) {
proc_number = i;
do_something();
exit(0);
} else {
pid[i] = child_pid;
}
}
while ((ch = getchar()) != 'q') {
if (isdigit(ch)) {
int index = ch - '0';
if (index < child_proc_number) {
if (kill(pid[index], SIGTERM) == 0) {
printf("\n已杀死子进程 %d (PID: %d)\n", index, pid[index]);
} else {
perror("kill failed");
}
}
}
}
for (i = 0; i < child_proc_number; i++) {
if (pid[i] > 0) {
kill(pid[i], SIGTERM);
}
}
while (wait(NULL) > 0);
printf("\n所有子进程已终止,程序结束\n");
return 0;
}
void do_something() {
for (;;) {
printf("This is process No.%d and its pid is %d\n", proc_number, getpid());
sleep(SLEEP_INTERVAL);
}
}
3️⃣ 零基础讲解:程序想干嘛?
想象你是“机器人指挥官” 👨💼:
- 你批量制造 N 个机器人(子进程)🤖
- 每个机器人每隔 2 秒喊一次:“我是X号,PID是YYYY!”
- 你可以按数字键,精准“销毁”某个机器人
- 最后按 q,一键清场 + 办理“离职手续”
→ 这就是一个简化版的“进程池控制器”,是 Web 服务器、数据库连接池、监控系统的原型!
4️⃣ 深度拆解:每一行代码都讲透
▶️ 第一部分:头文件 & 宏定义
#include <stdio.h> // printf, getchar
#include <unistd.h> // fork, getpid, sleep
#include <signal.h> // kill, SIGTERM
#include <sys/wait.h> // wait
...
#define MAX_CHILD_NUMBER 10 // 最多10个子进程
int proc_number = 0; // 子进程编号(fork后独立副本)
💡
proc_number是全局变量,但 fork 后每个进程有独立副本,所以安全!
▶️ 第二部分:命令行参数解析
if (argc > 1) {
child_proc_number = atoi(argv[1]);
child_proc_number = (child_proc_number > 10) ? 10 : child_proc_number;
}
./a.out 5→ 创建 5 个子进程./a.out 99→ 自动限制为 10(防数组越界)
⚠️ 改进建议:用
strtol替代atoi做错误检查。
▶️ 第三部分:创建子进程(核心!)
for (i = 0; i < child_proc_number; i++) {
child_pid = fork(); // ← 关键!返回值决定父子身份
if (child_pid == 0) { // 子进程
proc_number = i;
do_something(); // 无限循环打印
exit(0);
} else { // 父进程
pid[i] = child_pid; // 记录子进程 PID,后续控制用
}
}
🖼️ 图解 fork 过程(创建3个子进程):
父进程 PID=1000
├── i=0 → fork → child_pid=1001 → pid[0]=1001 → 子进程0干活
├── i=1 → fork → child_pid=1002 → pid[1]=1002 → 子进程1干活
└── i=2 → fork → child_pid=1003 → pid[2]=1003 → 子进程2干活
✅
child_pid是临时扫描仪,pid[]是永久档案柜!
▶️ 第四部分:用户交互控制
while ((ch = getchar()) != 'q') {
if (isdigit(ch)) {
int index = ch - '0'; // '3' → 3
kill(pid[index], SIGTERM); // 发送终止信号
printf("已杀死子进程 %d (PID: %d)\n", index, pid[index]);
}
}
💡 终端默认规范模式 → 必须按回车才触发。想“按下即响应”需设 raw mode。
▶️ 第五部分:清理阶段
// 杀死残余子进程
for (i = 0; i < child_proc_number; i++) {
if (pid[i] > 0) kill(pid[i], SIGTERM);
}
// 回收僵尸进程
while (wait(NULL) > 0);
printf("所有子进程已终止,程序结束\n");
👻 不 wait → 子进程变僵尸 → 占用 PID 和进程表项!
5️⃣ 三大核心系统调用详解:fork / kill / wait
🧬 fork() —— “生孩子”
pid_t fork(void);
- 返回 0 → 子进程
- 返回 >0 → 父进程(值为子 PID)
- 返回 <0 → 失败
🧠 底层:复制地址空间(写时复制)、文件描述符、信号处理表等。
💣 kill() —— “发信号”
int kill(pid_t pid, int sig);
SIGTERM(15):请求终止(可捕获)SIGKILL(9):强制终止(不可抗拒)kill(pid, 0):测试进程是否存在
✅ 默认行为:终止进程。可通过
signal()自定义处理。
🧹 wait() —— “收尸”
pid_t wait(int *status);
- 阻塞等待子进程结束
- 回收资源,避免僵尸
- 解析退出状态:
if (WIFEXITED(status)) {
printf("正常退出,状态码:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("被信号 %d 终止\n", WTERMSIG(status));
}
🔄 非阻塞版:
waitpid(pid, &status, WNOHANG)
6️⃣ 缺陷分析与生产级改进建议
❗ 1. atoi 不安全 → 改用 strtol
char *end;
long n = strtol(argv[1], &end, 10);
if (*end != '\0' || n <= 0 || n > 10) {
fprintf(stderr, "请输入 1~10 的数字\n");
exit(1);
}
❗ 2. 防止重复 kill
if (pid[index] > 0 && kill(pid[index], 0) == 0) {
kill(pid[index], SIGTERM);
pid[index] = 0; // 标记已杀
} else {
printf("进程已不存在\n");
}
❗ 3. 子进程优雅退出
volatile sig_atomic_t running = 1;
void handler(int sig) { running = 0; }
void do_something() {
signal(SIGTERM, handler);
while (running) {
printf("...");
fflush(stdout);
sleep(2);
}
printf("正在优雅退出...\n");
exit(0);
}
❗ 4. 输出防交错
printf("...\n");
fflush(stdout); // 强制刷新缓冲区
7️⃣ 扩展方向与学习路线
学完这个程序,你可以:
🔹 加入管道通信 → 父子进程传数据
🔹 实现进程间同步 → 信号量、共享内存
🔹 图形化/Web 控制 → 用 ncurses 或 Flask
🔹 日志系统 → 记录每个子进程生命周期
🔹 守护进程化 → 脱离终端后台运行
📚 推荐学习路径:
fork/kill/wait→pipe/fifo→shm/semaphore→select/poll→socket→epoll→ 多线程 → 并发服务器
8️⃣ 总结
✅ 这个不到 100 行的程序,涵盖了 Unix 进程管理的核心:
创建(fork)→ 控制(kill)→ 回收(wait)→ 交互(getchar)→ 资源安全(防僵尸)
它不是玩具,而是通向系统编程、服务器开发、操作系统理解的第一块真正基石!
📌 收藏 + 点赞 + 转发,让更多人看到这份深度解析!
所有评论(0)