进程通信 system V共享内存 ─── linux第25课
system V共享内存
我们原来学习的进程间通信方法(管道 ,命名管道)是基于文件系统的 ,而共享内存与文件系统无关
共享内存运用进程的地址空间的共享区(类似可以改变的共享库(动态库))地址空间的共享库不仅可以映射动态库,还可以映射共享内存
当两个内存同时映射了同一个共享内存,即可让不同进程,看到同一块资源 .
共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递 不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
注意:共享内存没有任何保护机制
共享内存示意图
- 现在物理内存开辟空间形成共享内存
- 将共享内存挂载到两个进程的地址空间
OS管理共享内存
OS中会有众多共享内存,要管理--->先描述,再组织
共享内存 =共享内存的内核数据结构+内存块
共享内存的数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment
(bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current
attaches */
unsigned short shm_unused; /* compatibility */
void shm_unused2; / ditto - used by
DIPC */
void shm_unused3; / unused */
};
共享内存的使用流程
程序A创建共享内存得到shmid ,程序B获取共享内存得到相同的shmid , 两个程序通过shmid都将共享内存挂载到自己的地址空间 ,两程序就能利用共享内存通信 ,通信完成 ,两程序都将去关联共享内存, 最后删除共享内存
共享内存的创建和控制
1.创建共享内存/获取共享内存
shmget函数
功能:⽤来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存⼤⼩
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
获取共享内存 -->取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。
创建共享内存--->取值为IPC_CREAT | IPC_EXCL | mode:共享内存不存在,创建并返回;共享内存已存在,出错返回。
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
key: 创共享内存时 ,唯一的key, 可以确保产生的共享内存唯一 ,如果传入的key 已经被其他进程使用了(已经产生了共享内存) 函数会创建共享内存失败返回 -1 ,需要再次传入唯一没用过的key
key生成器: ftok() 函数使用公共路路径+公共项目ID 使key的冲突率大大降低
ftok()失败返回-1
使用key的原因: key是被需要通信的两个进程都知道的 ,利用key可以使得一个进程构建共享内存,另一个进程可以获取同一个共享内存 .进程具有独立性 (进程一构建好共享内存,不能将shmid给进程2 ,因为我们就是要实现进程通信 如果可以通信shmid ,我们没必要用共享内存通信, 进程二只能通过共知的key通过调用shmget来获取shmid ,不是创建,所以shmget才有两种模式)
shmget成功的返回值是shmid
ipcs -m指令可以看到已经建成的共享内存的shmid
总结:
两个程序利用key 和 shmget 分别创建和获取共享内存, 同时拿到了shmid
key仅仅只用于传给OS,创建共享内存
shmid用于用户挂载 ,删除,查找,改共享内存
挂载共享内存
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存的起始地址(如果指定了地址,返回指定地址);失败返回-1
说明:
- shmaddr为NULL,核⼼⾃动选择⼀个地址
- 先将shmlg设置为0 ,知识受限以后再解决
- shmaddr不为NULL且shmflg⽆SHM_RND标记,则以shmaddr为连接地址。
- shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会⾃动向下调整为SHMLBA的整数倍。 公式:shmaddr - (shmaddr % SHMLBA)
- shmflg = SHM_RDONLY,表⽰连接操作⽤来只读共享内存
去关联共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段 ,仅仅只是将nattch由1变为0
删除共享内存
1.命令行删除
ipcrm -m "shmid" //命令行操作将shmid替换即可
ipcrm -m 用于命令行中删除 System V 共享内存段
2, 代码执行删除
shmctl函数
shmctl(shmid, IPC_RMID, nullptr);
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
共享内存的特点:
- 共享内存通信速度最快只有两次拷贝 ,管道有4 次
- 让两个进程在各自的地址空间共享内存块, 但是 ,没有任何保护机制 !
- 共享内存的保护机制需要用户自己设计(控制读写的时机, 因为有可能客户端还未写完 ,服务器端就读取了不完整信息 )
- 例如使用用两个管道来传递一个int,控制两端的读写时机(此时管道传递数据小 ,共享内存传递信息大)
补充:
- 公共资源也叫做临界资源 ,需要被保护
- 在代码中访问公共资源的代码 ,是临界区,需要加锁
- 其他区域是非临界区,不需要加锁
下面的使用就没有加锁 ,用户也没有用管道来控制读写时机,下一个博客将使用管道来控制
共享内存的使用
ShareMemory.hpp
#pragma once
#include<iostream>
#include<string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<unistd.h>
const std::string gPath="/home/bmh/lesson26";
int gProjectId = 0X6666;
int gSize = 4096;
mode_t gMode =0600;
std::string KeyToStr(key_t k)
{
char buf[64];
snprintf(buf, sizeof(buf), "0x%x", k);
return buf;
}
class Sharem
{
public:
Sharem():_shmid(-1),_address(nullptr),_key(-1)
{
}
void CreatShm()
{
//1.创建key
_key =::ftok(gPath.c_str() ,gProjectId);
if(_key == -1)
{
std::cerr<<"ftok error"<<std::endl;
}
std::cout<<"key :"<<_key <<std::endl;
//2.创建共享内存
_shmid =::shmget(_key , gSize, IPC_CREAT|IPC_EXCL|gMode);
if(_shmid == -1)
{
std::cerr<<"shmget error"<<std::endl;
}
std::cout<<"shmid :"<<_shmid <<std::endl;
}
void GetShm()
{
//1.创建key
_key =::ftok(gPath.c_str() ,gProjectId);
if(_key == -1)
{
std::cerr<<"ftok error"<<std::endl;
}
std::cout<<"key :"<<_key <<std::endl;
_shmid =::shmget(_key , gSize, IPC_CREAT);
if(_shmid == -1)
{
std::cerr<<"shmget error"<<std::endl;
}
}
void AttachShm()
{
_address =::shmat(_shmid ,nullptr ,0);
if((long long)_address == -1)//linux是64位地址 ,转化成数字比较
{
std::cerr<<"shmat error"<<std::endl;
}
}
//去关联
void DetachShm()
{
if(_address != nullptr)
{
int deRet=::shmdt(_address);
if(deRet == -1)
{
std::cerr<<"shmdt error"<<std::endl;
}
}
std::cout<<"Detach done"<<std::endl;
}
void* GetAddress()
{
return _address;
}
void DeleteShm()
{
// 3. 删除共享内存
shmctl(_shmid, IPC_RMID, nullptr);
}
~Sharem()
{
}
private:
key_t _key;
int _shmid;
void* _address;
};
Sharem shm;
server.cc
#include"ShareMemory.hpp"
int main()
{
shm.CreatShm();
shm.AttachShm();
//使用共享内存
//server的shm._address与client的shm._address可能不一样
char* shmaddress= (char*)shm.GetAddress();
while(true)
{
printf("%s\n",shmaddress);
sleep(1);
}
shm.DetachShm();
shm.DeleteShm();
return 0;
}
client.c
#include"ShareMemory.hpp"
#include<string.h>
int main()
{
shm.GetShm();
shm.AttachShm();
//使用共享内存
char* shmaddress= (char*)shm.GetAddress();
char ch = 'A';
while(ch <= 'Z')
{
sleep(2);
shmaddress[ch -'A']=ch;
ch++;
}
shm.DetachShm();
return 0;
}
makefile
CLIENT=client
SERVER=server
CC=g++
CLIENT_SRC=client.cc
SERVER_SRC=server.cc
.PHONY:all
all:$(CLIENT) $(SERVER)
$(CLIENT):$(CLIENT_SRC)
$(CC) -o $@ $^ -std=c++11
$(SERVER):$(SERVER_SRC)
$(CC) -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f $(SERVER) $(CLIENT)