Linux进程间通信解析

什么是进程?

1. 进程是指程序的一次执行过程,同一个程序,每次执行均为不同的进程。

2. 进程是系统资源分配的最小单位。

进程间通信的目的

操作系统往往会为每一个进程开辟一个独立的虚拟内存空间,从而使得进程间可以各司其职、互不干扰。但是在一些稍微复杂点业务场景中,往往又需要进程之间相互配合,进行资源共享、数据传输、信号传递的操作,因此需要一定的手段来帮助我们实现进程间通信。

进程间通信的方式常用的有一下6种:

  1. 管道
  2. 消息队列
  3. 共享内存
  4. 信号量
  5. 信号
  6. socket

下面我们来详细解析其中的若干种通信方式。

1. 管道通信(pipe)

            管道是最古老的一种通信方式

管道可以分为两种:

1.1 匿名管道

           匿名管道只能用于具有血缘关系的进程之间进行通信,并且是半双工的,即 使用管道通信时,数据只能在管道的一端进 然后在另一端出。具有一下特性

  1. 本质是内核的缓冲区(伪文件)(借助内核缓冲区以环形队列的形式实现)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端
  3. 管道中的数据 只能从写端流入,读端流出。

具有以下局限性:

  1. 数据支持读取自己写入的数据
  2. 数据一旦读出,就不再管道里存留,因此一份数据不能读取多次
  3. 半双工,数据只能单向流动
  4. 具有血缘关系的进程间使用。

PIPE函数原型为

      int pipe(int pipefd[2]); 成功返回 0 失败返回-1 设置errno

      以上函数调用成功之后会返回两个文件描述符。无需Open,但需要手动close。规定fd[0] --> read, fd[1]-->write。因此 向文件读写数据就是在读写内核的缓冲区。

管道创建成功之后,往往是父进程掌握这管道的两端,具体实现父子进程间通信的方式 通常采用以下过程

  • 父进程调用pipe函数,创建了管道,并得到两个文件描述符, fd[0]-->read, fd[1]-->write.
  • 父进程调用fork函数创建子进程,此时 子进程也同时具有指向管道两端的两个文件描述符
  • 父进程关闭管道的读端,子进程关闭管道的写端。这样父进程就可以通过写端向管道中写入数据,子进程可以通过读端从管道里读取数据。此时就实现了父子进程间的通信。

管道的读写行为

管道的读写有以下四种注意事项

  1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),此时如果有进程想要从管道的读端读取数据,那么当管道里的数据全部被读出之后会返回0,类似于文件读取 读到了末尾。
  2. 如果所有指向管道写端的文件描述符没有全部关闭(管道写端引用技术大于0), 此时如果有进程想要从管道的读端读取数据,那么管道中的数据全部读完之后,再次read会发生阻塞,直到有进程通过写端往管道里写入数据,read才会返回读取到的数据。
  3. 如果所有指向管道读端的文件描述符全部关闭了(管道读端引用计数为0),此时如果有进程想要通过写端往管道里写入数据,那么该进程会收到SIGPIPE信号,通常会引起进程异常终止 或 对SIGPIPE信号进行捕获 然后做其他的操作。
  4. 如果所有指向管道读端的文件描述符没有全部关闭(管道读端引用计数大于0), 那么此时如果有进程想要往管道里写入数据,那么在管道写满之后,也会发生阻塞,直到有进程从写端读取数据之后,写入端才能写入数据并返回。

而如果我们想要使用匿名管道实现进程间数据双向流动,那么就需要借用两个管道来实现,分别关闭父子进程的一个读端和写端即可实现双向传输。

1.2 命令管道(fifo)

 由于匿名管道基于fork函数来实现通信过程,因此只能局限与父子进程间的通信,而命名管道FIFO则被用于非血缘关系的进程之间通信,原理如下:

FIFO本质上同属于一种特殊的文件类型,它在文件系统中具有对应的路径, 因此进程之间可以通过路径的方式对管道进行读写,具体操作的过程基本和匿名管道一一致。

fifo函数原型

int mkfifo(const char *filename, mode_t mode)

其中filename为所创建的管道的路径,mode表示为该文件设置的权限。

