进程间通信(IPC)
一、IPC 机制总览
| 机制 |
范围 |
特点 |
用途 |
| 管道(pipe) |
父子/兄弟 |
字节流、半双工 |
shell |
| 命名管道(FIFO) |
任意进程 |
文件系统节点 |
简单通信 |
| 消息队列 |
任意进程 |
消息边界、优先级 |
SysV/POSIX |
| 信号量 |
任意进程 |
同步原语 |
互斥/计数 |
| 共享内存 |
任意进程 |
最快、需要同步 |
大数据传输 |
| 信号(signal) |
任意进程 |
异步通知 |
事件 |
| socketpair |
父子/兄弟 |
全双工 |
fork 服务 |
| Unix domain socket |
任意进程 |
本机高性能 |
微服务 |
| 文件 |
任意进程 |
简单、需要 flock |
配置、日志 |
| mmap 临时文件 |
任意进程 |
简单高效 |
大量数据 |
| D-Bus |
任意进程 |
高层、对象化 |
桌面/Linux 服务 |
二、管道 pipe
1. 匿名管道
#include <unistd.h>
int fds[2];
pipe(fds);
pid_t pid = fork();
if (pid == 0) {
close(fds[1]);
char buf[128];
read(fds[0], buf, sizeof(buf)); // 子进程读
close(fds[0]);
_exit(0);
} else {
close(fds[0]);
write(fds[1], "hello", 5); // 父进程写
close(fds[1]);
wait(NULL);
}
2. 命名管道(FIFO)
mkfifo /tmp/myfifo
echo "hi" > /tmp/myfifo
cat /tmp/myfifo
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "data", 4);
close(fd);
3. shell 用法
ls | grep "\.c$" | wc -l
cmd1 < input > output 2> err
三、System V IPC
1. 共享内存
#include <sys/shm.h>
key_t key = ftok("/tmp", 'A');
int shmid = shmget(key, 4096, IPC_CREAT | 0666);
void *p = shmat(shmid, NULL, 0);
strcpy(p, "hello");
shmdt(p);
shmctl(shmid, IPC_RMID, NULL); // 删除
2. 消息队列
struct msgbuf {
long mtype;
char mtext[100];
};
int msqid = msgget(key, IPC_CREAT | 0666);
msgsnd(msqid, &msg, strlen(msg.mtext)+1, 0);
msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0); // mtype=1
msgctl(msqid, IPC_RMID, NULL);
3. 信号量
#include <sys/sem.h>
int semid = semget(key, 1, IPC_CREAT | 0666);
semctl(semid, 0, SETVAL, 1); // 初值 1
struct sembuf sb = { .sem_num = 0, .sem_op = -1, .sem_flg = 0 };
semop(semid, &sb, 1); // P
sb.sem_op = 1;
semop(semid, &sb, 1); // V
四、POSIX IPC(推荐,更现代)
1. POSIX 共享内存
#include <fcntl.h>
#include <sys/mman.h>
int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0666);
ftruncate(fd, SIZE);
void *p = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 使用 *p
munmap(p, SIZE);
close(fd);
shm_unlink("/my_shm");
2. POSIX 消息队列
#include <mqueue.h>
mqd_t mq = mq_open("/my_mq", O_CREAT|O_RDWR, 0666, NULL);
struct mq_attr attr;
mq_getattr(mq, &attr);
char buf[1024];
mq_send(mq, "hello", 5, 0);
mq_receive(mq, buf, sizeof(buf), NULL);
mq_close(mq);
mq_unlink("/my_mq");
3. POSIX 信号量
见 06-thread.md。
五、信号 Signal
1. 常用信号
| 信号 |
编号 |
默认动作 |
含义 |
| SIGHUP |
1 |
Term |
终端断开 / 重载配置 |
| SIGINT |
2 |
Term |
Ctrl+C |
| SIGQUIT |
3 |
Core |
Ctrl+\ |
| SIGILL |
4 |
Core |
非法指令 |
| SIGABRT |
6 |
Core |
abort |
| SIGFPE |
8 |
Core |
算术错(除零) |
| SIGKILL |
9 |
Term |
必杀(不可捕获) |
| SIGSEGV |
11 |
Core |
段错误 |
| SIGPIPE |
13 |
Term |
管道破裂 |
| SIGALRM |
14 |
Term |
alarm |
| SIGTERM |
15 |
Term |
优雅退出 |
| SIGCHLD |
17 |
Ign |
子进程状态变化 |
| SIGSTOP |
19 |
Stop |
必停(不可捕获) |
| SIGTSTP |
20 |
Stop |
Ctrl+Z |
| SIGIO / SIGPOLL |
29 |
Term |
I/O 可用 |
| SIGSYS |
31 |
Core |
非法系统调用 |
2. 处理函数
void handler(int sig) {
// 仅 async-signal-safe 函数可用!
// 不能 printf / malloc / pthread_* / stdio
const char *msg = "got\n";
write(STDOUT_FILENO, msg, 4);
}
struct sigaction sa = {
.sa_handler = handler,
.sa_flags = SA_RESTART,
};
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
3. 信号屏蔽
sigset_t set, old;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, &old); // 屏蔽
sigprocmask(SIG_SETMASK, &old, NULL); // 恢复
4. 实时信号(SIGRTMIN..SIGRTMAX)
5. 注意事项
- 在信号处理函数中只用 async-signal-safe 函数(见
signal-safety(7))
- 多线程信号处理复杂:默认发给进程,主线程接收;用
pthread_kill 发指定线程
- SIGPIPE 必须处理(写已关闭的管道)
六、socketpair / Unix Domain Socket
1. socketpair
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
// 类似 pipe,但全双工
2. Unix Domain Socket
- 路径:
/tmp/my.sock
- 类型:SOCK_STREAM(类 TCP)/ SOCK_DGRAM(类 UDP)
- 比 TCP 快(无需协议栈)
// server
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
unlink("/tmp/my.sock");
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strcpy(addr.sun_path, "/tmp/my.sock");
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
listen(fd, 5);
int cfd = accept(fd, NULL, NULL);
// ...
// client
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = { .sun_family = AF_UNIX };
strcpy(addr.sun_path, "/tmp/my.sock");
connect(fd, (struct sockaddr*)&addr, sizeof(addr));
3. abstract namespace(Linux 特性)
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0'; // 首个字节 0 = abstract
strcpy(addr.sun_path + 1, "myabstract");
七、选型建议
| 场景 |
推荐 |
| 父子进程字节流 |
pipe |
| 命令行连接 |
shell pipe |
| 任意进程大数据传输 |
共享内存 + 信号量 |
| 任意进程消息 |
POSIX 消息队列 / Unix Socket |
| 异步事件 |
signal / signalfd |
| 微服务间通信 |
Unix Socket / D-Bus |
| 跨机器 |
TCP Socket |