system V共享内存
共享内存区是最快的IPC
形式。
一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,
换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
共享内存原理示意图
共享内存数据结构
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 */
};
共享内存函数
shmget
函数
功能:用来创建共享内存(只需要创建一次)
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字(保证不同的进程看到同一份共享内存)
size:共享内存大小(单位字节)
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
IPC_CREAT(单独使用):如果申请的共享内存不存在,就创建,存在,就获取并返回
IPC_CREAT|IPC_EXCL:如果申请的共享内存不存在,就创建,存在,就出错并返回
(确保,如果申请成功了一个共享内存,那么这个共享内存一定是新的!)
IPC_EXCL:不单独使用!
返回值(共享内存标识符):成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
谈谈key:
- key是一个数字,这个数字是几不重要,
关键在于它在内核中必须具有唯一性,能够让不同进程进行唯一性标识。
- 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key
就可以和第一个进程看到同一个共享内存了!
-
对于一个已经创建好的共享内存,key在共享内存的描述对象中!
-
第一次创建的时候,必须有一个key了。怎么有key?
ftok
是一套算法,pathname
和proj_id
进行了数值的计算!pathname
和proj_id
由用户自由指定!如果与操作系统内的
key
冲突了,就要修改pathname
或proj_id
。key
为什么不让操作系统直接生成?因为操作系统不清楚哪个进程之间要通信!用户才清楚哪些进程要通信,所以由用户约定,用同一个
pathname
和proj_id
生成用一个key
! -
key - 类似 - 路径 都具有唯一性。
key
和 shmid
都具有唯一性。
key
操作系统内标定唯一性。
shmid
只在进程内,用来表示资源的唯一性。
将进程挂接到共享内存
shmat
函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节(虚拟起始地址);失败返回-1
说明:
shmaddr为NULL,核心自动选择一个地址
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
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl
函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
实例代码
测试代码
comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include"log.hpp"
Log log;
using namespace std;
const int size=4096;
//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;
key_t Getkey()
{
key_t k=ftok(pathname.c_str(),proj_id);
if(k<0)
{
log(Fatal,"ftok error : %s",strerror(errno));
exit(1);
}
log(Info,"ftok sucess,key is %d",k);
return k;
}
int GetshmMen()
{
int k=Getkey();
int shmid=shmget(k,size,IPC_CREAT|IPC_EXCL);
if(shmid<0)
{
log(Fatal,"create share memory error: %s",strerror(errno));
exit(2);
}
log(Info,"create share memory success , shmid : %d",shmid);
return shmid;
}
processa.cc
#include"comm.hpp"
#include"log.hpp"
int main()
{
int shmid=GetshmMen();
sleep(20);
log(Debug,"processa quit..");
return 0;
}
结果演示
查看共享内存的命令:ipcs -m
共享内存的生命周期是随内核的!
用户不主动关闭,共享内存会一直存在。
除非内核重启(用户释放)
用户没有主动关闭算内存泄漏!
关闭释放共享内存的命令:ipcrm -m 该共享内存的shmid
(不是用 key
)
用户层(应用层)统一用 shmid
!key
只给操作系统使用
最终代码:
makefile
.PHONY:all
all:processa processb
processa:processa.cc
g++ -o $@ $^ -std=c++11
processb:processb.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -rf processa processb
comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include"log.hpp"
Log log;
using namespace std;
//共享内存的大小一般建议是4096的整数倍
//设置为4097 实际上操作系统分配了4096*2的大小给共享内存
//虽然操作系统分配了4096*2 但是你设置了4097也只能用4097
const int size=4096;
//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;
key_t Getkey()
{
key_t k=ftok(pathname.c_str(),proj_id);
if(k<0)
{
log(Fatal,"ftok error : %s",strerror(errno));
exit(1);
}
log(Info,"ftok sucess,key is 0x%x",k);
return k;
}
int GetshmMenHelper(int flag)
{
int k=Getkey();
int shmid=shmget(k,size,flag);
if(shmid<0)
{
log(Fatal,"create share memory error: %s",strerror(errno));
exit(2);
}
log(Info,"create share memory success , shmid : %d",shmid);
return shmid;
}
int Createshm()
{
return GetshmMenHelper(IPC_CREAT|IPC_EXCL|0666);
}
int Getshm()
{
return GetshmMenHelper(IPC_CREAT);
}
processa.cc
#include"comm.hpp"
#include"log.hpp"
int main()
{
//创建共享内存
int shmid=Createshm();
// log(Debug,"create share memory done");
// sleep(5);
//挂接
char*shmaddr=(char*)shmat(shmid,nullptr,0);
// log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);
//ipc code
//一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
//不需要经过系统调用,直接就能看到数据了!
while(1)
{
cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
sleep(1);
}
// sleep(5);
//去关联
shmdt(shmaddr);
// log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
//释放共享内存
shmctl(shmid,IPC_RMID,nullptr);
// log(Debug,"destory share memory done,shmid: %d",shmid);
// log(Debug,"processa quit..");
return 0;
}
processb.cc
#include"comm.hpp"
int main()
{
//一旦有了共享内存,挂接到自己的地址空间中,你直接把它当成你的内存空间来用即可!
//不需要调用系统调用
//获取shmid
int shmid=Getshm();
// log(Debug,"get share memory done");
//挂接
char*shmaddr=(char*)shmat(shmid,nullptr,0);
// log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);
//ipc code
while(1)
{
cout<<"please enter:"<<endl;
fgets(shmaddr,size,stdin);
}
//去关联
shmdt(shmaddr);
// log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
return 0;
}
运行结果:
bytes
和 nattch
有时间可以研究一下
注意:共享内存没有进行同步与互斥!
共享内存的特性+扩展代码
-
共享内存没有同步互斥之类的保护机制!
-
共享内存是所有进程间通信中,速度最快的! -> 拷贝少!
管道至少拷贝两次!共享内存直接使用,不用拷贝!
-
共享内存内部的数据由用户自己维护!
看看共享内存的属性
processa.cc
#include"comm.hpp"
#include"log.hpp"
int main()
{
//创建共享内存
int shmid=Createshm();
// log(Debug,"create share memory done");
// sleep(5);
//挂接
char*shmaddr=(char*)shmat(shmid,nullptr,0);
// log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);
//ipc code
//一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
//不需要经过系统调用,直接就能看到数据了!
struct shmid_ds shmds;
while(1)
{
cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
sleep(1);
shmctl(shmid,IPC_STAT,&shmds);
cout<<"shm size: "<<shmds.shm_segsz<<endl;
cout<<"shm nattch: "<<shmds.shm_nattch<<endl;
printf("shm key: 0x%x\n",shmds.shm_perm.__key);
cout<<"shm mode: "<<shmds.shm_perm.mode<<endl;
cout<<"--------------------------------"<<endl;
}
// sleep(5);
//去关联
shmdt(shmaddr);
// log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
//释放共享内存
shmctl(shmid,IPC_RMID,nullptr);
// log(Debug,"destory share memory done,shmid: %d",shmid);
// log(Debug,"processa quit..");
return 0;
}
同步机制(结合命名管道)
comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include<sys/stat.h>
#include"log.hpp"
Log log;
using namespace std;
//共享内存的大小一般建议是4096的整数倍
//设置为4097 实际上操作系统分配了4096*2的大小给共享内存
//虽然操作系统分配了4096*2 但是你设置了4097也只能用4097
const int size=4096;
//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR=1,
FIFO_OPEN_ERR,
FIFO_DELETE_ERR
};
class Init
{
public:
Init()
{
//创建管道
int n=mkfifo(FIFO_FILE,MODE);
if(n==-1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
//删除管道
int m=unlink(FIFO_FILE);
if(m==-1)
{
perror("unlink");
exit(FIFO_DELETE_ERR);
}
}
};
key_t Getkey()
{
key_t k=ftok(pathname.c_str(),proj_id);
if(k<0)
{
log(Fatal,"ftok error : %s",strerror(errno));
exit(1);
}
log(Info,"ftok sucess,key is 0x%x",k);
return k;
}
int GetshmMenHelper(int flag)
{
int k=Getkey();
int shmid=shmget(k,size,flag);
if(shmid<0)
{
log(Fatal,"create share memory error: %s",strerror(errno));
exit(2);
}
log(Info,"create share memory success , shmid : %d",shmid);
return shmid;
}
int Createshm()
{
return GetshmMenHelper(IPC_CREAT|IPC_EXCL|0666);
}
int Getshm()
{
return GetshmMenHelper(IPC_CREAT);
}
processa.cc
#include"comm.hpp"
#include"log.hpp"
int main()
{
Init init;
//创建共享内存
int shmid=Createshm();
// log(Debug,"create share memory done");
// sleep(5);
//挂接
char*shmaddr=(char*)shmat(shmid,nullptr,0);
// log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);
int fd=open(FIFO_FILE,O_RDONLY);
if(fd<0)
{
log(Fatal,"error string: %s , error code: %d\n",strerror(errno),errno);
exit(FIFO_OPEN_ERR);
}
//ipc code
//一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
//不需要经过系统调用,直接就能看到数据了!
struct shmid_ds shmds;
while(1)
{
char c;
ssize_t s=read(fd,&c,1);
if(s==0)break;
else if(s<0)break;
cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
sleep(1);
shmctl(shmid,IPC_STAT,&shmds);
cout<<"shm size: "<<shmds.shm_segsz<<endl;
cout<<"shm nattch: "<<shmds.shm_nattch<<endl;
printf("shm key: 0x%x\n",shmds.shm_perm.__key);
cout<<"shm mode: "<<shmds.shm_perm.mode<<endl;
cout<<"--------------------------------"<<endl;
}
// sleep(5);
//去关联
shmdt(shmaddr);
// log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
//释放共享内存
shmctl(shmid,IPC_RMID,nullptr);
// log(Debug,"destory share memory done,shmid: %d",shmid);
close(fd);
// log(Debug,"processa quit..");
return 0;
}
processb.cc
#include"comm.hpp"
int main()
{
//一旦有了共享内存,挂接到自己的地址空间中,你直接把它当成你的内存空间来用即可!
//不需要调用系统调用
//获取shmid
int shmid=Getshm();
// log(Debug,"get share memory done");
//挂接
char*shmaddr=(char*)shmat(shmid,nullptr,0);
// log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);
int fd=open(FIFO_FILE,O_WRONLY);
if(fd<0)
{
log(Fatal,"error string: %s , error code: %d\n",strerror(errno),errno);
exit(FIFO_OPEN_ERR);
}
//ipc code
while(1)
{
cout<<"please enter:"<<endl;
fgets(shmaddr,size,stdin);
write(fd,"c",1);//通知对方
}
// sleep(5);
//去关联
shmdt(shmaddr);
// log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
close(fd);
return 0;
}
效果: