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

嵌入式解谜日志之Linux操作系统—进程间的通信(IPC):无名管道,有名管道,信号通信5

在操作系统中,进程间通信(IPC,Inter-Process Communication) 是指不同进程之间传递数据、共享资源或协调行为的机制。由于进程拥有独立的内存空间(隔离性),不能直接访问彼此的内存,因此需要操作系统提供专门的 IPC 机制。

通信方式分类
  1. 古老通信方式

    • 无名管道、有名管道、信号通信
    • 特点:信号通信是唯一的异步通信机制
  2. IPC对象通信(System V)

    • 消息队列(较少使用)
    • 共享内存(最高效的通信方式)
    • 信号量集
  3. Socket通信

    • 用于网络通信

一、IPC 的主要目的

  1. 数据交换:进程之间传递数据(如文件内容、网络数据)。
  2. 同步:协调多个进程的执行顺序(如 “先生产后消费”)。
  3. 共享资源:多个进程共享硬件设备或文件(如打印机、数据库)。
  4. 通知事件:一个进程向其他进程发送事件信号(如进程退出、数据就绪)。

二、常见的进程间通信方式

1. 无名管道(Pipe)与命名管道(FIFO)

适用场景:父子进程或亲缘进程间的单向字节流通信,适用于少量数据传输。

  • 无名管道(Pipe)

    • 特点:半双工(单向传输),仅存在于内存中,生命周期随进程,只能用于有亲缘关系的进程(如父子进程)。
    • 核心函数:pipe(int fd[2]),创建读端(fd[0])和写端(fd[1])。
    • 特殊文件:读写使用 read/write,不支持文件定位
    • 容量限制:Linux系统中管道容量为64KB(16页,每页4KB)
    • 阻塞规则
      • 读端存在时,写满64KB会阻塞
      • 写端存在时,空管道读操作会阻塞
      • 读端关闭时写操作触发 SIGPIPE 信号
      • 写端关闭时读操作返回0(表示结束)
无名管道
特性
  • 仅限亲缘关系进程使用(父子/兄弟进程)   //父进程写,子进程读
  • 有固定读写端:fd[0] 为读端,fd[1] 为写端
  • 必须在 fork() 前创建
使用框架
  1. 创建管道:pipe()
  2. 读写管道:read()/write()
  3. 关闭管道:close()

1.创建管道:

#include <unistd.h>
int pipe(int pipefd[2]);
int fd[2] = {0};  // 管道文件描述符数组int ret = pipe(fd);  // 创建无名管道
  • 功能:创建并打开无名管道
  • 参数
    • pipefd[0]:管道读端
    • pipefd[1]:管道写端
  • 返回值:成功返回0,失败返回-1

管道读阻塞示例 (01pipe_read_block.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};  // 管道文件描述符数组int ret = pipe(fd);  // 创建无名管道if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();  // 创建子进程if (pid > 0)  // 父进程{close(fd[0]);  // 关闭读端char buf[] = "hello,child";  // 待写入数据sleep(3);  // 延迟3秒write(fd[1], buf, strlen(buf) + 1);  // 写入管道}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端char buf[50] = {0};  // 读缓冲区read(fd[0], buf, sizeof(buf));  // 从管道读取(会阻塞等待)printf("child, buf:%s\n", buf);  // 打印读取内容}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程阻塞3秒后,打印:
child, buf:hello,child

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[1024] = {0};memset(buf, 'a', sizeof(buf));  // 填充'a'int i = 0;for (i = 0; i < 65; i++)  // 超过64KB容量{write(fd[1], buf, sizeof(buf));printf("i is %d\n", i);  // 观察写阻塞点}}else if (0 == pid)  // 子进程{close(fd[1]);sleep(5);  // 延迟读取char buf[50] = {0};read(fd[0], buf, sizeof(buf));printf("child, buf:%s\n", buf);}else {perror("fork");return 1;}return 0;
}

理想运行结果
父进程打印 i is 0 到 i is 63 后阻塞(64次×1024=64KB),第64次写入后继续打印 i is 64。子进程5秒后读取并打印64个 'a'

管道破裂示例 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{sleep(1);  // 确保子进程先关闭close(fd[0]);  // 关闭读端char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);  // 触发管道破裂printf("write end\n");  // 不会执行(已崩溃)}else if (0 == pid)  // 子进程{close(fd[1]);  // 关闭写端close(fd[0]);  // 关闭读端sleep(3);      // 等待父进程写操作printf("child, pid:%d\n", getpid());}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程先关闭所有管道端,父进程写操作触发 SIGPIPE 信号,进程异常终止。终端显示:
Broken pipe

读端关闭示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd[2] = {0};int ret = pipe(fd);if (-1 == ret){perror("pipe");return 1;}pid_t pid = fork();if (pid > 0)  // 父进程{close(fd[0]);char buf[] = "hello,child";write(fd[1], buf, strlen(buf) + 1);}else if (0 == pid)  // 子进程{close(fd[1]);while (1){char buf[50] = {0};int ret = read(fd[0], buf, sizeof(buf));if (0 == ret)  // 写端关闭且无数据{printf("IPC end\n");break;}printf("child, buf:%s\n", buf);}}else {perror("fork");return 1;}return 0;
}

理想运行结果
子进程先读取到 hello,child,父进程退出后写端关闭,子进程下一次 read() 返回0并打印:
IPC end

  • 命名管道(FIFO)

    • 特点:半双工,以文件形式存在于文件系统中,可用于任意进程间通信(无亲缘关系也可)。
    • 核心函数:mkfifo(const char *path, mode_t mode) 创建 FIFO 文件,通过open/read/write操作。
特性
  • 文件系统可见(有路径名)
  • 适用于任意进程通信
  • 打开时需指定读/写模式
  • 无亲缘关系限制
使用框架
  1. 创建管道:mkfifo()
  2. 打开管道:open()(必须指定 O_RDONLY/O_WRONLY
  3. 读写管道:read()/write()
  4. 关闭管道:close()
  5. 删除管道:remove()

创建管道函数:

int mkfifo(const char *pathname, mode_t mode);
  • 功能:创建有名管道文件
  • 参数
    • pathname:管道路径
    • mode:8进制权限(如 0666
  • 返回值:成功返回0,失败返回-1

写端程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);  // 创建管道if (-1 == ret && EEXIST != errno)  // 忽略已存在错误{perror("mkfifo error");return 1;}int fd = open("myfifo", O_WRONLY);  // 以写方式打开if (-1 == fd){perror("open error");return 1;}char buf[] = "hello, this is fifo tested...\n";sleep(3);  // 等待读端准备write(fd, buf, strlen(buf) + 1);  // 写入数据close(fd);return 0;
}

理想运行结果
等待3秒后写入数据到管道(无直接输出,需配合读端程序)。

读端程序 (02fifo_r.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[])
{int ret = mkfifo("myfifo", 0666);if (-1 == ret && EEXIST != errno){perror("mkfifo error");return 1;}int fd = open("myfifo", O_RDONLY);  // 以读方式打开if (-1 == fd){perror("open error");return 1;}char buf[256] = {0};read(fd, buf, sizeof(buf));  // 从管道读取printf("fifo: %s\n", buf);   // 打印内容close(fd);return 0;
}

理想运行结果
当写端程序运行后,读端打印:
fifo: hello, this is fifo tested...

文件传输示例(写端),写入管道

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_w = open("myfifo", O_WRONLY);  // 写端int src = open("/home/linux/1.png", O_RDONLY);  // 源文件char buf[4096];while (1){int ret = read(src, buf, sizeof(buf));  // 读取图片if (ret <= 0) break;write(fifo_w, buf, ret);  // 写入管道}close(src);close(fifo_w);return 0;
}

文件传输示例(读端):从管道读出

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>int main()
{mkfifo("myfifo", 0666);  // 创建管道int fifo_r = open("myfifo", O_RDONLY);  // 读端int dst = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);  // 目标文件char buf[4096];while (1){int ret = read(fifo_r, buf, sizeof(buf));  // 从管道读取if (ret <= 0) break;write(dst, buf, ret);  // 写入目标文件}close(fifo_r);close(dst);return 0;
}

理想运行结果
1.png 被完整传输到 2.png,两个文件内容完全一致。

信号通信

核心特性
  • 唯一的异步通信机制
  • 信号处理方式:
    • SIG_DFL:默认处理(终止、忽略等)
    • SIG_IGN:忽略信号
    • 自定义处理函数
  • 不可捕获信号:SIGKILL(9)SIGSTOP(19)
核心函数
int kill(pid_t pid, int sig);
  • 功能:向指定进程发送信号
  • 参数
    • pid:目标进程ID
    • sig:信号编号(kill -l 查看)
  • 返回值:成功返回0,失败返回-1
