前言
作者:小蜗牛向前冲
名言:我可以接受失败,但我不能接受放弃
如果觉的博主的文章还不错的话,还请点赞,收藏,关注支持博主。如果发现有问题的地方欢迎❀大家在评论区指正
本期学习目标:认识什么是 system V共享内存,认识共享内存的接口函数,学会运用共享内存
目录
一、共享内存的基本原理
1、什么是共享内存
2、共享内存和管道的对比
二、共享内存的系统接口
1、shmget函数(创建共享内存)
2、shmat函数(关联)
3、shmdt函数(去关联)
4、shmctl函数(控制)
三、用共享内存进行通信
1、comm.hpp
2、server.cpp
3、client.cpp
4、现象
共享内存是一种进程间通信机制,它允许两个或多个进程共享同一块物理内存空间,从而实现数据共享。在共享内存中,进程可以通过读写共享内存的方式来相互通信,而不必进行复杂的管道、消息队列等进程间通信操作。
那我们知道了共享内存,其实就是OS操作系统管理的一块共享物理内存,那共享内存是怎么样实现进程间通信的呢?
下面我们看图来理解
首先我们让操作系统在物理内存中,创建一块共享内存。然后在将创建的内存通过页表映射到进程地址空间,这样 A和B进程就通过共享内存建立起来联系,A进程如果想和B进程通信,只要让A进程往物理内存中写数据,在让B进程读数据就可以了。最后我们不在想让AB进程进行通信,我们只要去关联就可以了,AB进程的关联无非是共享的物理内存,所以我们只要取消二者的映射关系,在释放内存即可。
那这种方式和我们前面将的管道将有什么优缺点吗?
客官别走,且听我细细道来。
共享内存和管道是两种不同的进程间通信机制,它们各有优势和适用场景。下面是它们的对比:
数据传输方式:
- 共享内存:进程直接访问同一块物理内存,数据在内存中共享。
- 管道:通过缓冲区进行数据传输,数据在缓冲区中依次流动。
通信效率:
- 共享内存:由于直接访问内存,读写效率高,适用于大量数据交换的场景。
- 管道:数据需要经过内核空间,相对较慢,适用于小量数据传输或者不频繁的通信。
数据同步和通信方式:
- 共享内存:需要考虑并发控制和同步问题,通信方式更为灵活,可以使用类似锁、信号量等机制进行同步。
- 管道:基于先进先出的原则,数据流动是单向的,不需要显式的同步操作。
可扩展性:
- 共享内存:多个进程可以同时访问共享内存,适用于多个生产者和消费者的情况。
- 管道:一般情况下,只支持一个生产者和一个消费者,不适用于多个进程之间的数据交换。
综上所述,共享内存适用于大量数据交换、需要高效率和灵活同步的场景,而管道适用于小量数据传输或者不频繁的通信,并且只涉及一个生产者和一个消费者。
功能:用来创建共享内存
原型: int shmget(key_t key, size_t size, int shmflg);
参数 :
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样
返回值:成功返回一个非负整数,即该共享内存段的标识码(shmid);失败返回-1
key是什么
key是共享内存段的名字,我们要注意他是多少我们不关系,我们关心的是他的作用是能够进行唯一的标识。
虽然我们不关系他是多少,但是我们要传什么key给shmegt,key_t 是一个什么类型。
这里我们先看一个获取k的函数ftok()
。那key_t是个什么类型?
key_t
是一个在Unix/Linux系统中用于表示IPC(进程间通信)键的类型。它被定义为一个整数类型(通常是int
),用于唯一标识共享内存、消息队列和信号量等进程间通信机制。
举例子获取k:
#define PATHNAME "."
#define PROJ_ID 0x66
key_t getKey()
{
key_t k = ftok(PATHNAME,PROJ_ID);
if(k < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
其中的ftok() 函数使用由给定路径名命名的文档的标识(必须引用现有的、可访问的文档)和最不重要的 8 位proj_id(必须为非零)生成一个key_t类型的 System V IPC 密钥
shmflg是什么
shmflg是标志位,用于指定操作共享内存的方式这里我们举例二种最常见的标志位:
IPC_CREAT:如果共享内存不存在就创建,如果存在就获取他。
IPC_EXCL:无法单独使用,IPC_CREAT|IPC_EXCL如果不存在创建共享内存,如果存在就出错返回。
shmget的用法:
#define MAX_SIZE 4096
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k,MAX_SIZE,flags);
if(shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
功能:将共享内存段连接到进程地址空间
原型 :void *shmat(int shmid, const void *shmaddr, int shmflg);
参数: shmid: 共享内存标识 shmaddr:指定连接的地址 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
这里我们要注意的是返回指针可以认为是共享内存的起始地址。
shmflg:
SHM_RND
:将size
参数舍入到系统页面大小的倍数。这样做可以保证共享内存段的大小是系统页面大小的整数倍,以提高性能。
SHM_RDONLY
:以只读方式打开共享内存段。这意味着进程只能读取共享内存中的数据,不能进行写操作。这个标志通常用于让多个进程共享只读数据的情况
用法:
//返回指是共享内存的开始地址
void* attachShm(int shmid)
{
void* start = shmat(shmid,nullptr,0);
if((long long)start == -1L)
{
std::cerr << errno << ":" << strerror(errno) <
功能:将共享内存段与当前进程脱离
原型: int shmdt(const void *shmaddr);
参数 :shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1 注意:将共享内存段与当前进程脱离不等于删除共享内存段
用法:
void detachShm(void *start)
{
if(shmdt(start) == -1)
{
std::cerr <<"shmdt: "<< errno << ":" << strerror(errno) << std::endl;
}
}
功能:用于控制共享内存
原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid:由shmget返回的共享内存标识码 cmd:将要采取的动作(有三个可取值) buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
这里我们重点关注:参数cmd
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET:在进程有足够权限的前提下,把共享内存的当前值设置为shmid_ds数据结构中给出的值
IPC_RMID:删除共享内存
struct shmid_ds *buf参数保存着共享内存的模式状态和访问权限的数据结构,里面存放这共享内存的共享参数
下面我们将用共享内存实现server和client的通信
我们定义一个comm.hpp的头文件定义server和client共同使用的函数:
#ifndef _COMM_HPP
#define _COMM_HPP
#include
#include
#include
#include
#include
#include
#include
#include
#define PATHNAME "."
#define PROJ_ID 0x66
#define MAX_SIZE 4096
//获取密钥k
key_t getKey()
{
key_t k = ftok(PATHNAME,PROJ_ID);
if(k < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
//调用shmget创建共享内存
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k,MAX_SIZE,flags);
if(shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
//获取shmid标识码
int getShm(key_t k)
{
return getShmHelper(k,IPC_CREAT);
}
//调用getShmHelper
int createShm(key_t K)
{
return getShmHelper(K,IPC_CREAT | IPC_EXCL | 0600);
}
//返回指是共享内存的开始地址
void* attachShm(int shmid)
{
void* start = shmat(shmid,nullptr,0);
if((long long)start == -1L)
{
std::cerr << errno << ":" << strerror(errno) <
这里我们让server进程,打印k和shmid值给我们看一下,并进行创建共享内存,并且进行和client进行通信
#include"comm.hpp"
#include
int main()
{
key_t k = getKey();
//看看k
printf("key: 0x%x\n",k);
//创建共享内存
int shmid = createShm(k);
printf("shmid: %d\n",shmid);
//关联共享内存
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
const char* message = "hello server,我是clinet正在和你通信";
pid_t id = getpid();
int cnt = 1;
while(true)
{
sleep(5);
snprintf(start,MAX_SIZE,"%s[pid:%d][信息标号:%d]",message,id,cnt);
}
detachShm(start);
return 0;
}
这里我们让client和server进行通信,server负责接受就可以了
#include"comm.hpp"
#include
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = getShm(k);
printf("shmid: %d\n", shmid);
//关联共享内存
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
const char* message = "hello server,我是clinet正在和你通信";
pid_t id = getpid();
int cnt = 1;
while(true)
{
sleep(5);
snprintf(start,MAX_SIZE,"%s[pid:%d][信息标号:%d]",message,id,cnt);
}
//去关联
detachShm(start);
return 0;
}
运行shm_server
运行shm_client
这是为什么呢?
我们通过ipcs -m命令查看一下:
ipcs -m
ipcs -m
是一个Unix/Linux系统中的命令,用于列出当前系统中的共享内存段信息。它可以显示已经创建的共享内存段的详细信息,包括标识符、权限、大小、进程ID等。
key
:共享内存段的键值。shmid
:共享内存段的标识符。owner
:创建该共享内存段的用户ID。perms
:共享内存段的权限。bytes
:共享内存段的大小(字节数)。nattch
:连接到该共享内存段的进程数量。status
字段表示共享内存段的状态
其中
status
值及其含义如下:
0
:表示共享内存段当前未被使用或已被释放。dest
:表示共享内存段标记为准备删除状态。这意味着共享内存段即将被销毁,但仍然有进程连接到它,只有当所有连接到该共享内存段的进程都脱离连接时,它才会被完全删除。out
:表示共享内存段处于被卸载状态。这意味着该共享内存段已经被脱离连接,但仍然存在于系统中。可以通过手动操作或进程退出来释放该共享内存段。err
:表示共享内存段状态异常,可能由于系统错误或其他问题导致无法正常访问和管理。
这说明了共享内存生命周期是随操作系统的,不是随进程。用就是说如果我们没有主动调用shmcl函数去控制删除共享内存空间,那么我们后面就要自去删除一下就可以在次运行了。
ipcrm -m shmid