Linux进程间通信(IPC)全解析:从管道到 Socket,一篇讲透 电子说
在 Linux 世界里,进程并非孤立存在。无论是后台服务协作(如 Web 服务器与数据库)、命令行工具联动(如ps | grep),还是复杂应用的模块通信,都离不开进程间通信(IPC,Inter-Process Communication)。
今天我们就来系统梳理 Linux 中最常用的 6 种 IPC 方式,从原理到实例,从流程到适用场景,帮你彻底搞懂进程间如何 “对话”。
一、管道:最简单的“单向传送带”
管道是 Linux 中最古老的 IPC 方式,本质是内核中的一块缓冲区,类似 “传送带”—— 数据从一端写入,从另一端读出。
1. 匿名管道(Pipe):只认 “自家人”
特点:
•半双工(数据只能单向流动),需创建两个管道实现双向通信;
•仅用于有亲缘关系的进程(父子、兄弟进程,通过 fork 继承管道描述符);
•随进程退出自动销毁,不占用磁盘空间。
实例:父子进程用匿名管道聊天
父进程写消息,子进程读消息:
#include#include #include int main() { int pipefd[2]; // 管道描述符:[0]读端,[1]写端 pipe(pipefd); // 创建匿名管道 pid_t pid = fork(); // 创建子进程(继承管道描述符) if (pid == 0) { // 子进程:读数据 close(pipefd[1]); // 关闭写端(只需要读) char buf[100]; read(pipefd[0], buf, sizeof(buf)); // 从管道读 printf("子进程收到:%sn", buf); close(pipefd[0]); } else { // 父进程:写数据 close(pipefd[0]); // 关闭读端(只需要写) char *msg = "Hi,我是父进程!"; write(pipefd[1], msg, strlen(msg)+1); // 写入管道 close(pipefd[1]); } return 0;}
通信流程:

2. 命名管道(FIFO):陌生人也能聊
特点:
•与匿名管道功能类似,但通过文件系统中的路径标识(如/tmp/myfifo);
•可用于无亲缘关系的进程(只要知道 FIFO 路径就能通信);
•是特殊文件(用mkfifo创建),但数据仍存于内存,不落地。
实例:两个独立进程通过 FIFO 通信
写进程(writer.c):
#include#include #include #include int main() { mkfifo("/tmp/myfifo", 0666); // 创建FIFO文件 int fd = open("/tmp/myfifo", O_WRONLY); // 打开写端 char *msg = "来自陌生进程的消息"; write(fd, msg, strlen(msg)+1); close(fd); return 0;}
读进程(reader.c):
#include#include #include #include int main() { int fd = open("/tmp/myfifo", O_RDONLY); // 打开读端(会阻塞到有写端打开) char buf[100]; read(fd, buf, sizeof(buf)); printf("收到:%sn", buf); close(fd); return 0;}
通信流程:

二、信号:进程间的“紧急电报”
信号是 Linux 中最 “轻量” 的 IPC 方式,用于通知进程发生了某种事件(如异常、用户指令),类似 “紧急电报”。
特点:
•异步通信(无需进程主动等待);
•携带信息少(仅一个信号编号);
•内核负责递送(进程可注册处理函数)。
常用信号:
•SIGINT(2):用户按Ctrl+C,默认终止进程;
•SIGTERM(15):请求进程终止(默认行为);
•SIGUSR1/SIGUSR2(10/12):用户自定义信号。
实例:进程 A 给进程 B 发 “自定义电报”
进程 B(接收方):
#include#include // 信号处理函数void handle_usr1(int sig) { printf("收到SIGUSR1信号!n");}int main() { signal(SIGUSR1, handle_usr1); // 注册信号处理函数 printf("我是进程B,PID:%d,等待信号...n", getpid()); while(1); // 无限循环等待 return 0;}
进程 A(发送方):
#include#include int main() { pid_t b_pid = 12345; // 进程B的PID(需替换为实际值) kill(b_pid, SIGUSR1); // 发送SIGUSR1信号给B printf("已发送SIGUSR1给进程%dn", b_pid); return 0;}
通信流程:

三、共享内存:最快的“公共黑板”
共享内存是速度最快的 IPC 方式 —— 多个进程直接访问同一块物理内存,无需内核 “中转” 数据。
特点:
•无数据拷贝(直接操作内存),效率极高;
•需要同步机制(如信号量)防止“同时写” 冲突;
•由内核管理(用shmget创建,shmctl销毁)。
实例:两个进程共享一块内存
写进程(shm_write.c):
#include#include #include int main() { key_t key = ftok(".", 123); // 生成唯一键值 int shmid = shmget(key, 1024, IPC_CREAT|0666); // 创建共享内存(大小1024字节) char *shmaddr = shmat(shmid, NULL, 0); // 关联到进程地址空间 strcpy(shmaddr, "共享内存中的数据"); // 写入数据 shmdt(shmaddr); // 解除关联 return 0;}
读进程(shm_read.c):
#include#include int main() { key_t key = ftok(".", 123); // 相同键值 int shmid = shmget(key, 1024, 0666); // 获取共享内存 char *shmaddr = shmat(shmid, NULL, 0); // 关联到地址空间 printf("读到共享数据:%sn", shmaddr); // 读取数据 shmdt(shmaddr); // 解除关联 shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 return 0;}
通信流程:

