一句话理解:如果把进程比作一个人,那么管道就是两个人之间的一根通话管(一个人对着管子说话,另一个人在另一头听),信号就是一个人冲另一个人喊一嗓子"嘿,出事了!"。今天我们就来学会怎么让 Linux 里的进程之间也能这样沟通。
开篇:为什么需要 IPC?
过去两天我们学会了怎么创建进程(fork)、替换进程(exec)和回收进程(wait)。但你会发现,这些操作都是"纵向"的——父子进程之间除了启动和等待,几乎没有任何数据交换。
但真实世界里的程序不是孤岛。比如:
- 你在 Shell 里输入 ps aux | grep nginx,ps 的输出能自动"流入" grep 的输入,这是怎么做到的?
- 一个后台 Web 服务器收到"关机"通知后能优雅地保存数据再退出,又是怎么做到的?
答案就是 IPC(Inter-Process Communication,进程间通信)。今天学最经典的两种:
| 机制 | 通俗类比 | 核心作用是 |
| 管道(Pipe) | 两个人之间拉了一根水管,一个人倒水,另一个人在另一头接水 | 传递数据 |
| 信号(Signal) | 一个人冲另一个人喊"开饭了!",对方听到后放下手头的活去吃饭 | 发送通知 |
第一部分:管道(Pipe)—— 进程之间的"数据水管"
1.1 什么是管道?用生活场景理解
想象你和朋友在两个房间里,中间墙上有一根 PVC 水管。你往水管里塞纸条,朋友在另一头取出来——这就是管道:
- 单向:纸条只能从你这边往朋友那边走,不能倒流。想要双向聊天?那就得再装一根水管。
- 先进先出:你先塞的纸条,朋友先拿到。不会乱序。
- 满了会堵:你塞太快太多,水管满了,你就得等朋友取走一些才能继续塞。
- 空了会等:朋友来取纸条,如果水管是空的,他就得等你塞进来。
在 Linux 里,管道是内核(操作系统的核心)帮你管理的一块内存缓冲区,一端写、一端读。有两种:
| 类型 | 使用范围 | 存在方式 | 类比 |
| 匿名管道(pipe) | 只有父子进程/兄弟进程能用 | 用完就消失,不留下文件 | 两个人之间临时拉的一根线 |
| 命名管道(FIFO) | 任意进程都能用 | 在文件系统中有一个名字(像一个文件) | 小区墙上装了一个公共信箱,谁都能往里投信、取信 |
1.2 第一步:匿名管道入门 —— 父子进程单向传话
1.2.1 核心函数:pipe()
#include <unistd.h> // 包含 pipe 函数的头文件
int pipe(int pipefd[2]); // 创建一个管道,pipefd 是一个长度为 2 的整数数组
这个函数调用后:
- pipefd[0] 是管道的读端(从管道里取数据)
- pipefd[1] 是管道的写端(往管道里塞数据)
- 返回值:成功返回 0,失败返回 -1
⚠️ 新手必看——最重要的一条规则:pipe() 必须在 fork() 之前调用!因为 fork 之后子进程会继承父进程的所有文件描述符。如果在 fork 之前创建管道,父子进程就拿到了同一根管道的两端,才能通信。顺序搞反了就白忙了。
1.2.2 完整示例:子进程给父进程发消息
下面这段代码实现了:子进程往管道里写一句话,父进程从管道里读出来并打印。
第1步:打开终端,用 nano pipe_demo.c 创建新文件。
第2步:把以下代码完整输入(注释已逐行标注):
#include <stdio.h> // 标准输入输出:printf
#include <stdlib.h> // 标准库:exit
#include <unistd.h> // Unix 标准函数:pipe、fork、read、write
#include <sys/wait.h> // 等待子进程:wait
#include <string.h> // 字符串处理:strlen
int main() {
// 第3步:创建一个长度为 2 的数组,用于存放管道的两个"端口号"
int fd[2];
// 第4步:创建管道(必须在 fork 之前!)
if (pipe(fd) == -1) {
perror("pipe"); // 如果创建失败,打印错误原因
exit(1); // 并以状态码 1 退出程序
}
// 第5步:fork 一个子进程,从此开始有两个进程在跑
pid_t pid = fork();
if (pid == 0) {
// ===== 子进程的代码 =====
// 第6步:子进程只负责"写",所以关掉读端
close(fd[0]);
// 第7步:准备要发送的消息
char *msg = "Hello from child!";
// 第8步:把消息写入管道(strlen(msg)+1 是为了把字符串末尾的 '\0' 也发过去)
write(fd[1], msg, strlen(msg) + 1);
// 第9步:写完就关闭写端,表示"我说完了"
close(fd[1]);
// 第10步:子进程任务完成,退出
exit(0);
} else {
// ===== 父进程的代码 =====
// 第11步:父进程只负责"读",所以关掉写端
close(fd[1]);
// 第12步:准备一个字符数组作为"接收缓冲区"(相当于一个空盒子)
char buffer[128];
// 第13步:从管道读端读取数据,存入 buffer
int n = read(fd[0], buffer, sizeof(buffer));
// 第14步:如果读到了数据(n > 0),就打印出来
if (n > 0) {
printf("父进程收到: %s\n", buffer);
}
// 第15步:读完关闭读端
close(fd[0]);
// 第16步:等待子进程结束(收尸),防止僵尸进程
wait(NULL);
}
return 0;
}
第17步:保存文件(Ctrl+O 回车,Ctrl+X 退出)。
第18步:编译运行:
gcc pipe_demo.c -o pipe_demo # 编译,-o 指定输出文件名为 pipe_demo
./pipe_demo # 运行程序

