前言:本期介绍共享内存。
在之前学过的进程地址空间的基础上,我们知道,进程之间具有独立性,因为每个进程的内核数据结构的数据以及页表的映射都是独立的。而对于共享内存,我们同样了解,这是为了让进程之间能够进行通信的公共空间,接下来就通过进程地址空间的结构去了解共享空间的位置及原理:
OS为了让两个毫不相关的进程之间进行通信,进行了三个工作:
因此,我们把申请的这块空间称之为共享内存,将映射关系称之为进程和共享内存进行挂接。将取消进程和内存的映射关系称之为去关联,释放内存释放的就是共享内存。
理解:
通过让不同的进程,看到同一个内存块的方式,叫做共享内存。
#include
#include
int shmget(key_t key, size_t size, int shmflg);// size:共享内存的大小
对于shmflg
,常见的有两种选择:
IPC_CREAT
:如果不存在共享文件则创建,存在则获取IPC_EXCL
:
IPC_CREAT
IPC_CREAT|IPC_EXCL
:如果不存在,就创建,如果已存在,就出错返回。即在用户的角度,如果创建成功,一定是一个新的shm
!shmget返回值: 记住他是一个标识符就够用了,得到的是共享内存的标识符。(和文件fd没有任何关系)
key: 是什么不重要,最重要的是其具备的唯一性。
而获取key值,则通过一个新的接口:ftok
,ftok
通过指定的字符串数据*pathname
以及char类型的proj_id
数据进行一系列的算法整合返回了具有唯一性的Key:
key_t ftok(char *pathname, char proj_id);
由于创建的key值有可能已经被别人使用了,因此有失败的可能性。创建Key值如果失败,则返回-1。
# Makefile
.PHONY:all
all:shm_client shm_server
shm_client:shm_client.cc
g++ -o $@ $^ -std=c++11
shm_server:shm_server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f shm_client shm_server
// comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include
#include
#include
#include
#include
#include
#include
#define PATHNAME "."//当前路径
#define PROJ_ID 0x66
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if(k < 0)
{
// cin, cout, cerr ->stdin, stdout, stderr->0, 1, 2;标准错误stderr向2打印。
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);//终止进程
}
return k;
}
#endif
// shm_server.cc
#include"comm.hpp"
int main()
{
key_t k = getKey();
printf("0x%x\n", k);
return 0;
}
// shm_client.cc
#include"comm.hpp"
int main()
{
key_t k = getKey();
printf("0x%x\n", k);
return 0;
}
[hins@VM-12-13-centos shm]$ ./shm_server
0x6601062a
[hins@VM-12-13-centos shm]$ ./shm_client
0x6601062a
通过make后执行发现,两个程序的k值是一样的,这就证明了ftok指定参数的返回值是唯一的。(k实际上就是32位的一个整数)
OS中一定存在多个共享内存,因为彼此之间可能都需要通信,因此也就都需要申请一块空间。而OS申请的共享空间,也一定和进程一样需要被管理,既然需要管理,那么一定也是先描述再组织的方式,即共享内存 = 物理内存块+共享内存的相关属性 。
之前谈到过,key是什么不重要,能进行唯一性的标识最重要,因此创建共享内存的时候,是如何保证共享内存在系统中是唯一的呢?当然是通过key来确定的,只要一个进程也看到了同一个key,就能够访问这个共享内存。那么key在哪里,实际上这就和PCB一样,key就在内核中的属性集合里,即:
struct shm{
key_t key;
//...
}
即:key是通过shmget这样的系统调用,设置进入共享内存属性中,用来表示该共享内存在内核中的唯一性!
shmid和key就好比fd和inode。为什么有了key还需要shmid呢?通过key和shmid的区分,能够面向系统层面和用户层面,这样能够更好的进行解耦,以免内核中的变化影响到用户级。
通过让不同的进程,看到同一个内存块的方式,叫做共享内存。
查看共享内存:ipcs -m/-q/-s
(共享内存/消息队列/信号量数组)
删除共享内存:ipcrm -m shmnid
将共享内存与虚拟内存进行关联:
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);// 共享内存id,被映射的进程地址空间(给nullptr),给0默认可以读写。成功时,将返回共享内存的虚拟地址。失败返回-1(失败错误码errno被设置)
将共享内存与虚拟内存去关联:
#include
#include
int shmdt(const void *shmaddr);//参数:shmat的返回值。成功时,将返回0。失败返回-1(失败错误码errno被设置)
控制(主要用移除)共享内存(shmctl):
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//shmid(类似fd),传入系统设定的宏,shmid_ds数据结构。传入IPC_RMID移除共享内存成功时,将返回0。失败返回-1(失败错误码errno被设置)
// comm.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PATHNAME "."//当前路径(路径都行)
#define PROJ_ID 0X55//项目id也无要求
#define MAX_SIZE 4096
key_t getKey()
{
key_t k=ftok(PATHNAME, PROJ_ID);
if(k==-1)
{
std::cout<<"ftok"<<errno<<":"<<strerror(errno)<<std::endl;
exit(1);
}
return k;
}
int getShmHelper(key_t key,int flags)
{
int shmid=shmget(key,MAX_SIZE,flags);
if(shmid==-1)//创建共享内存失败
{
std::cerr<<"shmget"<<errno<<":"<<strerror(errno)<<std::endl;
exit(2);
}
return shmid;//返回共享内存标识符
}
int getShm(key_t key)//创建||获取共享内存
{
return getShmHelper(key,IPC_CREAT);//传0也行
}
int createShm(key_t key)//必定创建共享内存
{
return getShmHelper(key,IPC_CREAT|IPC_EXCL|0600);//生成一个全新的共享内存
}
void* attachShm(int shmid)//让共享内存与虚拟内存建立联系
{
void* memstart=shmat(shmid,nullptr,0);
if((long long)memstart==-1L)
{
std::cerr<<"shmat"<<errno<<":"<<strerror<<std::endl;
exit(3);
}
return memstart;
}
void detchShm(void* memStart)//去关联
{
if(shmdt(memStart)==-1)
{
std::cerr<<"shmdt"<<errno<<":"<<strerror<<std::endl;
exit(4);
}
}
void delShm(int shmid)//删除共享内存
{
if(shmctl(shmid,IPC_RMID,nullptr)==-1)
{
std::cerr<<"shmctl"<<errno<<":"<<strerror<<std::endl;
}
}
// shm_server.cc
#include "comm.hpp"
int main()
{
key_t k=getKey();
printf("0X%x\n",k);
int shmid=createShm(k);
char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系
printf("memStart address:%p\n",memStart);
//通信接收代码
while(true)
{
printf("client say:%s\n",memStart);
sleep(1);
//调用用户级结构体
struct shmid_ds ds;//创建结构体对象ds
shmctl(shmid,IPC_STAT,&ds);//获取ds对象的状态
printf("获取属性:%d,pid:%d,myself:%d,key:%d\n",ds.shm_segsz,getpid(),ds.shm_cpid,ds.shm_perm.__key);
}
detchShm(memStart);//去关联
sleep(10);
delShm(shmid);//删除共享内存,client和server都能删除共享内存,尽量谁创建谁删
return 0;
}
// shm_client.cc
#include "comm.hpp"
int main()
{
key_t k=getKey();
printf("0X%x\n",k);
int shmid=getShm(k);//获取共享内存
sleep(5);
char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系
printf("memStart address:%p\n",memStart);
//通信传输代码
const char* massage="I am client";
pid_t id=getpid();
int cnt=0;//发送计数
while(true)
{
snprintf(memStart,MAX_SIZE,"%s[%d]:%d\n",massage,getpid,++cnt);
sleep(1);
}
detchShm(memStart);//去关联
return 0;
}
优点:共享内存是所有进程间通信中速度最快的。(无需缓冲区,能大大减少通信数据的拷贝次数)
缺点:如果服务端读取速度较快,用户端发送数据较慢,就会产生同一段消息被服务端读取多遍。共享内存是不进行同步和互斥的,没有对数据进行任何保护。
共享内存大小的建议:因为系统分配共享内存是以4KB为基本单位,一般建议申请共享内存的大小为4KB的整数倍。
OK,以上就是本期知识点“共享内存”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟~
如果有错误❌,欢迎批评指正呀~让我们一起相互进步
如果觉得收获满满,可以点点赞支持一下哟~
❗ 转载请注明出处
作者:HinsCoder
博客链接: 作者博客主页