【Linux】vscode的使用 | 进程间通信(简单概括)(下)

简介: 【Linux】vscode的使用 | 进程间通信(简单概括)

3. 进程间通信

管道的进程具有独立性的

一个进程挂掉,不影响另一个进程, 可会增加通信的成本

要让两个不同的进程进行通信,前提条件是:先让两个进程看到同一份 资源

在操作系统内创建一份公共的资源,既不属于进程A,又不属于进程B,进程A能看到资源,进程B也能看到资源

把进程A生产的数据放入 资源中 ,进程B就可以拿到数据放入自己的上下文中

1. 简单举例

who

查看当前用户哪一个处于登录状态

ff28242d42d74f2aa021ad4d339f9d65.png


wc 统计文本行有多少行的命令

who | wc -l 统计当前正在登录用户的个数


d612014aae5146948ab86d8f254364b4.png

62727d76eb034b6db7aa2207b09525c3.png


who进程 以写方式打开文件

wc -l 进程 以读方式 打开文件

who进程将自己的标准输出重定向到管道中

wc -l 进程将自己的标准输入重定向到管道中


2.管道原理


def7b464f1bd44b2832bec9180bdf8bd.png

每一个进程被创建时都有自己的文件描述符表


1. 新创建的文件被打开时,有自己的缓冲区,它是由操作系统提供的纯纯的内存文件,不需要将自己的内容刷新到磁盘中 , 以读方式和写方式分别打开同一个文件


2. 当前进程进行一次fork

操作系统会为子进程创建PCB结构,操作系统也会把文件描述符表拷贝给子进程

父进程打开的文件内容不需要再次拷贝给子进程

因为是创建子进程,是需要把进程相关的内核数据结构拷贝就可以了,右侧属于文件系统,属于操作系统在内存中打开的文件


文件描述表中保存的是文件的地址,所以依旧会指向父进程所对应的文件


15e66fefdcf847a9bbea5f790e2a3702.png


管道只支持单向通信

确定数据流向,关闭关闭不需要的fd

若想要子进程进行写入,父进程进行读取,关闭子进程对应的读端,以及父进程的写端

此时就可以正常通信了


为什么把读写都打开,只打开读或者写不可以吗?

若只打开读方式打开,则被子进程继承下去后依旧是只能以读方式打开,无法进行数据交互的


3. 通过父子进程理解管道


0b26143fcc4f49d6b06c0e670fc0746f.png

在vscode中 点击新建文件夹,即可创建目录 pipe


035a202837db4a179e7b30459fb452e9.png


在目录pipe上 点击右键 新建文件 ,即可 生成 pipe.cc(cc结尾代表cpp) 的文件


1. 创建匿名管道


pipe 作用是 创建一个无名管道

pipe函数 参数是两个元素的数组

参数作为输出型参数


f93215839b3b407db66b39839b8935cb.png


要一次获得该管道文件的读和写,对应的是两个文件描述符,需要将两个文件描述符的数字返回


acbd39346102408c8720db85e1c76355.png


pipe的参数是一个数组,实际上传入的是数组首元素的地址

若返回值小于0,则通过errno(出错码)来得到出错结果

strerror 将错误码转换成错误码描述的


517bad86aa964f9abaac54ba120a0638.png


最终发现打印出来的结果 为 3 与 4 ,正好对应 数组中下标 3与4的位置


系统调用为什么可以使用c语言的errno

正常来说,是调用c语言接口出错了,才调用的errno 或者 strerror的

为什么调用系统调用接口时,也会使用 errno来说明错误的原因

系统调用接口是由系统使用c语言的一套软件


2.创建子进程以及通信

8b95e68d69e74b7fb0ea53cd5ed0bc21.png

关闭不需要的fd,让父进程进行读取,让子进程进行写入

一般认为pipefd[0] 为读端 , pipefd[1]为写端

用close来关闭文件描述符

所以关闭子进程的读端 ,关闭父进程的写端

将子进程变化的数据导给父进程


562d19b1f0c84ae589b770d282e51371.png