1.2.3 这段代码到底发生了什么?(时间线图解)

⚠️ 关键规则:管道是单向的。如果要父子互相发消息(双向聊天),需要创建两根管道——一根父→子,一根子→父。今天的作业第1题就是让你做这个。
⚠️ 容易出错的地方:一定要关闭不需要的端口。如果父进程忘记 close(fd[1]),那么即使子进程写完后关闭了写端,父进程这边还有一个打开的写端,read() 就会一直等(它认为"还有人可能会写"),永远不会返回 EOF,程序就卡死了。
1.3 第二步:命名管道(FIFO)—— 没有亲戚关系也能通信
1.3.1 什么是 FIFO?
匿名管道只能父子进程用,太受限了。FIFO(First In First Out,先进先出)解决了这个问题:
- 它在文件系统中有一个名字(比如 /tmp/mypipe),看起来像一个文件
- 但它不存数据到硬盘,数据还是在内存中流转
- 任何进程只要知道这个名字,就能打开它来读或写
通俗类比:匿名管道 = 两个人之间拉的临时电话线,通话结束就拆了。FIFO = 小区门口的公共邮箱,任何人只要知道邮箱位置就可以投信或取信,邮箱本身不会消失。
1.3.2 创建 FIFO
方法一:用终端命令
mkfifo /tmp/mypipe # 在 /tmp 目录下创建一个名为 mypipe 的 FIFO
ls -l /tmp/mypipe # 查看,你会看到文件类型是 'p'(表示 pipe),不是普通文件
方法二:在 C 程序中创建
#include <sys/types.h> // 包含 mkfifo 需要的类型定义
#include <sys/stat.h> // 包含 mkfifo 函数声明
int mkfifo("/tmp/mypipe", 0666); // 创建 FIFO,权限设为 0666(所有人可读写)
1.3.3 完整示例:两个独立程序通过 FIFO 聊天
你需要写两个程序——一个负责写(writer),一个负责读(reader)。
写端程序(writer.c):
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <fcntl.h> // open 函数的 O_WRONLY 等标志
#include <unistd.h> // write, close
#include <string.h> // strlen
#include <sys/types.h> // mkfifo 需要的类型
#include <sys/stat.h> // mkfifo 函数
int main() {
// 第1步:如果 FIFO 还不存在,先创建(路径用 /tmp/myfifo)
mkfifo("/tmp/myfifo", 0666);
// 第2步:以"只写"方式打开 FIFO
// ⚠️ 这里会阻塞,直到有另一个进程以"只读"方式打开同一个 FIFO
int fd = open("/tmp/myfifo", O_WRONLY);
if (fd < 0) {
perror("open"); // 打开失败,打印错误
exit(1);
}
// 第3步:准备消息并写入
char *msg = "Hello via FIFO";
write(fd, msg, strlen(msg) + 1); // +1 把字符串结尾的 '\0' 也写进去
// 第4步:关闭文件描述符
close(fd);
return 0;
}
读端程序(reader.c):
#include <stdio.h> // printf, perror
#include <stdlib.h> // exit
#include <fcntl.h> // open 函数的 O_RDONLY 标志
#include <unistd.h> // read, close
int main() {
// 第1步:以"只读"方式打开 FIFO
// ⚠️ 这里会阻塞,直到有另一个进程以"只写"方式打开同一个 FIFO
int fd = open("/tmp/myfifo", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
// 第2步:准备缓冲区,读取数据
char buf[128];
int n = read(fd, buf, sizeof(buf));
// 第3步:如果读到了数据,打印出来
if (n > 0) {
printf("读取到: %s\n", buf);
}
// 第4步:关闭
close(fd);
return 0;
}
1.3.4 编译并观察阻塞行为
第1步:编译两个程序
gcc writer.c -o writer # 编译写端程序
gcc reader.c -o reader # 编译读端程序
第2步:打开两个终端窗口。
第3步:在终端1中先启动读端:
./reader # 你会发现程序卡住了,光标在闪烁——它在等写端打开
第4步:在终端2中启动写端:
./writer # 写端一运行,读端立刻收到数据并打印,两个程序几乎同时结束
如果/tmp/myfifo一开始没有创建,需要先启动writer。
⚠️ 重要特性:FIFO 默认是阻塞式打开的。以只读方式打开会等到有写端打开;以只写方式打开会等到有读端打开。这就像打电话——必须两边都接起来才能通话。如果想不阻塞,可以在 open 时加上 O_NONBLOCK 标志。
1.3.5 FIFO 的关键特点总结
| 特点 | 说明 |
| 有名字 | 在文件系统中存在,ls -l 能看到类型为 p |
| 不存数据到硬盘 | 数据在内存中流转,读完就没了 |
| 阻塞式打开 | 默认两边都要打开才能继续 |
| 持久存在 | 文件本身不会随进程结束而消失(需手动 rm 删除) |
第二部分:信号(Signal)—— 操作系统给进程的"紧急传呼"
2.1 什么是信号?用生活场景理解
假设你正在办公室写代码,突然有人拍你肩膀说"会议室开会了"。你停下手中的活,去开会——这就是信号:
- 你正在做的事被打断(异步,你不知道什么时候会来)
- 你可以选择忽略(继续写代码)、处理(去开会)、或者不做任何反应
在 Linux 中,信号就是内核向进程发送的一个短消息(只是一个编号,不携带数据),告诉进程"发生了一件事"。
2.2 常用信号速查表
下面这张表列出了最常见的信号,建议收藏:
| 信号名 | 编号 | 默认行为 | 什么时候触发 | 能不能拦截 |
| SIGINT | 2 | 终止进程 | 你按了 Ctrl+C | ✅ 可以捕获 |
| SIGQUIT | 3 | 终止 + 生成 Core 文件 | 你按了 Ctrl+\ | ✅ 可以捕获 |
| SIGKILL | 9 | 强制终止 | kill -9 进程号 | ❌ 不可捕获,不可忽略 |
| SIGTERM | 15 | 终止(优雅) | kill 进程号(默认) | ✅ 可以捕获 |
| SIGCHLD | 17 | 忽略 | 子进程退出时,自动发给父进程 | ✅ 可以捕获 |
| SIGALRM | 14 | 终止 | alarm() 定时器到时间了 | ✅ 可以捕获 |
| SIGSEGV | 11 | 终止 + Core | 程序访问了不该访问的内存(段错误) | ✅ 可以捕获 |
| SIGPIPE | 13 | 终止 | 往一个已经没人读的管道里写数据 | ✅ 可以捕获 |
⚠️ 一定要记住:SIGKILL(编号9)和 SIGSTOP(编号19)是"终极武器",不能被捕获、不能被忽略、不能被阻塞。这是操作系统保留的最后手段——无论你的程序在干什么,系统都能强行终止它。
2.3 第三步:用 kill 命令发送信号
2.3.1 基本用法
# 首先,找目标进程的 PID(进程 ID 号)
ps aux | grep 你的程序名 # 在进程列表中搜索
# 或者
pgrep 你的程序名 # 直接返回 PID
# 第1步:发送 SIGTERM(信号编号 15)—— 礼貌地请进程退出
kill 1234 # 默认发的就是 SIGTERM
kill -15 1234 # 明确指定发送 SIGTERM
# 第2步:发送 SIGINT(信号编号 2)—— 相当于在进程的终端按了 Ctrl+C
kill -INT 1234 # 也可以用信号名
kill -2 1234 # 或者用编号
# 第3步:发送 SIGKILL(信号编号 9)—— 强制杀死,进程无法拒绝
kill -9 1234 # 终极手段,慎用
kill -KILL 1234 # 也可以用信号名
2.3.2 在 C 程序中发送信号
#include <signal.h> // 包含 kill 函数
int kill(pid_t pid, int sig); // 向 pid 指定的进程发送 sig 信号
参数 pid 的含义:
| pid 值 | 含义 |
| > 0 | 发送给进程号为 pid 的那个进程 |
| == 0 | 发送给和当前进程同组的所有进程 |
| == -1 | 发送给所有能发送的进程(⚠️ 非常危险,几乎不用) |
2.4 第四步:捕获信号 —— 让进程对信号做出自定义响应
2.4.1 核心函数:signal()
#include <signal.h>
// 函数原型:告诉系统"当收到 signum 信号时,调用 handler 函数来处理"
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
- signum:你要捕获的信号编号或名称(如 SIGINT)
- handler:你写的处理函数的名称,或者用 SIG_IGN(忽略该信号)、SIG_DFL(恢复默认行为)
2.4.2 完整示例:让 Ctrl+C 失效
下面的程序运行后,按 Ctrl+C 不会退出,而是打印一条自定义消息:
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <signal.h> // signal、SIGINT
#include <unistd.h> // sleep、getpid
// 第1步:定义信号处理函数
// 函数签名必须是 void 函数名(int),参数是收到的信号编号
void handle_sigint(int sig) {
// ⚠️ 注意:信号处理函数里尽量少做事!这里只是演示
printf("\n收到 SIGINT(编号 %d),但我不退出!按 Ctrl+\\ 试试?\n", sig);
}
int main() {
// 第2步:注册信号处理函数——告诉系统"收到 SIGINT 时调用 handle_sigint"
signal(SIGINT, handle_sigint);
// 第3步:无限循环,每 2 秒打印一次当前进程号
while (1) {
printf("正在运行... 我的进程号 PID = %d\n", getpid());
sleep(2); // 暂停 2 秒
}
return 0;
}
第4步:编译运行:
gcc signal_demo.c -o signal_demo
./signal_demo
第5步:按 Ctrl+C,你会看到程序没有退出,而是打印了自定义消息。
第6步:怎么退出?有两种方法:
- 按 Ctrl+\(发送 SIGQUIT,默认终止,因为我们没有捕获它)
- 打开另一个终端,输入 kill -9 进程号
⚠️ 重要安全提示:信号处理函数里不能随便调用 printf!printf 不是"异步信号安全"的函数,在信号处理函数中调用它可能导致程序崩溃。上面的例子只是教学演示用,正式代码中信号处理函数应该只做:设置一个标志变量、或用 write() 系统调用(write 是异步信号安全的)。推荐的正确模式见下方 2.4.3。
2.4.3 信号处理的推荐写法(安全模式)
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// volatile:告诉编译器"这个变量可能随时被改变,每次用到都要从内存重新读"
// sig_atomic_t:保证读写是原子操作的类型(不会被信号中断打断)
volatile sig_atomic_t got_signal = 0; // 全局标志,0=未收到,1=已收到
// 信号处理函数:极其简短,只设置标志
void handle_sigint(int sig) {
got_signal = 1; // 只做这一件事:立一个旗子
}
int main() {
signal(SIGINT, handle_sigint);
while (1) {
printf("正在运行... PID=%d\n", getpid());
sleep(1);
// 在主循环中检查旗子是否被立起来了
if (got_signal) {
printf("检测到 SIGINT 信号,准备优雅退出...\n");
// 在这里做清理工作:关闭文件、释放内存等
break; // 退出循环
}
}
printf("程序正常退出。\n");
return 0;
}
这样写的好处:信号处理函数只做最简单的事(改一个变量),所有复杂的清理逻辑都在主循环中安全执行。
第三部分:信号 + 管道 —— 经典组合技
3.1 场景:父进程通知子进程"开始干活"
实际应用中,信号负责通知,管道负责传数据,两者分工明确。
生活类比:老板(父进程)先把任务文档放在员工桌上(管道写数据),然后电话通知员工"任务来了"(发信号),员工接到电话后去桌上取文档开始干活。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
volatile sig_atomic_t ready = 0; // 全局标志:0=还没准备好,1=准备好了
// 信号处理函数:只设置标志
void handle_signal(int sig) {
ready = 1;
}
int main() {
int pipe_fd[2];
pipe(pipe_fd); // 第1步:在 fork 之前创建管道
pid_t pid = fork();
if (pid == 0) {
// ===== 子进程 =====
// 第2步:注册信号处理函数,准备接收 SIGUSR1
signal(SIGUSR1, handle_signal); // SIGUSR1 是用户自定义信号1
// 第3步:子进程只读管道,关掉写端
close(pipe_fd[1]);
// 第4步:死循环等待信号,pause() 会让进程休眠直到收到任意信号
while (!ready) {
pause(); // 进程在这里睡觉,直到被信号叫醒
}
// 第5步:被信号叫醒后,从管道读取数据
char buf[256];
int n = read(pipe_fd[0], buf, sizeof(buf));
printf("子进程收到数据: %s\n", buf);
close(pipe_fd[0]);
exit(0);
} else {
// ===== 父进程 =====
// 第6步:父进程只写管道,关掉读端
close(pipe_fd[0]);
// 第7步:先往管道写数据(把"活"放在管道里)
write(pipe_fd[1], "开始干活!", 10);
close(pipe_fd[1]);
// 第8步:等 1 秒确保管道数据到位
sleep(1);
// 第9步:发送 SIGUSR1 信号,通知子进程"活来了"
kill(pid, SIGUSR1);
// 第10步:等待子进程干完活
wait(NULL);
}
return 0;
}
第四部分:实战演练 —— 用管道模拟 Shell 的 ps aux | grep
4.1 目标
模拟 Shell 命令 ps aux | grep bash。用 C 程序实现:
- 父进程 fork 出两个子进程
- 第一个子进程执行 ps aux(列出所有进程信息),输出灌入管道
- 第二个子进程从管道读取,执行 grep bash(筛选含 bash 的行)
4.2 关键新武器:dup2()
dup2 是"文件描述符重定向"函数,它的作用是让一个文件描述符指向另一个文件描述符指向的东西。
通俗类比:你原来用杯子1喝水,dup2(杯子2, 杯子1) 的效果是——杯子1现在也连到了杯子2的水源上,你再用杯子1接水,接到的和杯子2是同一个水源。
#include <unistd.h>
int dup2(int oldfd, int newfd); // 让 newfd 指向 oldfd 指向的东西
在管道场景中:
- dup2(fd[1], STDOUT_FILENO) → 让标准输出(1号文件描述符)指向管道写端。之后 printf 的输出就会进管道而不是屏幕。
- dup2(fd[0], STDIN_FILENO) → 让标准输入(0号文件描述符)指向管道读端。之后 scanf 等输入函数就会从管道读而不是键盘。
4.3 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int fd[2];
pipe(fd); // 第1步:创建管道
// 第2步:fork 第一个子进程(负责执行 ps aux)
pid_t pid1 = fork();
if (pid1 == 0) {
// ===== 子进程1:执行 ps aux =====
// 第3步:把标准输出"绑"到管道写端(之后 ps 的输出会进管道)
dup2(fd[1], STDOUT_FILENO);
// 第4步:关掉原始的文件描述符(dup2 之后这些就不需要了)
close(fd[0]);
close(fd[1]);
// 第5步:用 execlp 执行 ps aux 命令
// execlp 会在 PATH 中搜索 ps 程序,传入参数 "ps" 和 "aux"
execlp("ps", "ps", "aux", NULL);
// 如果 execlp 执行失败,才会走到这里
perror("execlp ps");
exit(1);
}
// 第6步:fork 第二个子进程(负责执行 grep bash)
pid_t pid2 = fork();
if (pid2 == 0) {
// ===== 子进程2:执行 grep bash =====
// 第7步:把标准输入"绑"到管道读端(之后 grep 会从管道读数据)
dup2(fd[0], STDIN_FILENO);
// 第8步:关掉原始的文件描述符
close(fd[1]);
close(fd[0]);
// 第9步:执行 grep bash
execlp("grep", "grep", "bash", NULL);
perror("execlp grep");
exit(1);
}
// ===== 父进程 =====
// 第10步:父进程不参与通信,关掉管道两端
close(fd[0]);
close(fd[1]);
// 第11步:等待两个子进程都结束
wait(NULL);
wait(NULL);
printf("父进程:两个子进程都已完成。\n");
return 0;
}
常见问题速查表
以下是新手最容易踩的坑,建议收藏,出问题时先来这里找:
| 问题现象 | 可能原因 | 解决方案 |
| 程序卡死不动,read() 永远不返回 | 有进程还持有管道写端没有关闭 | 用 ps aux 检查是否有进程残留;确保所有进程在使用完后 close() 写端 |
| 程序收到 SIGPIPE 信号后崩溃退出 | 往一个读端已经关闭的管道写数据 | 在写之前检查读端是否还在;或者用 signal(SIGPIPE, SIG_IGN) 忽略该信号 |
| 信号处理函数中调用 printf 导致程序崩溃 | printf 不是异步信号安全的函数 | 改用 write(STDERR_FILENO, msg, len);最佳做法是只在信号处理函数中设置 volatile sig_atomic_t 标志 |
| FIFO 的 open() 一直阻塞 | 另一端还没有进程打开同一个 FIFO | 先启动读端再启动写端(或反过来);或者在 open 时加 O_NONBLOCK 标志 |
| 创建 FIFO 时提示 Permission denied | 目录权限不够或路径不存在 | 确保 /tmp 目录存在且有写权限;用 ls -ld /tmp 检查目录权限 |
| signal() 在不同 Linux 发行版上行为不一致 | signal() 的实现在不同 Unix 系统上有差异 | 生产代码中推荐使用 sigaction()(Day 20+ 会讲)替代 signal() |
| 程序退出后 FIFO 文件还在 | FIFO 不会随进程自动删除 | 用 rm /tmp/myfifo 手动删除;或在程序中用 unlink() 删除 |
| kill -9 杀不掉进程 | 进程处于不可中断睡眠状态(D 状态),通常是在等待 I/O | 等待 I/O 完成,或重启系统(极少见) |
| 多个 Ctrl+C 同时按下,信号可能丢失 | 普通信号不排队,相同信号连续到达时只保留一个 | 如果需要可靠信号传递,使用实时信号(SIGRTMIN+)配合 sigaction |
关键知识关系表
| 你学过的 | 和今天的关系 |
| fork() | pipe() 必须在 fork() 之前创建,子进程才能继承管道 |
| exec() 系列 | exec 不会改变文件描述符表,所以管道在 exec 后仍然有效,这就是 Shell 管道能工作的原因 |
| wait() | 父进程用 wait() 回收子进程,避免僵尸进程。SIGCHLD 信号也可以在子进程退出时通知父进程 |
| dup2() (今天新学) | 将标准输入/输出重定向到管道,让普通程序不用改代码就能参与管道通信 |
今日总结
| 概念 | 一句话总结 | 关键函数 |
| 匿名管道 pipe | 父子进程间的单向数据通道 | pipe(fd) → fork() → close 不用的端 → read/write |
| 命名管道 FIFO | 有文件系统名字的管道,任意进程可用 | mkfifo(path, mode) → open() → read/write |
| 信号 signal | 操作系统发给进程的异步通知 | kill(pid, sig) 发送;signal(sig, handler) 捕获 |
| dup2 重定向 | 把标准输入/输出"接"到管道上 | dup2(fd[0], STDIN_FILENO) 或 dup2(fd[1], STDOUT_FILENO) |
下期,我们将进入更高级的 IPC 方式:共享内存(Shared Memory) 和 消息队列(Message Queue),它们能让进程以更快的速度交换大批量数据。
作业(Day 19 巩固)
以下题目请动手实操,只看不练是学不会的。
作业1:父子双向对话(必做)
用两根匿名管道实现父子进程间的"对话":
- 子进程通过管道1发送一条消息给父进程
- 父进程收到后打印,然后通过管道2发回一条确认消息
- 子进程收到确认后打印并退出
提示:需要两个 int fd1[2], fd2[2],注意每端只关掉不需要的方向。
作业2:FIFO 阻塞实验(必做)
创建两个独立的程序(不是父子进程):
- 一个写 FIFO,一个读 FIFO
- 先启动读端,观察它如何阻塞等待
- 再启动写端,观察两者如何同时完成
作业3:优雅退出(必做)
编写一个程序:
- 捕获 SIGTERM 信号
- 在信号处理函数中设置标志,主循环检测到标志后打印"正在保存数据... 准备退出"
- 安全退出(exit(0))
- 写另一个程序或用 kill 命令向它发送 SIGTERM,验证行为
作业4:挑战题 —— 自己实现 tee
tee 命令的功能:从标准输入读数据,同时输出到屏幕和文件。请用管道和信号实现一个简化版,并能响应 Ctrl+C 正常退出。
:管道与信号 —— 让进程“聊起来“和“喊起来“&spm=1001.2101.3001.5002&articleId=163298309&d=1&t=3&u=49ccfb19f2204f3e828b316ad5e8571b)
4963

被折叠的 条评论
为什么被折叠?



