📝个人主页🌹:Eternity._
⏩收录专栏⏪:Linux “ 登神长阶 ”
🌹🌹期待您的关注 🌹🌹
❀Linux进程间通信
前言:当提及Linux系统中的进程间通信(IPC),管道(Pipes)无疑是最基础且广泛使用的一种机制。作为匿名通信的典范,管道为进程间数据交换提供了一个简单而有效的途径。在这个信息飞速传递的时代,掌握Linux管道的使用不仅是理解操作系统底层通信原理的关键一步,也是提升软件开发效率、构建复杂应用系统的必备技能
本篇文章将带您深入探索Linux进程间匿名通信的管道机制。我们将从管道的基本概念出发,逐步揭开其背后的工作原理,并通过实例演示如何在实际编程中创建、使用和维护管道。无论您是初学者,希望建立对Linux IPC的初步认识;还是经验丰富的开发者,渴望在现有基础上进一步精进;亦或是对系统编程充满好奇的学习者,渴望深入了解操作系统内部的奥秘,本文都将为您提供丰富的知识和实用的指导
我们将详细介绍管道的创建过程、数据读写操作、管道的生命周期管理以及常见的使用场景。 同时,我们还会探讨管道在并发编程中的表现,分析其在多进程环境下的行为特性,并提供相应的优化策略。通过理论与实践相结合的方式,相信您能够全面掌握Linux进程间匿名通信的管道技术,为您的软件开发之路增添一份坚实的力量
让我们一同踏上这段探索之旅,揭开Linux管道的神秘面纱,领略其在进程间通信中的独特魅力!
📒1. 进程间通信介绍
进程间通信(Interprocess communication,IPC)是指在不同的进程之间传播或交换信息。由于进程的用户空间是互相独立的,一般而言不能互相访问,但存在一些双方都可以访问的介质或系统空间来实现通信
- 原理: 进程间通信主要依赖于双方都可以访问的介质或系统空间。这些介质包括共享内存区、系统空间以及双方都可以访问的外设(如磁盘上的文件、数据库中的表项等)。然而,广义上的通过这些方式进行的通信一般不算作“进程间通信”。进程间通信更常见的是通过一组编程接口来实现,这些接口允许程序员协调不同的进程,使它们能在一个操作系统里同时运行,并相互传递、交换信息
- 必要性: 即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行。这些进程之间必须互相通信,以协调它们的行为和共享资源。进程间通信使得一个程序能够在同一时间里处理许多用户的要求
📚2. 什么是管道
- 管道是Unix中最古老的进程间通信的形式
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
**
管道分为:匿名管道和命名管道
,本篇我们主要来了解一下匿名管道**
📜3. 匿名管道
匿名管道是Linux中一种非常基础的进程间通信(IPC)方式,其本质上是一种内存级的文件,专门用于父子进程间或具有亲缘关系的进程间的通信
创建匿名管道
#include<unistd.h>//功能:创建一无名管道//原型intpipe(int fd[2]);//参数//fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端//返回值:成功返回0,失败返回错误代码
实例代码:
#include<iostream>#include<cassert>#include<cstring>#include<sys/types.h>#include<unistd.h>#include<sys/wait.h>#defineMAX1024usingnamespace std;intmain(){// 1. 建立管道int pipefd[2]={0};int n =pipe(pipefd);assert(n ==0);// 定义 n(void)n;// 查看文件描述符
cout <<"pipefd[0]: "<< pipefd[0]<<", pipefd[1]: "<< pipefd[1]<< endl;// 2. 创建子进程
pid_t id =fork();if(id <0){perror("fork");return1;}// 子写,父读,// 3. 关闭父子不需要的fd,形成单向通信的管道if(id ==0){// 子进程close(pipefd[0]);// 写入int cnt =10;while(cnt){char message[MAX];snprintf(message,sizeof(message),"hello father, I am child, pid: %d, cnt: %d",getpid(), cnt);
cnt--;write(pipefd[1], message,strlen(message));
cout <<"writing cnt: "<< cnt << endl;}exit(0);}// 父进程close(pipefd[1]);// 读取char buffer[MAX];while(true){
ssize_t n =read(pipefd[0], buffer,sizeof(buffer)-1);if(n ==0){
cout <<"child qiut, read tail"<< endl;break;}elseif(n >0){
buffer[n]=0;// '\0', 当作字符串
cout <<getpid()<<": "<<"child say: "<< buffer <<" to me!"<< endl;}}
pid_t rid =waitpid(id,nullptr,0);if(rid == id){
cout <<"wait seccess"<< endl;}return0;}
🌞fork共享管道原理
🌙结合文件描述符
⭐站在内核角度
📝4. 管道的读写情况与特点
🎈管道的读写情况
- 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据)
- 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
我们让读端一直读,而写端在写入部分文件后让它sleep一段时间,我们这是来观察一下读端的情况
代码示例:(C++):
if(id ==0){// 子进程close(pipefd[0]);// 写入int cnt =10000;while(cnt){char message[MAX];snprintf(message,sizeof(message),"hello father, I am child, pid: %d, cnt: %d",getpid(), cnt);
cnt--;write(pipefd[1], message,strlen(message));// 在正常写入一次后,sleep,父进程读取不做修改sleep(4);}exit(0);}
当我们的管道被写满了的时候,写端就不能在进行写入了,我们必须等待读端将数据读取走才能继续往管道里面写入,我们让读端休眠上几面,让写端一直写
代码示例:(C++):
if(id ==0){// 子进程close(pipefd[0]);// 写入int cnt =0;while(true){char message[MAX];snprintf(message,sizeof(message),"hello father, I am child, pid: %d, cnt: %d",getpid(), cnt);
cnt++;write(pipefd[1], message,strlen(message));// 在正常写入一次后,sleep,父进程读取不做修改
cout <<"writing cnt: "<< cnt << endl;}exit(0);}
- 写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾
- 读端关闭,写端一直写入,0S会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程
写端关闭代码示例:(C++):
if(id ==0){// 子进程close(pipefd[0]);// 写入int cnt =0;while(true){char message[MAX];snprintf(message,sizeof(message),"hello father, I am child, pid: %d, cnt: %d",getpid(), cnt);
cnt++;write(pipefd[1], message,strlen(message));//sleep(2);
cout <<"writing cnt: "<< cnt << endl;// 在写入两次时,我们将子进程的写入关闭if(cnt ==2){close(pipefd[1]);break;}}exit(0);}// 父进程close(pipefd[1]);// 读取char buffer[MAX];while(true){sleep(4);
ssize_t n =read(pipefd[0], buffer,sizeof(buffer)-1);// 当 n == 0 时,代表read已经读到文件结尾了if(n ==0){
cout <<"child qiut, read tail"<< endl;break;}elseif(n >0){
buffer[n]=0;// '\0', 当作字符串
cout <<getpid()<<": "<<"child say: "<< buffer <<" to me!"<< endl;}}
我们这样设计代码,先让子进程写入之后,关闭掉pipefd[1],然后观察父进程是否会打印,我们需要的代码
读端关闭代码示例:(C++):
// 父进程close(pipefd[1]);// 读取char buffer[MAX];while(true){//sleep(4);
ssize_t n =read(pipefd[0], buffer,sizeof(buffer)-1);if(n ==0){
cout <<"child qiut, read tail"<< endl;break;}elseif(n >0){
buffer[n]=0;// '\0', 当作字符串
cout <<getpid()<<": "<<"child say: "<< buffer <<" to me!"<< endl;}
cout <<"father return val(n)"<< n << endl;sleep(1);// 打印一次后,我们退出循环 break;}// 关闭 pipefd[0],停止读取
cout <<"close point read"<< endl;close(pipefd[0]);sleep(3);int status =0;
pid_t rid =waitpid(id,&status,0);if(rid == id){
cout <<"wait seccess, exit sig: "<<(status&0x7f)<< endl;}
**注意:
当前状态码 & 0x7f
可以查看到最后的退出码**
🎩管道的特性
管道的5种特性
- 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
- 匿名管道,默认给读写端要提供同步机制
- 面向字节流的入
- 管道的生命周期是随进程的
- 管道是单向通信的,半双工通信的一种特殊情况
在了解完管道的这些情况和特征后,我们可以利用管道来写一个简单的线程池
线程池代码链接
📖5. 总结
在探索Linux进程间匿名通信的管道机制这一旅程的尾声,我们不禁对Linux操作系统的精妙设计和强大功能有了更深一层的理解。管道,作为进程间通信的基础而又高效的工具,不仅简化了数据在不同进程间的流动过程,还极大地促进了多任务并发执行的灵活性
**通过本文的学习,我们见证了管道从创建到使用的全过程,理解了其背后的工作原理,并掌握了如何在实际编程中利用管道来实现进程间的数据交换。从
pipe()
函数的调用,到文件描述符的分配,再到数据的读写操作,每一个步骤都蕴含着Linux系统设计的智慧与匠心**
但Linux提供的进程间通信机制远不止于此。命名管道、消息队列、共享内存、信号量以及套接字等多种IPC方式,各自拥有独特的优势和适用场景。在未来的学习与实践中,我们可以继续深入探索这些机制,以更加灵活多样的方式实现进程间的协同工作
让我们以更加饱满的热情和坚定的信心,继续前行在Linux系统编程的学习之路上!
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!
版权归原作者 Eternity._ 所有, 如有侵权,请联系我们删除。