当前位置: 首页 > news >正文

Linux-----进程间通信

一、按通信范围分类

  1. 同一主机进程通信

    • 传统IPC方式:
      • 管道(无名管道、有名管道)
      • 信号(Signal)
    • System V IPC:
      • 共享内存(效率最高)
      • 消息队列
      • 信号量
    • POSIX IPC(较新标准):
      • POSIX消息队列
      • POSIX信号量
      • POSIX共享内存
  2. 跨主机进程通信

    • 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_RDONLYO_NONBLOCK`立即返回(无数据返回0,需检查errno)-
`O_WRONLYO_NONBLOCK`-立即返回(无读端时返回ENXIO错误)

五、卸载管道unlink

一、函数原型

#include <unistd.h>
int unlink(const char *pathname);  // 正确拼写为 pathname

二、功能说明

  1. 核心作用

    • 删除文件系统中的一个目录项
    • 当文件引用计数归零时释放磁盘空间
  2. 对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// ...其他代码...
}


七、重要注意事项

  1. 延迟释放机制

    int fd = open("/tmp/fifo", O_RDONLY);
    unlink("/tmp/fifo");  // 立即删除文件系统入口
    read(fd, buf, size);  // 仍然可以正常读取数据
    close(fd);            // 此时真正释放资源
    
  2. 多进程场景

    • 建议由最后退出的进程执行删除
    • 可使用文件锁协调删除操作:
      flock(fd, LOCK_EX);
      unlink(path);
      flock(fd, LOCK_UN);
      
  3. 临时文件最佳实践

    // 创建临时管道(自动删除)
    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跨平台文件删除

九、开发建议

  1. 在程序启动时清理旧管道:

    if (access(fifo_path, F_OK) == 0) {unlink(fifo_path);
    }
    
  2. 使用绝对路径避免歧义:

    // 错误示例
    unlink("my_fifo");  // 可能误删其他目录文件// 正确示例
    unlink("/var/run/myapp_fifo");
    
  3. 监控文件状态:

    struct stat st;
    if (stat(fifo_path, &st) == 0) {if (S_ISFIFO(st.st_mode)) {// 确认是管道文件再删除unlink(fifo_path);}
    }
    

📌 关键点:unlink 只是删除文件链接,实际资源释放需要等待所有引用关闭。对于管道文件的清理,建议结合引用计数和进程生命周期管理来实现安全删除。


http://www.mrgr.cn/news/92211.html

相关文章:

  • SOME/IP-SD -- 协议英文原文讲解2
  • DroidDissector本地部署
  • Mesh自组网技术及应用
  • 记一些工具(持续更新)
  • 2.2 STM32F103C8T6最小系统板的四种有关固件的开发方式
  • 【DeepSeek-R1背后的技术】系列十一:RAG原理介绍和本地部署(DeepSeekR1+RAGFlow构建个人知识库)
  • KL 散度介绍及使用场景
  • NTS库学习,找bug中......
  • 蓝桥云课python代码
  • Linux Crontab面试题及参考答案
  • C++ day5 练习
  • html中的元素(1)
  • C语言数据结构—二叉树的链式结构实现
  • hot100-二叉树
  • MySQL入门:高频操作命令大全
  • 大白话javascript如何通过原型链实现对象的继承,并指出这种继承方式的优缺点
  • ddd 文章总结分享,ddd实战代码分享, 领域驱动设计java实战源码大全,我看过的ddd java源码
  • C1车证学习笔记
  • (七)趣学设计模式 之 适配器模式!
  • 【算法】二分789. 数的范围