FIFO具有下面两种用途

  1. FIFO由shell命令使用以便将数据从一条管道传输到另一条,为此无需创建中间临时文件。
  2. FIFO用于客户端进程和服务端进程应用程序中,以在客户进程之间传送数据。

FIFO用于 client-server之间通信的实例:

如果有一个服务端进程和多个客户端进程有关,那么每个客户进程都可以将其请求指令写到该服务端进程创建的fifo中,所有需要和服务端进程通信的进程都直到该FIFO的路径,然后服务端进程通过读取该FIFO文件给出相应的响应。

但是以上的安排具有一些问题,比如,服务端进程如何得知某条指令来源于哪个客户端呢?

对应的一种解决思路是 让每个客户端进程在写入的指令中包含自己的PID,然后服务端进程根据指令中的PID为每个进程创建独享的FIFO,这样每个客户端进程就可以在自己独享的FIFO里面获得服务端进程所给出的相应了。

上面的方案 仍旧存在一些缺陷,比如服务端进程无法判断客户端进程的状态,也就是无法得知客户端进程是否已经终止运行,这就使得服务端进程需要一直维护每一个客户端进程的专用FIFO。 另一个不足之处是服务器进程还必须捕获SIGPIPE信号,因为客户端进程在发送一个请求之后 没有读取数据就有可能终止,于是留下一个写进程(服务端进程)而无读进程的客户端进程专用FIFO,(有兴趣大家可以自行思考如何解决)

2. 消息队列

消息队列是存放在内核中并由消息队列标示符标示的消息组成的消息链表。

消息队列提供了从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是一个类型,接受者可以接收不同类型的消息。

特点:

全双工,可读可写

生命周期随内核(往往需要手动释放),与进程无关;

消息队列需要手动删除 释放IPC资源,可通过一下命令实现

linux 下 两个相关的命令:

ipcs  -q 显示IPC资源

ipcrm  -q msqid 手动删除IPC资源

通信流程

1. 创建消息队列

       int msgget(key_t key, int msgflag)   // 创建消息队列

      参数:  key消息队列关联的标示符,本质是一个整数,可以对int类型强制转换得到

                  msgflag:有两个选项IPD_CREATE、IPC_EXCL, 单独使用IPC_CREATE 如果消息队列不存在则创建一个,如果已经存在则打开; 单独使用IPC_EXCL没有意义;两个同时使用,如果消息队列不存在 则创建,如果已经存在 则出错返回。

       返回值: 成功 返回一个非负数,即该消息队列的表示码, 失败则返回-1;

    消息队列控制函数

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgctl(int msqid,  int cmd, struct msqid_ids *buf);

参数:

  • msqid: 由msgget函数返回的消息队列标识码
  • cmd    :有三个可选值,
    • IPC_STAT 把 msqid_ds结构中的数据设置为消息队列当前的关联值。
    • IPC_SET在进程有足够的权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
    • IPC_RMID删除消息队列

返回值:

           成功返回0  失败返回-1

int msgsnd(int smqid, const void *msgp, size_t msgsz, int msgflg);

参数:

  • msgid  由msgget返回的消息队列id
  • msgp  指针指向将要发送的消息
  • msgsz msgp 指向的消息的长度
  • msgflag   默认0

返回值:成功返回0  失败返回-1

4. msgrcv

从一个消息队列里 接受消息

size_t smgrcv(int msgid, void *msgp, size_t msgsz, long msgtype, int msgflag);

参数与msgsnd相同

以下借鉴一张图 方便理解通信过程

关于消息队列了解的不是很多,也没有找到很好的资源,所以这里就简单介绍一下基本的用法。

3. 共享内存

共享内存,顾名思义 多个进程之间通过共用一块内存区域的方式 从而实现进程间通信的过程。

因为使用共享内存进行进程间通信,数据一直保持在内存里面(同时也不需要额外的用户态 内核态的切换),所以是进程间最高效的一种通信方式。

借一张原理图

由于多个进程同时访问共享内存会引发同步和互斥的问题,所以通常情况下 共享内存往往与信号量一同使用。

 

未完待续。。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值