Day 19 进程间通信(IPC):管道与信号 —— 让进程“聊起来“和“喊起来“

一句话理解:如果把进程比作一个人,那么管道就是两个人之间的一根通话管(一个人对着管子说话,另一个人在另一头听),信号就是一个人冲另一个人喊一嗓子"嘿,出事了!"。今天我们就来学会怎么让 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 常用信号速查表

下面这张表列出了最常见的信号,建议收藏:

信号名编号默认行为什么时候触发能不能拦截
SIGINT2终止进程你按了 Ctrl+C✅ 可以捕获
SIGQUIT3终止 + 生成 Core 文件你按了 Ctrl+\✅ 可以捕获
SIGKILL9强制终止kill -9 进程号❌ 不可捕获,不可忽略
SIGTERM15终止(优雅)kill 进程号(默认)✅ 可以捕获
SIGCHLD17忽略子进程退出时,自动发给父进程✅ 可以捕获
SIGALRM14终止alarm() 定时器到时间了✅ 可以捕获
SIGSEGV11终止 + Core程序访问了不该访问的内存(段错误)✅ 可以捕获
SIGPIPE13终止往一个已经没人读的管道里写数据✅ 可以捕获

⚠️ 一定要记住: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. 子进程通过管道1发送一条消息给父进程
  2. 父进程收到后打印,然后通过管道2发回一条确认消息
  3. 子进程收到确认后打印并退出

提示:需要两个 int fd1[2], fd2[2],注意每端只关掉不需要的方向。

作业2:FIFO 阻塞实验(必做)

创建两个独立的程序(不是父子进程):

  1. 一个写 FIFO,一个读 FIFO
  2. 先启动读端,观察它如何阻塞等待
  3. 再启动写端,观察两者如何同时完成

作业3:优雅退出(必做)

编写一个程序:

  1. 捕获 SIGTERM 信号
  2. 在信号处理函数中设置标志,主循环检测到标志后打印"正在保存数据... 准备退出"
  3. 安全退出(exit(0))
  4. 写另一个程序或用 kill 命令向它发送 SIGTERM,验证行为

作业4:挑战题 —— 自己实现 tee

tee 命令的功能:从标准输入读数据,同时输出到屏幕和文件。请用管道和信号实现一个简化版,并能响应 Ctrl+C 正常退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翎_鸢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值