【Linux】零基础学习命名管道与共享内存
(2025–2026 年仍然最实用的两种经典进程间通信方式)
命名管道(FIFO)和共享内存(Shared Memory)是 Linux 中最基础、最常用的两种 IPC(进程间通信) 方式,但它们的适用场景、性能、复杂度、使用难度差异非常大。
下面用最直白的语言 + 对比 + 代码 + 常见误区的方式帮你快速建立认知。
一、先看对比表(强烈建议先背熟这张表)
| 维度 | 命名管道(FIFO / mkfifo) | 共享内存(shmget / shm_open) | 谁更常用(2025–2026 视角) |
|---|---|---|---|
| 是否需要亲缘关系 | 不需要 | 不需要 | — |
| 数据是否需要拷贝 | 需要(内核 → 用户 → 内核) | 不需要(直接映射到进程地址空间) | 共享内存完胜 |
| 传输方向 | 单向(默认)或双向(开两个管道) | 双向(同一块内存谁都能读写) | 共享内存更灵活 |
| 是否有阻塞 | 有(默认读写都会阻塞) | 无(除非自己加信号量/互斥锁) | 命名管道更“省心” |
| 最大数据量 | 通常 64KB 缓冲区(可调) | 理论上很大(受物理内存限制) | 共享内存容量更大 |
| 性能 | 中等(系统调用 + 拷贝) | 极高(几乎只有内存访问) | 共享内存碾压 |
| 编程复杂度 | 低(像普通文件读写) | 高(需要自己做同步、清理) | 命名管道对新手友好 |
| 典型场景 | 简单日志传递、脚本间通信、父子进程 | 高性能、大数据量、实时计算、数据库 | — |
| 是否跨机器 | 否(同一台机器) | 否(同一台机器) | — |
一句话总结选择依据:
- 要简单、数据量小、不在意性能 → 用命名管道
- 追求极致性能、数据量大、频繁读写 → 用共享内存(但要自己管同步)
二、命名管道(FIFO)——最容易上手的 IPC
创建方式(两种)
# 方法1:命令行创建(最常用)
mkfifo myfifo
# 方法2:C语言创建
mkfifo("myfifo", 0666);
最经典的父子进程通信示例
// pipe_writer.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("myfifo", O_WRONLY); // 只写方式打开
if (fd == -1) {
perror("open");
return 1;
}
char *msg = "Hello from writer!\n";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
// pipe_reader.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("myfifo", O_RDONLY); // 只读方式打开
if (fd == -1) {
perror("open");
return 1;
}
char buf[1024] = {0};
ssize_t n = read(fd, buf, sizeof(buf));
printf("Received: %s", buf);
close(fd);
return 0;
}
关键行为:
- 读端先打开 → 阻塞直到有写端打开
- 写端先打开 → 阻塞直到有读端打开
- 所有写端关闭后,读端 read() 返回 0(EOF)
- 管道缓冲区满时,写端阻塞
Shell 中最常见的用法(非常实用)
mkfifo /tmp/mypipe
# 终端1(生产者)
echo "data from terminal 1" > /tmp/mypipe
# 终端2(消费者)
cat < /tmp/mypipe
三、共享内存——性能之王,但最容易出错
两种主流方式(2025 年仍在并存)
| 方式 | API | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|
| System V 共享内存 | shmget / shmat / shmdt | 历史悠久,几乎所有系统支持 | API 古老,key 管理麻烦 | 传统应用、老项目 |
| POSIX 共享内存 | shm_open / mmap | 接口更现代,与文件类似 | 需要手动 unlink | 新项目、推荐优先使用 |
POSIX 共享内存最简示例(强烈推荐写法)
writer.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/my_shared_mem"
#define SHM_SIZE 4096
int main() {
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) { perror("shm_open"); return 1; }
ftruncate(fd, SHM_SIZE);
char *ptr = mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) { perror("mmap"); return 1; }
sprintf(ptr, "Hello from writer process! PID=%d", getpid());
printf("Data written. Press Enter to exit...\n");
getchar();
munmap(ptr, SHM_SIZE);
close(fd);
shm_unlink(SHM_NAME); // 清理(重要!)
return 0;
}
reader.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/my_shared_mem"
#define SHM_SIZE 4096
int main() {
int fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (fd == -1) { perror("shm_open"); return 1; }
char *ptr = mmap(0, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) { perror("mmap"); return 1; }
printf("Read from shared memory: %s\n", ptr);
munmap(ptr, SHM_SIZE);
close(fd);
return 0;
}
四、零基础最容易踩的 8 个大坑(强烈建议收藏)
命名管道:
- 忘记创建 FIFO(直接 open 会失败)
- 一端关闭后另一端行为没预期(读到 EOF)
- 多个写端并发写 → 数据可能交错(无原子性)
共享内存:
- 忘记同步(读写冲突 → 数据错乱 / 撕裂)
- 忘记 shm_unlink → 重启程序后 shm_open 可能失败
- mmap 失败后没检查 MAP_FAILED
- 进程崩溃后共享内存没释放(用 ipcs -m 查看残留)
- System V 与 POSIX 混用导致 key 冲突
五、快速总结一句话口诀
- 想简单、数据少、顺序读写 → 命名管道(像文件一样用)
- 要速度、数据大、频繁交互 → 共享内存(但必须自己加锁/信号量)
你现在最想搞清楚哪一部分?
- 命名管道的阻塞行为细节?
- 如何在共享内存上加互斥锁/信号量?
- System V 共享内存的写法对比?
- 实际项目中怎么选(比如日志系统、实时数据处理)?
- 想看一个带同步的完整共享内存例子?
告诉我你的具体困惑点,我可以继续展开。