💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃个人主页 :阿然成长日记 👈点击可跳转
📆 个人专栏: 🔹数据结构与算法🔹C语言进阶🔹C++🔹Liunx
🚩 不能则学,不知则问,耻于问人,决无长进
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍
文章目录
1.
管道pipe
:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2.命名管道FIFO
:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3.消息队列MessageQueue
:消息队列是消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4.共享存储SharedMemory
:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
5.信号量Semaphore
:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.套接字Socket
:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
7.信号 ( sinal )
: 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
一、什么是System V共享内存
System V:System V和POXIS是一种应用于系统的接口协议,POXIS相对于System V可以说是比较新的标准,语法相对简单。
共享内存是一种进程间通信(IPC)的机制,允许多个进程共享同一块内存区域,以便它们可以直接读取和写入其中的数据,从而实现高效的数据共享和通信。
二、System V共享内存原理
简单来说就是不同的进程指向了同一块内存空间。
上面的图只是画了两个进程一个共享内存块,其实System V共享内存是指大量的共享内存块被组织起来的统称,假设有很多进程都在用共享内存,这样内存中也会出现大量的 共享内存块,所以OS要把这些共享内存块管理起来,方式:先描述再组织,这样要把共享内存属性抽象成数据结构,然后利用一些方式将这些数据结构组织起来
特点:
- 1.
共享内存
=共享内存块
+共享内存块对应的内核数据结构
- 2.共享内存块一定不属于任何一个进程,而是属于操作系统.
三、共享内存的使用原理
1.创建共享内存段:使用 shmget()
系统调用来请求创建一个共享内存段。该调用需要指定共享内存的大小、权限和标志等参数,并返回一个唯一的共享内存标识符shmid
。另一种就是通过mmap将/dev/mem映射出来。
2.连接到共享内存段:使用 shmat()
系统调用将当前进程附加到共享内存段。这个调用将返回共享内存段的地址
,并将该地址映射到
当前进程的虚拟地址空间
。
3.访问共享内存:连接到共享内存的进程可以通过在其地址上执行内存操作,直接读取和写入共享内存段中的数据。进程可以使用指针、数组或结构体等方式在共享内存段中存储和访问数据。
4.分离共享内存:当进程完成对共享内存的访问后,使用 shmdt()
系统调用将其与共享内存段分离。分离后,进程将无法再访问共享内存段,但共享内存段仍然存在。
5.删除共享内存段:当不再需要共享内存段时,可以使用 shmctl()
系统调用删除它。这个调用需要指定共享内存标识符和特定的控制操作,比如传递 IPC_RMID 参数表示删除共享内存段.
可以看到涉及到了大量的函数的调用,下面我们就来一一讲解这些函数的使用方法。
1.shmget()函数创建
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
格式:
int shmget(key_t key, size_t size, int shmflg);
- 参数:
key
:给每个共享内存块编了个号,这个号码是唯一的,所以只要拿到了编号,不同进程之间就能通过编号也叫做key值
找到同一块共享内存。key值需要用到ftok()
函数
size
:要创建的共享内存有多大
shmflg
:要设置的选项。选项如下表:
IPC_CREATE | 创建共享内存,如果底层已经存在,则获取并返回;如果不存在,则创建共享内存然后再返回, |
---|---|
IPC_CREATE | IPC_EXCL | 如果底层不存在,则创建共享内存并返回;如果底层存在,则出错返回。言外之意,如果返回成功,那么一定是一个全新的内存块! |
- 返回值:如果建立成功,则返回这段共享内存的标识符,否则返回-1并且错误码被设置。
作用:创建并获取一个共享内存
2.ftok()函数生成key
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
格式:
key_t ftok(const char *pathname, int proj_id);
- 参数:
pathname指针
:一个字符串,用于标识一个文件的路径名。通常会选择一个已经存在的文件,因为 ftok() 函数将使用该文件的inode编号和 proj_id 参数通过算法来生成键值key。
proj_id
:一个整数,作为用于生成键的项目标识号。该参数通常取一个非负整数。 - 返回值:成功则返回生成的键值,否则返回-1。
3.shmat()函数挂接
头文件:
#include <sys/types.h>
#include <sys/shm.h>
格式:
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
参数:
1.shmid
:共享内存段的标识符(ID),即通过调用 shmget() 函数创建共享内存时返回的 shmid。表示你想挂接哪一个共享内存。
2.shmaddr
:共享内存段连接到进程地址空间的首地址。通常将其设置为 NULL,指示系统选择适当的地址。如果想要指定特定的地址,可以传递一个非空的地址值。但不建议这样使用。
3.shmflg
:标志参数,用于指定连接共享内存的选项。常用的选项有:
(1) SHM_RDONLY:以只读方式连接共享内存,不允许写入。
(2)SHM_RND:将 shmaddr 参数忽略,系统选择一个地址以进行连接 -
返回值:返回值是void*,是一个指向共享内存段的指针,即连接到进程地址空间的首地址。我们需要将结果强转为我们需要的类型,一般为
char*
。
例如:
char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
- 这个shmaddr就指向
进程
地址空间的首地址
4.shmdt()取消挂载
这个函数要在shmat()的基础上使用,它需要使用shmat()返回的进程地址空间的首地址shmaddr
头文件:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
返回值:取消挂接成功,则返回0,失败则返回-1.
5.shmctl()删除共享内存
为什么要删除?
当我们创建好共享内存后,最后还需要删除它,因为共享内存的生命周期是随内核的!
不关闭的话,只要操作系统一直在运行,那么它就一直存在,占用空间资源。所以必须需要删除.
删除的方式有两种:
1.第一种使用指令ipcrm
ipcrm -m shmid
2. 第二种就是使用函数shmctl()
头文件:
#include <sys/ipc.h>//不同之处
#include <sys/shm.h>
格式:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 参数:
shmid
:共享内存段的标识符(ID),即通过调用 shmget() 函数创建共享内存时返回的 shmid。
cmd
:cmd:控制命令,用于指定要执行的操作类型。可以使用以下命令之一:
(1)IPC_STAT:获取共享内存段的状态信息,将结果存储在 buf 参数指向的 struct shmid_ds 结构体中。
(2)IPC_SET:设置共享内存段的状态信息,使用 buf 参数中提供的值。
(3)IPC_RMID:删除共享内存段,将其标记为删除状态,并在释放最后一个进程的附加段之后销毁。
buf
:一个指向 struct shmid_ds 结构体的指针,用于传递或接收共享内存段的状态信息。
通常使用的时候,第二个参数用IPC_STAT.第三个参数填nullptr
int n = shmctl(shmid,IPC_RMID,nullptr);
以上就是共享内存的使用过程中涉及到的函数。
四、共享内存的特点
采用共享内存进行通信的一个主要好处是效率高
,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。
五、共享内存的优缺点
优点
:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。
缺点
:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。