Linux-----进程间通信
一、按通信范围分类
-
同一主机进程通信
- 传统IPC方式:
- 管道(无名管道、有名管道)
- 信号(Signal)
- System V IPC:
- 共享内存(效率最高)
- 消息队列
- 信号量
- POSIX IPC(较新标准):
- POSIX消息队列
- POSIX信号量
- POSIX共享内存
- 传统IPC方式:
-
跨主机进程通信
- Socket网络通信
二、通信原理
- 进程空间独立,必须通过内核中转
- 内核在内核空间建立通信机制,用户空间通过系统调用访问
三、管道通信
1. 无名管道(Pipe)
特性:
- 半双工通信(数据单向流动)
- 仅用于亲缘关系进程(父子/兄弟进程)
- 数据遵循FIFO原则
- 最大容量64KB(可通过
fcntl(fd, F_GETPIPE_SZ)
查询)
解释:
双工 ---发送和接收可以同时进行 --手机,电话
半双工 ---发送端 和 接收端 同一个时刻之后有一个起效 ---对讲机
单工 ---发送端 接收端固定 --- 广播
一、函数原型
#include <unistd.h>
int pipe(int pipefd[2]);
二、参数说明
- pipefd[2]:输出参数,用于接收两个文件描述符的数组
pipefd[0]
:管道的读端(从该描述符读取数据)pipefd[1]
:管道的写端(向该描述符写入数据)
三、返回值
- 成功返回
0
- 失败返回
-1
,并设置errno
:EMFILE
:进程打开的文件描述符过多ENFILE
:系统文件表已满EFAULT
:非法地址空间
四、基础用法示例
#include<stdio.h>
#include<unistd.h>
#include <sys/wait.h>
#include<string.h>
#include <stdlib.h>int main(int argc, const char *argv[])
{int fd[2];int fd1[2];int ret = pipe(fd);ret = pipe(fd1);char buf[20] = {0};char buf1[1024] = {0};if(ret < 0){perror("pipe fail");return -1;}pid_t pid = fork();if(pid < 0){perror("fork fail");return -1;}if(pid > 0){close(fd[0]);close(fd1[1]);while(1){printf("f> ");fgets(buf,sizeof(buf),stdin);write(fd[1],buf,strlen(buf)+1);//加1是要保证输入的是字符串if(strncmp(buf,"quit",4) == 0){wait(NULL);exit(EXIT_SUCCESS);}read(fd1[0],buf1,sizeof(buf));printf("buf1 = %s\n",buf1);}}else if(pid == 0){close(fd[1]);close(fd1[0]);while(1){read(fd[0],buf,sizeof(buf));if(strncmp(buf,"quit",4) == 0){printf("child exit....\n");exit(EXIT_SUCCESS);}printf("buf = %s\n",buf);sprintf(buf1,"child %s",buf);write(fd1[1],buf1,strlen(buf1)+1);}}return 0;
}
五、关键特性说明
1. 数据流向
- 单向流动:数据从写端(
pipefd[1]
)流向读端(pipefd[0]
) - 半双工:同一时刻只能有一个方向的数据流
2. 原子性保证
- 当写入数据量 ≤
PIPE_BUF
(POSIX 要求 ≥ 512字节)时,保证写入操作的原子性 - 可通过命令查看具体值:
cat /proc/sys/fs/pipe-max-size # 最大容量(默认 1MB) ulimit -a # 查看 PIPE_BUF 值(通常 4096)
3. 阻塞行为
场景 | 读端行为 | 写端行为 |
---|---|---|
管道空 & 写端开放 | 阻塞等待数据 | - |
管道满 & 读端开放 | - | 阻塞直到有空间 |
所有读端关闭 | - | 触发 SIGPIPE 信号(默认终止进程) |
所有写端关闭 | read 返回 0(EOF) | - |
四、mkfifo
一、函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
二、参数说明
- pathname:管道文件的路径(建议使用绝对路径)
- mode:文件权限(实际权限受
umask
影响,建议搭配umask(0)
使用)
三、返回值
- 成功返回
0
- 失败返回
-1
,并设置errno
:EEXIST
:文件已存在ENOENT
:路径不存在EACCES
:权限不足ENOSPC
:磁盘空间不足
四、基础用法示例
写入端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);return -1;}if(mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if(mkfifo(argv[2],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd1 = open(argv[1],O_WRONLY);int fd2 = open(argv[2],O_RDONLY);if (fd1 < 0 || fd2 < 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024];if (pid > 0){close(fd2);while (1){printf("> ");fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(fd1,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){wait(NULL);printf("father exit......\n");exit(0);}}}else if (pid == 0){close(fd1);while (1){printf("c> ");read(fd2,buf,sizeof(buf));printf("%s \n",buf);if (strncmp(buf,"quit",4) == 0){printf("child exit......\n");exit(0);}}}return 0;
}
读取端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>//./a.out fifo_A2B fifo_B2A
int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <fifo_A2B> <fifo_B2A>\n",argv[0]);return -1;}if(mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}if(mkfifo(argv[2],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}int fd1 = open(argv[1],O_RDONLY);int fd2 = open(argv[2],O_WRONLY);if (fd1 < 0 || fd2 < 0){perror("open fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}char buf[1024];if (pid > 0){close(fd1);while (1){printf("> ");fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(fd2,buf,strlen(buf)+1);if (strncmp(buf,"quit",4) == 0){wait(NULL);printf("father exit......\n");exit(0);}}}else if (pid == 0){close(fd2);while (1){printf("c> ");read(fd1,buf,sizeof(buf));printf("%s \n",buf);if (strncmp(buf,"quit",4) == 0){printf("child exit......\n");exit(0);}}}return 0;
}
一、打开模式与阻塞关系表
打开方式 | 读端行为 | 写端行为 | 是否推荐使用 |
---|---|---|---|
O_RDONLY | 阻塞直到有写端打开 | - | ✅ 推荐 |
O_WRONLY | - | 阻塞直到有读端打开 | ✅ 推荐 |
O_RDWR | 立即返回(破坏FIFO语义) | 立即返回(破坏FIFO语义) | ❌ 禁止使用 |
`O_RDONLY | O_NONBLOCK` | 立即返回(无数据返回0,需检查errno) | - |
`O_WRONLY | O_NONBLOCK` | - | 立即返回(无读端时返回ENXIO错误) |
五、卸载管道unlink
一、函数原型
#include <unistd.h>
int unlink(const char *pathname); // 正确拼写为 pathname
二、功能说明
-
核心作用:
- 删除文件系统中的一个目录项
- 当文件引用计数归零时释放磁盘空间
-
对FIFO的特殊行为:
- 立即删除文件系统入口(文件不再可见)
- 已打开的管道描述符仍可继续使用
- 实际文件资源在所有进程关闭描述符后释放
三、参数说明
参数 | 说明 |
---|---|
pathname | 要删除的有名管道完整路径 |
示例:"/tmp/my_fifo" |
四、返回值
返回值 | 说明 |
---|---|
0 | 删除成功 |
-1 | 删除失败,可通过 errno 获取错误原因 |
五、错误处理
errno 值 | 触发场景 | 处理方法 |
---|---|---|
EACCES | 权限不足(文件/目录不可写) | 检查文件权限或使用 sudo |
ENOENT | 文件不存在 | 先检查文件是否存在 |
EISDIR | 路径是目录 | 改用 rmdir 删除目录 |
EBUSY | 文件正在被使用(某些系统) | 关闭所有进程的文件描述符 |
六、使用示例
1. 基础用法
const char *fifo_path = "/tmp/my_fifo";// 创建并删除管道
if (mkfifo(fifo_path, 0666) == -1) {perror("mkfifo error");exit(EXIT_FAILURE);
}// 使用管道...// 删除管道文件
if (unlink(fifo_path) == -1) {perror("unlink error");exit(EXIT_FAILURE);
}
2. 安全删除模式
#include <stdlib.h>
#include <signal.h>// 注册退出清理函数
void cleanup() {if (unlink("/tmp/my_fifo") == -1 && errno != ENOENT) {perror("cleanup error");}
}int main() {atexit(cleanup); // 正常退出时调用signal(SIGTERM, cleanup); // 捕获终止信号signal(SIGINT, cleanup); // 捕获Ctrl+C// ...其他代码...
}
七、重要注意事项
-
延迟释放机制:
int fd = open("/tmp/fifo", O_RDONLY); unlink("/tmp/fifo"); // 立即删除文件系统入口 read(fd, buf, size); // 仍然可以正常读取数据 close(fd); // 此时真正释放资源
-
多进程场景:
- 建议由最后退出的进程执行删除
- 可使用文件锁协调删除操作:
flock(fd, LOCK_EX); unlink(path); flock(fd, LOCK_UN);
-
临时文件最佳实践:
// 创建临时管道(自动删除) char tmp_path[] = "/tmp/fifo_XXXXXX"; mktemp(tmp_path); // 生成唯一名称 mkfifo(tmp_path, 0600); // 严格权限 unlink(tmp_path); // 立即标记删除
八、与 remove() 的区别
特性 | unlink() | remove() |
---|---|---|
标准来源 | POSIX 系统调用 | C标准库函数 |
目录处理 | 不能删除目录 | 可删除空目录 |
封装实现 | 原始系统调用 | 内部调用 unlink/rmdir |
错误返回 | 通过 errno 获取 | 通过返回值判断 |
推荐场景 | 删除文件/FIFO | 跨平台文件删除 |
九、开发建议
-
在程序启动时清理旧管道:
if (access(fifo_path, F_OK) == 0) {unlink(fifo_path); }
-
使用绝对路径避免歧义:
// 错误示例 unlink("my_fifo"); // 可能误删其他目录文件// 正确示例 unlink("/var/run/myapp_fifo");
-
监控文件状态:
struct stat st; if (stat(fifo_path, &st) == 0) {if (S_ISFIFO(st.st_mode)) {// 确认是管道文件再删除unlink(fifo_path);} }
📌 关键点:
unlink
只是删除文件链接,实际资源释放需要等待所有引用关闭。对于管道文件的清理,建议结合引用计数和进程生命周期管理来实现安全删除。