把namestr 字符串内容与 计数器 cnt 以及pid值 构建成一个字符串 打包给 父进程


使用snprintf函数 将amestr 字符串内容与 计数器 cnt 以及pid值写入buffer中,并规定传入buffer大小

c_str():返回const char*类型的指针


ssize_t write(int fd, const void *buf, size_t count);

fd代表文件描述符

buf代表 缓冲区

count代表 缓冲区大小

使用write 将缓冲区的count大小的数据写入 fd中


将buffer中的所有数据都传入读端中


3. 父进程读取消息


a186df0d4ebe40b29d27d98452f17476.png


使用write 将缓冲区的count大小的数据写入 fd中

ssize_t read(int fd, void *buf, size_t count);

从文件描述符fd中将我们想要的数据,按照数据块的方式读取出来


返回值代表多少字节,读取到文件结尾为0,失败为-1

read读取时并不会把buffer当作一个字符串,而我们要把buffer看作是一个字符串,所以要预留出\0的位置

即 sizeof(buffer)-1

将读端读取到buffer字符串的内容


4. 完整代码

#include<iostream>
#include<cerrno>//C++提供
#include<unistd.h>
#include<string.h>
#include<cassert>
#include<string>
#include<cstdio>
#include<stdlib.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    //1.创建管道
   int n=pipe(pipefd);
   //返回值为0 则成功
   if(n<0)//说明出错
   {
     cout<<"pipe error,"<<errno<<": "<<strerror(errno)<<endl;
     return 1;
   }
   //返回0和1里面的文件描述符
   cout<<"pipefd[0]: "<<pipefd[0]<<endl;
   cout<<"pipefd[1]: "<<pipefd[1]<<endl;
    //2.创建子进程
     pid_t id=fork();
     assert(id!=-1);//返回-1,说明创建子进程失败
     if(id==0)
     {
        //子进程
        //让父进程进行读取,让子进程进行写入
        close(pipefd[0]);//关闭子进程的读端
        //开始通信
       const  string namestr="hello,我是子进程";
       int cnt=1;
       char buffer[1024];
        while(true)
        {
          snprintf(buffer,sizeof(buffer),"%s:计数器,PID:%d\n",namestr.c_str(),cnt++,getpid());
          write(pipefd[1],buffer,strlen(buffer));
        }
        close(pipefd[1]);//当子进程用完,就关闭      
        exit(0);//退出
     }
    //父进程
    //关闭不需要的fd (文件描述符)
    close(pipefd[1]);//关闭父进程的写端
    //4.开始通信
    char buffer[1024];
    while(true)
    {
     int n=read(pipefd[0],buffer,sizeof(buffer)-1);
     if(n>0)//读取成功
     {
           buffer[n]='\0';
           //由子进程传过来的消息
           cout<<"我是父进程:child send give message:"<<buffer<<endl;
     }
    }
      close(pipefd[0]);//关闭父进程的读端
   return 0;
}

4. 管道特点

1.单向通信

2.管道本质是文件,因为fd的声明周期随进程,管道的生命周期随进程的

3.管道通信 ,通常用来进行具有血缘关系的进程,来进行进程通信的,常用于父子通信

pipe打开管道,并不清楚管道的名字,被称为匿名管道

4.管道面向字节流(对写入和读取的次数无关)

5.具有一定的协同能力,让读端和写端能够按照一定的步骤进行通信

(若写端写满了,就需要等待读端读好才能继续写

当读端把管道的数据读完后,如果写端不发数据,读端只能等待)


5. 场景

1. 如果我们read读取完毕了所有的管道数据,如果对方不发,就只能等待

2. 如果写端将管道写满了,就不能再写了

3.若关闭写端,读取完毕管道数据,再读,就会read返回0,表明读到了文件结尾

4.写端一直写,读端关闭,没有意义操作系统不会维护无意义,低效率,或者浪费资源的事情,操作系统会通过信号来终止进程(13 SIGPIPE)

相关文章
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
250 67
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
138 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
108 20
|
10月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
275 1
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
86 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
118 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
84 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
82 0
|
7月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
241 4