• 进程间通信


    目录

    进程间通信的介绍

    匿名管道

    命名管道

    共享内存

    信号量


    进程间通信的介绍

    进程之间可能会存在特定的协同工作的场景!如下图

    概念:一个进程要把自己的数据交付给另一个进程,让其进行处理

    因为进程具有独立性!所以如果要进行进程间通信,就得交互数据,成本一定很高,同时,操作

    系统也要设计通信方式!

    由于进程具有独立性,所以一个进程是看不到另一个进程的资源的,而两个进程要互相通信,那就

    必须得先看到一份公共的资源!这个资源就是一段内存(属于操作系统)——可能以文件的方式提

    供,也可能以队列的方式,也可能提供的就是原始的内存块,这也是通信方式有很多种的原因!

    进程间通信的前提本质:有os参与,提供一份所有通信进程能看到的公共资源

    匿名管道

    如下图,父子进程能够看到一份公共的资源文件,父进程执行后,将数据写入缓冲区,但

    是不刷新到磁盘,交给子进程去做,这种经由文件的方式,叫做管道

    如下图,pipe是用来创建管道的系统调用接口,其中pipefd[2]是输出型参数,我们想通过这个参

    数读取到两个打开的fd

    如下图,让父子进程进行通信,子进程写,父进程读,那子进程就关闭0,父进程就关闭1

    注意:子进程有sleep,父进程没有sleep

    如果read的返回值为0,意味着子进程关闭文件描述符了!

    如下图,子进程没有sleep,父进程有sleep,子进程不会去等父进程读了才去写,而是会以字节为

    单位一个个地写

    如下图,让子进程每一次写一个字节,父进程一直休眠,当读满64KB时,子进程也就不再写了,

    因为管道有大小!至于为什么不写了,是因为要让父进程来读,否则继续写,就会把之前写的覆盖

    掉,前面的工作就白做了!!!

    如下图,让父进程每隔一秒读一次

    如下图,让子进程每隔5秒写一次,父进程没有sleep,可得知此时以写为主

    如下图,子进程5秒后写一次,然后将1关闭,然后退出,父进程也会随之退出

      

    如下图,子进程一直写,而父进程等5秒后再读,然后break跳出来关掉0,父进程结束,而此时子

    进程还在写入,站在操作系统层面,是严重不合理的,因为已经没有人读了,你还在写,本质上

    就是在浪费os的资源,os会直接终止子进程!os给目标进程发送SIGPIPE信号!

      

    如下图,获取子进程退出的情况

     

    总结

    pipe本质:是通过子进程继承父进程资源的特性,达到让不同的进程看到同一份资源!

    4种情况

    读端不读或者读得慢,写端要等读端

    读端关闭,写端收到SIGPIPE信号,进程终止

    写端不写或者写得慢,读端要等写端

    写端关闭,读端读完pipe内部的数据然后再读,会读到0,表明读到文件结尾!

    5个特点

    管道是一个只能单向通信的通信信道

    管道是面向字节流的!——tcp、FILE、fstream

    仅限于父子通信——具有血缘关系的进程进行进程间通信

    管道自带同步机制,原子性写入

    管道的生命周期是随进程的!

    注意:管道是文件,如果一个文件只被当前进程打开,相关进程退出了,被打开的文件,会被os

    自动关闭!(引用计数ref为0)

    命名管道

    如下图,命名管道是用mkfifo指令创建的文件,以p开头,echo写入,cat来读取,即一个进程将

    据交给另一个进程,这就是管道文件

    我们通常标识一个磁盘文件,是用路径/文件名的方式,具有唯一性!!!

    概念

    如下图,A、B进程用路径/文件名的方式将同一份文件从磁盘中打开并加载到内存中,然后A进程

    写入数据到文件,B进程从文件中读取数据,这就是命名管道!

    具体实现

    首先创建两个文件server.c和client.c,让其都成为可执行程序,然后成为两个进程,client.c用来

    入,server.c用来读取,Makefile文件和需要用到的头文件,如下图

    如下图,首先在server.c文件中用mkfifo创建一个管道文件,同时还需要设置一下掩码,否则我

    们自己输入的参数066,与创建的管道文件的权限不是对应的

    如下图,在client.c中,我们从键盘读取数据,然后再拿到数据进行写入,再在server.c中,我们

    取数据!

    注意:必须server先执行,client后执行,否则无法运行,因为管道文件是server中创建的!

    因为命名管道也是基于字节流的,所以实际上,信息传递的时候,是需要通信双方定制协议的!

    (这里不考虑)

    如下图,在server.c中创建一个子进程,以达到一个进程控制另一个进程的目的!

    如下图,让读取的进程每读一次就休息10秒钟,命名管道的数据,为了效率 ,不会刷新到磁盘!

    我们之前的pipe叫作匿名管道,文件没有名字,是因为它是通过父子继承的方式,看到同一份资

    源,不需要名字来标识同一个资源!

    我们现在的pipe叫作命名管道,为了保证不同的进程看到同一份文件,是必须得字!

    SystemV 

    前面讲的管道都是基于文件的通信方式,而SystemV是在os层面专门为进程间通信设计的一个方

    案,由计算机科学家和程序员设计,给用户使用,又因为os不信任任何用户,给用户提供功能的

    时候,采用系统调用的方式

    三种方案:共享内存;消息队列(有点落伍);信号量

    共享内存

    如下图,用两步操作,来让不同进程看到同一份资源!

    通过某种调用,在内存中创建一份内存空间

    通过某种调用,让进程(参与通信的多个进程)"挂接"到这份新开辟的内存空间上!

    挂接:即在进程的地址空间中,开辟一块虚拟空间,让其起始地址通过页表可以映射到新开的物理

    内存空间的起始地址 

    共享内存不用了,也分为两步操作

    去关联(去挂接)

    释放共享内存

    os内可能存在多个进程,同时使用不同的共享内存来进行进程间通信,即共享内存在系统中可能

    在多份!那操作系统就必须得管理这些不同的共享内存,管理方式:先描述,再组织

    共享内存一定要有一定的标识唯一性的ID,方便让不同的进程能识别同一个共享内存资源!这个ID

    是存放在描述共享内存的结构体中的!

    这个唯一的标识符,是用来进行进程间通信的,本质:让不同的进程能看到同一份资源——你得先

    同的进程看到同一个ID(需要由用户自己设定)

    认识接口

    创建共享内存

    参数key_t key 

    需要通过接口ftok来获取key,这个接口有两个参数,一个是自定义路径名,另一个则是自定义项

    目ID,这两个可以随便填,但是不一定成功,返回-1就失败了!

    这个key也就是前面所提到的唯一性的ID!!!会设置进内核的关于shm在内核中的数据结构中!

    参数size

    即你想要创建的共享内存的大小,建议是4KB的整数倍!

    参数shmflg

    这个参数可能是单独的IPC_CREAT,也可能是IPC_CREAT | IPC_EXCL,而IPC_EXCL单独使用

    有意义!

    如果单独使用IPC_CREAT,或者flg为0:创建一个共享内存,如果创建的共享内存已经存在,则

    直接返回当前已经存在的共享内存,不存在则创建(基本不会空手而归)

    IPC_CREAT | IPC_EXCL:如果不存在共享内存,则创建,如果已经有了共享内存,则返回出

    错!(意义:如果我调用成功,得到的一定是一个最新的,没有被别人使用过的共享内存)

    只要我们形成key的算法+原始数据是有意义的,形成同一个ID,就能保证不同的进程看到的是同

    个共享内存!

    如下图,打印key值和shmid的返回值

    如下图,./server执行完毕了,进程运行结束,但是该进程曾经创建的共享内存没有被释放!

    如下图,用ipcs -m查看共享内存,ipcs不带选项会默认显示消息队列,共享内存,信号量

    systemV的IPC资源,生命周期是随内核的!只能通过程序员显示的释放(命令或系统调用、或os

    重启)

    用命令释放IPC资源

    如下图,用命令ipcrm -m来释放共享内存,同时,可得知,

    key:只是用来在系统层面进行标识唯一性的,不能用来管理

    shmid:是os给用户返回的id,用来在用户层进行shm管理

    控制共享内存(只讲删除)

    参数shmid是创建共享内存接口shmget的返回值,cmd由于只讲删除,所以传IPC_RMID,后面

    结构体指针传NULL即可

    如下图,用shmctl来释放共享内存

    如下图,在创建共享内存的最后一个参数中再按位或上一个数字,比如0666,来为共享内存创建

    权限!

    如下图,shmat,让进程"挂接"到新开辟的内存空间上,参数shmid是shmget的返回值shmaddr

    是确定挂接到地址空间的某个范围,我们无法确定,就由操作系统去做,所以shmaddr和shmflg

    使用时就设为NULL和0即可!返回值是创建的共享内存的起始地址,不过这个地址和malloc的返

    回值一样,都是虚拟地址!!!

    shmdt:去关联,参数是shmat的返回值,注意,并不是释放共享内存,而是取消当前进程和共享

    内存的关系!

    如下图,是对"挂接"的一个实验验证,nattch是"挂接"共享内存的进程个数

    如下图,当client没有写入,甚至没有启动的时候,server端会直接读取shm,但是没有数据,根

    本不会等待client写入

    共享内存不提供任何同步或互斥机制,需要程序员自行保证数据的安全!

    共享内存一旦建立好并映射进自己进程的地址空间,该进程就可以直接看到该共享内存,就如同

    malloc的空间一样,不需要任何系统调用接口(比如read或write)!所以共享内存是所有的进程间

    中速度最快的!

    read或write的本质,将数据从内核拷贝到用户,或者从用户拷贝到内核

    SIZE

    如下图,因为共享内存在内核中申请的基本单位是页,内存页(4KB),所以建议共享内存申请

    4096的整数倍,而如果你要申请4097个字节,操作系统就给你4097个字节,但是实际上操作系统

    在底层申请的时候,是申请的4096*2个字节

    信号量

    管道(匿名or命名),共享内存,消息队列,都是以传输数据为目的的!信号量不是以传输数据为

    目的的!通过共享"资源"方式,来达到多个进程的同步和互斥的目的!

    临界资源:凡是被多个执行流同时能够访问的资源就是临界资源!比如:同时向显示器打印!进程

    间通信的时候,管道,共享内存,消息队列等,都是临界资源

    凡是要进行进程间通信,必定要引入被多个进程看到的资源(通信需要),同时,也引入一个新的

    问题,临界资源的问题!

    信号量的本质,是一个计数器,类似int count,衡量临界资源中的资源数目

    以电影院的某个放映厅为例,要看电影,你就得买票,而买票的本质就是对临界资源的预定机制,

    而一个放映厅最怕的就是只有100个座位,却卖出了110张票(卖不出去的情况不考虑),所以就需要

    信号量来控制,卖出一张票count--,访问电影院座位的人离开count++

    临界区:进程的代码可是有很多的,其中,用来访问临界资源的代码,就叫做临界区

    原子性:一件事情要么不做,要么就做完,没有中间态,就叫做原子性!比如父子进程,都能看到

    一个变量count,值为100,父子进程对其进行--操作,父进程先开始--,值为99,再到子进程运行

    完后,值为5,父进程再去操作时,又从99开始--,多进程对全局数据操作出现错乱问题,也证明

    count--本身不是原子的

    每个人想进入电影院,必须先对count--,前提时每个人都得先看到count!count本身也是临界资

    源!!!信号量本身就是资源!就要求信号量的--和信号量的++操作是安全的,所以必须是原子

    的,也被称为p操作和v操作

    互斥:任意一个时刻,只能允许一个执行流访问临界资源,执行它自己的临界区,比如有一间VIP

    自习室,只能一个人自习,即int sem = 1,当有人在的时候,其他人就无法进入,当他离开,

    sem--,当没有人在,有人进入,sem++,sem只有0和1两种值,也被称为二元信号量!

    注意:信号与信号量,根本毫无关系!!!

  • 相关阅读:
    Hi3559av100 u-boot bootargs bootcmd
    护网攻防演练-内网横向移动总结
    在 Ubuntu 环境中安装 Go 语言及运行脚本
    使用GParted为Ubuntu根目录扩容
    react18-webchat网页聊天实例|React Hooks+Arco Design仿微信桌面端
    [激光原理与应用-38]:《光电检测技术-5》- 光学测量基础 - 光调制
    Redis Cluster 集群的介绍
    基于 Appium 的 Android UI 自动化测试!
    每日一文(第三天)
    安装单机hbase
  • 原文地址:https://blog.csdn.net/weixin_58867976/article/details/126821879