Linux 进程通信(IPC)一站式笔记:概念 → 常用方式 → 函数原型与参数详解
文章目录
- Linux 进程通信(IPC)一站式笔记:概念 → 常用方式 → 函数原型与参数详解(含示例)
- 1. 进程为什么要通信?什么是 IPC?
- 2. 常用通信方式概览(按“用途/特点”记忆)
- 3. 信号(Signals):事件通知的万能扳手
- 3.1 快速上手示例
- 3.2 信号分类与默认动作
- 3.3 发送信号
- A) 自动发送
- B) 系统调用 `kill(2)`
- C) 命令 `kill(1)` / `pkill(1)`
- 3.4 设置信号处理
- `signal(2)`(简单)
- `sigaction(2)`(推荐,健壮)
- 3.5 信号屏蔽字(阻塞/解除/恢复)
- 3.6 补充:信号集操作小抄
- 4. 其它 IPC 方式的“怎么记、怎么用”(极简可用版)
- 4.1 匿名管道 `pipe(2)`(亲缘进程单向字节流)
- 4.2 命名管道 FIFO `mkfifo(3)`(非亲缘也可)
- 4.3 共享内存(System V 或 POSIX)
- 4.4 信号量
- 4.5 消息队列(System V)
- 4.6 本地套接字(Unix Domain Socket)
- 5. 实战建议与易踩坑
- 6. 函数与参数小抄(可快速回看)
- 信号收发
- 管道/FIFO
- 共享内存(POSIX)
- 信号量(POSIX)
- 消息队列(SysV)
- 本地套接字(UNIX)
- 7. 读完就能用:两段最小可运行骨架
- 7.1 “父发信号控制子”的骨架
- 7.2 “匿名管道抓子进程输出”的骨架
- 8. 收尾:如何选择 IPC?
Linux 进程通信(IPC)一站式笔记:概念 → 常用方式 → 函数原型与参数详解(含示例)
这篇就是“看一眼就能想起来怎么用”的那种。覆盖:为什么要通信、IPC 概念、常见 7 大方式、信号完整用法(含屏蔽/排队/挂起/恢复)、关键系统调用的原型与参数解释、易踩坑与实战建议。
1. 进程为什么要通信?什么是 IPC?
- 进程彼此隔离(独立地址空间),安全但“互相看不到”。
- IPC(Inter-Process Communication)进程间通信:为协作而在受控前提下交换数据/事件。
- 典型场景:多进程服务、日志采集、前后台任务协作、GUI 与后端守护进程交互、生产者-消费者等。
2. 常用通信方式概览(按“用途/特点”记忆)
| 方式 | 适合场景 | 主要特点 | 难度 |
|---|---|---|---|
| 信号 (signal) | 事件通知、打断系统调用 | 轻量、带语义;传统信号不排队 | 低 |
| 信号量 (semaphore) | 同步/互斥 | 计数/锁;不传数据 | 中 |
| 匿名管道 (pipe) | 亲缘进程单向字节流 | 简单、内核缓冲、半双工 | 低 |
| 命名管道 (FIFO) | 非亲缘进程字节流 | 文件路径可见、半双工 | 低 |
| 共享内存 (shm) | 大量数据高性能共享 | 最快;需配合互斥同步 | 中 |
| 消息队列 (msg queue) | 结构化消息、排队 | 有消息边界、内核队列 | 中 |
| 本地套接字 (Unix Domain Socket) | 客户端/服务器模型 | 通用可靠、双向、支持传句柄 | 中偏高 |
本文重点把信号讲细(你给的素材覆盖最全),其它方式给“记忆点+调用小抄”。
3. 信号(Signals):事件通知的万能扳手
3.1 快速上手示例
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void signal_handler(int sig) {printf("Received signal: %d\n", sig);
}int main(void) {signal(SIGINT, signal_handler); // Ctrl+C → SIGINTint count = 1;while (1) {printf("working... %d\n", count++);sleep(1);}return 0;
}
运行后按 Ctrl + C,会触发 SIGINT,进入处理函数。
3.2 信号分类与默认动作
- 非实时信号(1–31):不排队,同类信号可能合并(丢数目,只记“来过”)。
- 实时信号(32–64,宏
SIGRTMIN…SIGRTMAX):支持排队,不丢。
常见非实时信号(摘要):
| 信号 | 号 | 默认 | 说明 |
|---|---|---|---|
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 |
SIGCONT | 18 | Continue | 从停止恢复 |
默认动作简记:Term 终止、Ign 忽略、Core 终止并产生 core、Stop/Cont 停/继续。
3.3 发送信号
A) 自动发送
- 终端 Ctrl+C → SIGINT,Ctrl+Z → SIGTSTP,
fg/bg恢复会发SIGCONT。
B) 系统调用 kill(2)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig); // 返回 0 成功,-1 失败(看 errno)
例:父控子进程暂停/继续/杀死
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>int main(void){pid_t pid = fork();if (pid == 0) { // 子int c = 1;while (1) { printf("child %d\n", c++); sleep(1); }} else { // 父int n;while (scanf("%d",&n)==1){if (n==1) kill(pid, SIGSTOP);else if (n==2) kill(pid, SIGCONT);else if (n==3) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); break; }}}
}
C) 命令 kill(1) / pkill(1)
kill -TERM <PID> # 优雅结束
kill -KILL <PID> # 强制结束
pkill nginx # 按名字发信号
3.4 设置信号处理
signal(2)(简单)
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
// handler 可取:SIG_IGN(忽略)、SIG_DFL(默认)、或函数指针
返回上一次的 handler(或 SIG_ERR)。
sigaction(2)(推荐,健壮)
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);struct sigaction {void (*sa_handler)(int); // 简单处理void (*sa_sigaction)(int, siginfo_t*, void*); // 带信息处理(需 SA_SIGINFO)sigset_t sa_mask; // 处理期间需要屏蔽的信号集int sa_flags; // 行为标志void (*sa_restorer)(void); // 兼容字段,忽略
};
常用 sa_flags:
SA_RESTART:被信号打断的可重启系统调用自动重启(如read)。SA_SIGINFO:启用sa_sigaction,可获取发送者 PID、UID、附加数据等。SA_NOCLDSTOP/SA_NOCLDWAIT:影响SIGCHLD语义(子停/子退是否通知/是否产生僵尸)。
示例:带 SA_RESTART 的可重启 read
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>void sig_handler(int s){ printf("Received: %d\n", s); }int main(void){struct sigaction sa = {0};sa.sa_handler = sig_handler;sa.sa_flags = SA_RESTART; // 关键sigemptyset(&sa.sa_mask);sigaction(SIGUSR1, &sa, NULL);printf("pid=%d\n", getpid());char buf[512];while (1) {int n = read(STDIN_FILENO, buf, sizeof(buf)-1);if (n == -1) {if (errno == EINTR) printf("read interrupted\n");} else {buf[n] = 0; printf("read: %s", buf);}}
}
3.5 信号屏蔽字(阻塞/解除/恢复)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
// how: SIG_BLOCK | SIG_UNBLOCK | SIG_SETMASK
// set: 要加入/移除/覆盖的集合,oldset:返回原屏蔽字
完整流程示例:阻塞 SIGINT → 检查挂起 → 恢复
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void h(int s){ printf("SIGINT handled: %d\n", s); }int main(void){struct sigaction sa = {0};sa.sa_handler = h; sigemptyset(&sa.sa_mask);sigaction(SIGINT, &sa, NULL);puts("20s later will block SIGINT");sleep(20);sigset_t mask, oldmask;sigemptyset(&mask);sigaddset(&mask, SIGINT);sigprocmask(SIG_BLOCK, &mask, &oldmask); // 开始屏蔽puts("blocked SIGINT");sigset_t pend; sigpending(&pend);if (sigismember(&pend, SIGINT)) puts("SIGINT is pending!");puts("20s later will restore");sleep(20);sigprocmask(SIG_SETMASK, &oldmask, NULL); // **正确恢复**puts("restored");while (1) pause();
}
常见坑:恢复时不要再用
SIG_BLOCK,要么SIG_UNBLOCK指定某些信号,要么SIG_SETMASK恢复原集。
3.6 补充:信号集操作小抄
int sigemptyset(sigset_t *set); // 置空
int sigfillset(sigset_t *set); // 全 1(包含所有信号)
int sigaddset(sigset_t *set, int signum); // 加入
int sigdelset(sigset_t *set, int signum); // 删除
int sigismember(const sigset_t *set, int sig); // 是否成员
int sigpending(sigset_t *set); // 取挂起集
4. 其它 IPC 方式的“怎么记、怎么用”(极简可用版)
4.1 匿名管道 pipe(2)(亲缘进程单向字节流)
#include <unistd.h>
int pipe(int fd[2]); // fd[0]=读端, fd[1]=写端
与 fork() 配合:子进程 dup2(fd[1], STDOUT_FILENO),父进程读 fd[0]。
4.2 命名管道 FIFO mkfifo(3)(非亲缘也可)
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
// 打开用 open(),读写与普通文件一致,但遵循管道语义(阻塞/唤醒)
4.3 共享内存(System V 或 POSIX)
System V:
#include <sys/shm.h>
int shmid = shmget(key_t key, size_t size, int shmflg);
void* addr = shmat(shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf); // 删除等
POSIX(更现代):
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
int fd = shm_open("/name", O_CREAT|O_RDWR, 0600);
ftruncate(fd, size);
void* p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
munmap(p, size); close(fd); shm_unlink("/name");
同步:共享内存只共享数据,不共享“顺序”,配合互斥量/信号量。
4.4 信号量
POSIX 有名信号量:
#include <semaphore.h>
#include <fcntl.h>
sem_t* sem = sem_open("/semname", O_CREAT, 0600, 1);
sem_wait(sem); // P
sem_post(sem); // V
sem_close(sem); sem_unlink("/semname");
匿名(进程内/共享内存内):
sem_t sem;
sem_init(&sem, 1, 1); // pshared=1 表示可跨进程(需放在共享内存中)
4.5 消息队列(System V)
#include <sys/msg.h>
int msgid = msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
消息结构:
struct msgbuf { long mtype; char mtext[1]; };
4.6 本地套接字(Unix Domain Socket)
流式(SOCK_STREAM) 与 TCP 相同用法,但走内核本地协议、支持传文件描述符:
#include <sys/socket.h>
#include <sys/un.h>
// server: socket(AF_UNIX, SOCK_STREAM, 0) → bind("/tmp/sock") → listen → accept
// client: socket → connect("/tmp/sock") → read/write
传 FD 参考
sendmsg/recvmsg+SCM_RIGHTS。
5. 实战建议与易踩坑
- 信号处理函数内只做“异步信号安全”的事:设置标志、
write(2);避免malloc/printf。 SIGKILL/SIGSTOP不可捕获/忽略:不要尝试自定义它们的处理。SA_RESTART与系统调用:不是所有调用都能重启(如select有细节);处理好EINTR。- 共享内存一定配同步:信号量/互斥量/读写锁,或事件通知 + 序列号。
- 资源清理:FIFO、消息队列、共享内存段、命名信号量记得
unlink/ctl(IPC_RMID)。 - 安全:Unix 域套接字默认走文件权限模型;共享内存/队列注意创建权限。
- 调试:
strace -f -p <PID>看看谁在阻塞/被信号打断;ipcs/ipcrm管理 SysV 资源。
6. 函数与参数小抄(可快速回看)
信号收发
int kill(pid_t pid, int sig);
int sigaction(int sig, const struct sigaction *act, struct sigaction *old);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int sig); int sigdelset(sigset_t *set, int sig);
int sigismember(const sigset_t *set, int sig);
管道/FIFO
int pipe(int fd[2]);
int mkfifo(const char *path, mode_t mode);
共享内存(POSIX)
int shm_open(const char *name, int oflag, mode_t mode);
int ftruncate(int fd, off_t length);
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off);
int munmap(void *addr, size_t len);
int shm_unlink(const char *name);
信号量(POSIX)
sem_t* sem_open(const char *name, int oflag, mode_t mode, unsigned value);
int sem_close(sem_t *sem); int sem_unlink(const char *name);
int sem_wait(sem_t *sem); int sem_post(sem_t *sem);
int sem_init(sem_t *sem, int pshared, unsigned value); // 匿名
int sem_destroy(sem_t *sem);
消息队列(SysV)
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
本地套接字(UNIX)
int socket(int domain, int type, int protocol); // AF_UNIX
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t send(int fd, const void *buf, size_t len, int flags);
ssize_t recv(int fd, void *buf, size_t len, int flags);
7. 读完就能用:两段最小可运行骨架
7.1 “父发信号控制子”的骨架
pid_t pid = fork();
if (pid == 0) {while (1) { /* 工作 */ sleep(1); }
} else {kill(pid, SIGSTOP); // 暂停sleep(2);kill(pid, SIGCONT); // 继续sleep(2);kill(pid, SIGTERM); // 优雅结束waitpid(pid, NULL, 0);
}
7.2 “匿名管道抓子进程输出”的骨架
int p[2]; pipe(p);
pid_t pid = fork();
if (pid == 0) {close(p[0]); dup2(p[1], 1); // 子把 stdout 指向管道写端execlp("echo", "echo", "hello IPC", (char*)NULL);_exit(127);
}
close(p[1]); char buf[256]; ssize_t n = read(p[0], buf, sizeof buf);
write(1, buf, n); close(p[0]); waitpid(pid, NULL, 0);
8. 收尾:如何选择 IPC?
- 事件/唤醒/打断:信号(+
sigwaitinfo/signalfd也可考虑) - 少量字节流:管道/FIFO
- 大量数据:共享内存(+ 同步原语)
- 结构化消息/多生产者排队:消息队列/本地套接字
- 通用 Client/Server:本地套接字(需要传 FD 时更合适)
