Linux进程常见通信方式

本文详细介绍了进程间通信的三种主要方式:管道(包括匿名管道和命名管道)、共享内存及信号量。探讨了每种通信方式的工作原理、应用场景及具体实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要进程要进行通信呢?

进程间可能存在特定的协同工作的场景,这个时候就需要一个进程把自己的数据交付给另一个进程,让其进行处理。

进程通信的本质

因为进程具有独立性,那么两个进程要互相通信,必须得先看到同一份资源(一段内存,内存可以以文件方式提供、也可能以队列的方式提供、也可能提供的就是原始的内存块)。进程通信的本质就是,由操作系统参与提供一份所有通信进程都能看到的资源

通信的方式

1、管道

什么是管道

1、管道是Unix中最古老的进程间通信的形式。
2、我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
在这里插入图片描述

<1>匿名管道

#include <unistd.h>
原型 int pipe(int fd[2]);
功能:创建一无名管道
参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件描述符中读取数据
参数:fd:文件描述符,buf:目标缓存区, count:期望读取的数据字节大小
返回值:如果成功,则返回读取的字节数(0表示文件结束),读取出错返回-1

ssize_t write(int fd, const void *buf, size_t count);
功能:向文件描述符中写入数据
参数:fd:文件描述符,buf:目标缓存区, count要写入的数据字节大小
返回值:如果成功,则返回写入的字节数(0表示什么都没有写入)。如果出错,返回-1

读写规则

1、如果所有管道写端对应的文件描述符被关闭,则read返回0
2、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
3、当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
4、当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

代码
在这里插入图片描述

运行结果:

在这里插入图片描述

通过上面的3, 4我们可以注意到为啥不是3,5或3,6呢?其实文件描述符的本质就是个数组,因此是有顺序的。

Linux下管道大小

我们可以把上面代码中的29行放开,每次只写一个字符进去,50行读的时间延迟执行就会发现,管道是有大小的,大小为64KB。如下图运行结果:
在这里插入图片描述
上面读写规则中的第三四条也就是因为这个原因。

站在文件描述符角度理解管道

在这里插入图片描述

站在内核角度理解管道

看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

匿名管道特点

1、只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
2、管道提供流式服务(面向字节流的)
3、一般而言,进程退出,管道释放,所以管道的生命周期随进程
4、一般而言,内核会对管道操作进行同步与互斥(自带同步互斥)
5、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

通信中的四种情况
1.读端不读或读的慢,写端(写满后)要等待读端
2.读端关闭,写端收到SIGPIPE信号直接终止
3.写端不写或写的慢,读端要等写端
4.写端关闭,读端要读完pipe内部的数据然后在读,会读到0表示读到文件结尾。

<2> 命名管道

为了解决匿名管道只能父子通信,引入了命名管道。

命名管道创建

命令行创建

mkfifo filename
在这里插入图片描述
通过程序创建
相关函数:

int mkfifo(const char *filename,mode_t mode)
功能:创建一个特殊文件
参数:filename: 文件名,mode: 权限模式
返回值:成功返回0,失败返回-1

用命名管道实现server&client通信

server
在这里插入图片描述

client

在这里插入图片描述

运行结果:
在这里插入图片描述

命名管道和匿名管道的区别

1、匿名管道由pipe函数创建并打开。
2、命名管道由mkfifo函数创建,打开用open
3、FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

2、共享内存

原理

1、通过调用库函数shmget,在内存中创建一块内存空间。
2、通过调用库函数shmat,让进程 “挂接” 到这块共享内存上。
如图:
在这里插入图片描述
相关函数

功能:用来创建共享内存
原型 int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmflg中的
IPC_CREAT:to create a new segment,单独使用或flg为0,创建共享,内存若已存在则返回共享内存
IPC_EXCL : 单独使用没有价值
IPC_CREAT | IPC_EXCL 失败返回-1,若我成功则得到一定是最新共享内存

功能:生成key秘钥
原型:key_t ftok(const char *pathname, int proj_id);
参数:
pathname:路径名, proj_id是子序号,它是一个8bit的整数。即范围是0~255。
返回值:如果成功,将返回生成的key_t值。失败时返回-1

功能:将共享内存段连接到进程地址空间 (attach)
原型 void *shmat(int shmid, const void *shmaddr, int shmflg);
参数 shmid: 共享内存标识 shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

功能:将共享内存段与当前进程脱离 (detach)
原型 int shmdt(const void *shmaddr);
参数 shmaddr:由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

功能:用于控制共享内存
原型 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

在这里插入图片描述

共享内存总结

  1. 共享内存不提供任何同步或者互斥机制,需要程序员自行保证数据安全
  2. 共享内存是所有进程间通信最快的
  3. shmget中第二个参数size一般为4096字节的整数倍(共享内存在中申请的基本单位是页,内存页4kb)如果申请4097->内核给4096 byte*2,实际查看是4097但系统按两页申请。

用享内存实现server&client通信

server端
在这里插入图片描述

client端
在这里插入图片描述
运行结果:
在这里插入图片描述

3、信号量

什么是信号量

管道,共享内存、消息队列都是以传输数据为目的的。
信号量不是以传输数据为目的的,他是通共享资源的方式来达到多个线程的同步和互斥的目的的。

学习信号量我们要知道的几个概念

1、信号量的本质:是一个计数器,类似 int count;衡量临界资源中资源数目的。
2、什么是临界资源:凡是能够被多个执行流同时能够访问的资源就是临界资源,如管道,共享
内存,消息队列等都是临界资源。
3、什么是临界区:进程代码有很多,其中用来访问临界资源的代码,就叫做临界区。
4、什么是原子性:一件事要么不做,要做就做完,没有中间态,就叫做原子性。
5、什么是互斥:在任意时刻只允许一个执行流进入临界资源,执行它自己的临界区。
6、什么是同步:让访问临界资源的过程在安全的前提下,有一定顺序性。

信号量的代码我们在后面的生产消费者模型中讲解。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小朱同学..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值