sighandler_t signal(int signum, sighandler_t handler);
    • 功能:注册自定义处理函数,捕获信号并执行特定逻辑
    • 参数
      • signum:信号编号
      • handler:处理方式(SIG_DFL/SIG_IGN/函数指针)
    • 返回值:旧处理函数指针
    • 程序注册了 SIGINT(Ctrl+C)和 SIGTERM(默认 kill 信号)的处理函数,收到信号后执行清理并退出。
    • SIGKILL 无法被捕获,若执行 kill -9 <PID>,程序会立即终止,不执行自定义处理函数。

    信号发送程序 (01kill.c)

    #include <sys/types.h>
    #include <signal.h>
    #include <stdio.h>int main(int argc, char *argv[])
    {if (argc < 3){printf("usage: ./a.out pid sig_num\n");return 1;}int pid = atoi(argv[1]);int sig_num = atoi(argv[2]);int ret = kill(pid, sig_num);  // 发送信号if (-1 == ret){perror("kill error");return 1;}return 0;
    }
    

    理想运行结果
    成功向指定进程发送信号(无输出),目标进程按信号类型执行相应操作。


    信号处理示例

    #include <signal.h>
    #include <unistd.h>
    #include <stdio.h>int flag = 0;void myhandle(int num)  // 信号处理函数
    {flag = 1;  // 修改全局标志
    }int main()
    {signal(SIGINT, myhandle);  // 注册Ctrl+C处理while (1){if (0 == flag)printf("i'm processing..., pid:%d\n", getpid());elseprintf("i'm resting..., pid:%d\n", getpid());sleep(1);}return 0;
    }
    

    理想运行结果

    • 正常运行时每秒打印处理中信息
    • 按 Ctrl+C 后,后续打印变为休息状态

    自定义信号处理

    #include <signal.h>
    #include <unistd.h>
    #include <stdio.h>void myhandle1(int num)
    {static int i = 0;printf("老爸叫你....\n");if (++i == 3) signal(SIGUSR1, SIG_IGN);  // 第3次后忽略信号
    }void myhandle2(int num)
    {static int i = 0;printf("老妈叫你....\n");if (++i == 5) signal(SIGUSR2, SIG_DFL);  // 第5次后恢复默认
    }int main()
    {signal(SIGUSR1, myhandle1);  // 注册USR1处理signal(SIGUSR2, myhandle2);  // 注册USR2处理while (1){printf("i'm playing..., pid:%d\n", getpid());sleep(1);}return 0;
    }
    

    理想运行结果

    • 收到 SIGUSR1 信号时打印"老爸叫你",第3次后不再响应
    • 收到 SIGUSR2 信号时打印"老妈叫你",第5次后恢复默认处理(终止进程)

    子进程回收 

    #include <signal.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>void myhandle(int num)  // SIGCHLD处理函数
    {pid_t pid = wait(NULL);  // 非阻塞回收printf("father recycle pid: %d\n", pid);
    }int main()
    {signal(SIGCHLD, myhandle);  // 注册子进程结束信号pid_t pid = fork();if (pid > 0)  // 父进程{for (int i = 10; i > 0; i--){printf("father.... pid:%d\n", getpid());sleep(1);}}else if (0 == pid)  // 子进程{for (int i = 5; i > 0; i--){printf("child... pid:%d\n", getpid());sleep(1);}}return 0;
    }
    

    理想运行结果
    子进程结束后,父进程自动回收:

    father.... pid:1234
    ...
    child... pid:5678
    father recycle pid:5678
    

    http://www.dtcms.com/a/361427.html

    相关文章:

  • 单片机元件学习
  • 【stm32】定时器(超详细)
  • Git安装教程
  • 【51页PPT】智慧社区解决方案(附下载方式)
  • 审美积累 | 金融类 SaaS 产品落地页设计
  • Empire: LupinOne靶场渗透
  • 贪心算法解决固定长度区间覆盖问题:最少区间数计算
  • CICD实战(2) - 使用Arbess+GitLab+SonarQube实现Java项目快速扫描/构建/部署
  • 【MySQL详解】索引、事务、锁、日志
  • 【C++上岸】C++常见面试题目--数据结构篇(第十六期)
  • 科学研究系统性思维的方法体系:数据收集
  • 11,FreeRTOS队列理论知识
  • linux内核 - ext 文件系统介绍
  • 嵌入式学习日志————I2C通信外设
  • 拥抱智能高效翻译 ——8 款视频翻译工具深度测评
  • Linux Shell 脚本中括号类型及用途
  • 【项目思维】嵌入式产业链与技术生态
  • 2025 最新React前端面试题目 (9月最新)
  • Windows Qt5.15.17源码使用VS2019编译安装
  • 六、练习3:Gitee平台操作
  • 瑞芯微RK3576平台FFmpeg硬件编解码移植及性能测试实战攻略
  • 深入掌握 Flask 配置管理:从基础到高级实战
  • 校园网IP地址要如何管理
  • MySQL基础知识保姆级教程(四)基础语句
  • 人工智能学习:NLP文本处理的基本方法
  • C++函数执行时间统计工具:轻量级性能分析的最佳实践
  • 触想轨道交通应用案例集锦(一)
  • PAT 1089 Insert or Merge
  • G156HAN04.0 宽温域高亮工业屏技术白皮书
  • 矩阵中寻找好子矩阵