四、消息队列:带“标签” 的 “邮件箱”
消息队列是内核中的消息链表,每个消息有“类型标签”,进程可按类型接收,类似 “带标签的邮件箱”。
特点:
•数据有结构(消息类型 + 数据),支持按类型读取;
•异步通信(发送方无需等待接收方);
•有大小限制(内核参数MSGMAX控制单条消息最大长度)。
实例:按类型发送 / 接收消息
发送进程(msg_send.c):
#include#include #include // 消息结构(必须以long mtype开头)struct msgbuf { long mtype; // 消息类型(正数) char mtext[100]; // 消息内容};int main() { key_t key = ftok(".", 456); int msqid = msgget(key, IPC_CREAT|0666); // 创建消息队列 struct msgbuf msg; msg.mtype = 1; // 类型为1 strcpy(msg.mtext, "类型1的消息"); msgsnd(msqid, &msg, sizeof(msg.mtext), 0); // 发送消息 return 0;}
接收进程(msg_recv.c):
#include#include struct msgbuf { long mtype; char mtext[100];};int main() { key_t key = ftok(".", 456); int msqid = msgget(key, 0666); // 获取消息队列 struct msgbuf msg; msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0); // 只接收类型1的消息 printf("收到类型%d的消息:%sn", msg.mtype, msg.mtext); msgctl(msqid, IPC_RMID, NULL); // 删除消息队列 return 0;}
通信流程:

五、信号量:进程同步的“红绿灯”
信号量不是用于传递数据,而是控制多个进程对共享资源的访问(如共享内存、文件),类似“红绿灯”。
核心概念:
•信号量值(semaphore):>=0 的整数,代表 “可用资源数”;
•P 操作(等待):信号量值 - 1,若值 < 0 则阻塞;
•V 操作(释放):信号量值 + 1,若有进程阻塞则唤醒。
实例:用信号量保护共享内存
(基于共享内存,添加信号量控制):
#include// 定义P/V操作(简化版)void P(int semid) { struct sembuf s = {0, -1, 0}; // 第0个信号量,-1(P操作) semop(semid, &s, 1);}void V(int semid) { struct sembuf s = {0, 1, 0}; // +1(V操作) semop(semid, &s, 1);}// 初始化信号量(值为1,代表互斥)int init_sem() { key_t key = ftok(".", 789); int semid = semget(key, 1, IPC_CREAT|0666); semctl(semid, 0, SETVAL, 1); // 第0个信号量值设为1 return semid;}// 写进程在访问共享内存前P,写完V;读进程同理
同步流程:

六、Socket:跨主机通信的 “万能接口”
Socket(套接字)是最灵活的 IPC 方式,不仅支持同一主机的进程通信,还能跨网络(如服务器与客户端)。
特点:
•支持 TCP(可靠、面向连接)和 UDP(不可靠、无连接);
•本地通信可用AF_UNIX协议(通过文件路径标识);
•网络通信用AF_INET协议(通过 IP + 端口标识)。
实例:本地 Socket 通信(AF_UNIX)
服务器(sock_server.c):
#include#include #include #include #include int main() { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建本地套接字 struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/tmp/mysock"); // 本地路径 bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 绑定 listen(sockfd, 5); // 监听 int connfd = accept(sockfd, NULL, NULL); // 接受连接 char buf[100]; read(connfd, buf, sizeof(buf)); printf("收到:%sn", buf); close(connfd); close(sockfd); unlink("/tmp/mysock"); // 删除套接字文件 return 0;}
客户端(sock_client.c):
#include#include #include #include #include int main() { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/tmp/mysock"); connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 连接服务器 char *msg = "本地Socket消息"; write(sockfd, msg, strlen(msg)+1); close(sockfd); return 0;}
通信流程:

总结:如何选择合适的 IPC 方式?
| 方式 | 速度 | 复杂度 | 适用场景 |
| 匿名管道 | 中 | 低 | 父子进程简单单向通信 |
| 命名管道 | 中 | 中 | 无亲缘关系进程简单通信 |
| 信号 | 快(异步) | 低 | 事件通知(如异常、退出) |
| 共享内存 | 最快 | 高(需同步) | 高频、大数据量共享 |
| 消息队列 | 中 | 中 | 需按类型传递消息的场景 |
| 信号量 | 快 | 中 | 进程同步与互斥(配合其他 IPC) |
| Socket | 较慢 | 高 | 跨主机或复杂通信(如网络服务) |
进程间通信是 Linux 开发的核心基础,理解每种方式的优缺点,才能在实际场景中 “对症下药”。比如日志收集可用命名管道,实时数据共享用共享内存 + 信号量,网络服务则离不开 Socket。
你在开发中用过哪种 IPC 方式?遇到过哪些坑?欢迎在评论区交流~
全部0条评论
快来发表一下你的评论吧 !