目录
system V共享内存
共享内存的原理
共享内存的建立与释放
共享内存的创建
shmget
共享内存的释放
shmctl
共享内存的关联
shmat
共享内存的去关联
shmdt
用共享内存实现serve和client的简单通信
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存。
一般通过共享内存通信分为三个步骤
1.创建共享内存
2.关联进程和取消关联
3.释放贡献内存
注意:
这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成。
共享内存的建立大致包括以下两个过程:
- 在物理内存当中申请共享内存空间。
- 将申请到的共享内存挂接到地址空间,即建立映射关系。
共享内存的释放大致包括以下两个过程:
- 将共享内存与地址空间去关联,即取消映射关系。
- 释放共享内存空间,即将物理内存归还给系统。
创建共享内存我们需要用shmget函数,shmget函数的函数原型如下:
int shmget(key_t key, size_t size, int shmflg);
shmget函数的参数说明:
第一个参数key,表示待创建共享内存在系统当中的唯一标识。
key值一般不会随便设置(这个值独一无二的),一般用ftok()函数来获取key值
key_t ftok(const *pathname,int proj_id);
第一个参数是路径字符串
第二个参数是项目ID
第二个参数size,表示待创建共享内存的大小。
第三个参数shmflg,表示创建共享内存的方式。
第三个参数常用这个函数自带的宏选项,下面是两个常用选项IPC_CREAT和IPC_EXCL
shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)。
shmget调用失败,返回-1。
系统中有大量的共享内存同时存在,最终操作系统要管理所有的共享内存,这里就要用一个及结构体对象将共享内存描述起来管理struct shm(假设), 既共享内存=共享内存的内核数据结构+真正开辟的空间
key值设置测试
创建共享内存
server端,client同理
查看共享内存
在Linux当中,我们可以使用ipcs
命令查看有关进程间通信设施的信息。
单独使用
ipcs
命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:
- -q:列出消息队列相关信息。
- -m:列出共享内存相关信息。
- -s:列出信号量相关信息。
这里的共享内存 ,并不会随着进程的退出而释放,其生命周期是随操作系统的,一般要用指令或者系统接口释放,如上面的server端运行一次后共享内存依然存在,在下次运行就会出现报错,这就是因为没有释放共享内存
指令删除
我们可以使用
ipcrm -m shmid
命令释放指定id的共享内存资源。
接口删除
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl函数的参数说明:
第一个参数shmid,表示所控制共享内存的用户级标识符。
第二个参数cmd,表示具体的控制动作。
第三个参数buf,用于获取或设置所控制共享内存的数据结构。
shmctl函数的返回值说明:shmctl调用成功,返回0。
shmctl调用失败,返回-1。
其中,作为shmctl函数的第二个参数传入的常用的选项有以下三个
IPC_STAT 获取共享内存的当前关联值,此时参数buf作为输出型参数
注意:这里的获取共享内存当前的关联值需要权限,要将权限设置一下
IPC_SET 在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值
IPC_RMID 删除共享内存段
虽然进程进程创建了共享内存,但是并不意味着进程能够使用这个共享内存,所以这里需要将进程和共享内存关联起来
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat函数的参数说明:
第一个参数shmid,表示待关联共享内存的用户级标识符。
第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。
第三个参数shmflg,表示关联共享内存时设置的某些属性。
shmat函数的返回值说明:shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。
shmat调用失败,返回(void*)-1。
注意:创建内存时候要设置权限,否则创建出来的共享内存的默认权限为0,就没有权限关联 ,也就是nattch怎么运行都是0
同时运行server和client
int shmdt(const void *shmaddr);
shmdt函数的参数说明:
- 待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址。
shmdt函数的返回值说明:
- shmdt调用成功,返回0。
- shmdt调用失败,返回-1。
client
server
comm.hpp
#ifndef __COMM_HPP__
#define __COMM_HPP__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
// IPC_CREAT and IPC_EXCL
// 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回
// IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的!
#define PATHNAME "."
#define PROJID 0x6666
const int gsize = 4096; //暂时
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJID);
if(k == -1)
{
cerr << "error: " << errno << " : " << strerror(errno) << endl;
exit(1);
}
return k;
}
string toHex(int x)
{
char buffer[64];
snprintf(buffer, sizeof buffer, "0x%x", x);
return buffer;
}
static int createShmHelper(key_t k, int size, int flag)
{
int shmid = shmget(k, gsize, flag);
if(shmid == -1)
{
cerr << "error: " << errno << " : " << strerror(errno) << endl;
exit(2);
}
return shmid;
}
int createShm(key_t k, int size)
{
umask(0);
return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}
int getShm(key_t k, int size)
{
return createShmHelper(k, size, IPC_CREAT);
}
char* attachShm(int shmid)
{
char *start = (char*)shmat(shmid, nullptr, 0);
return start;
}
void detachShm(char *start)
{
int n = shmdt(start);
assert(n != -1);
(void)n;
}
void delShm(int shmid)
{
int n = shmctl(shmid, IPC_RMID, nullptr);
assert(n != -1);
(void)n;
}
#define SERVER 1
#define CLIENT 0
class Init
{
public:
Init(int t):type(t)
{
key_t k = getKey();
if(type == SERVER) shmid = createShm(k, gsize);
else shmid = getShm(k, gsize);
start = attachShm(shmid);
}
char *getStart(){ return start; }
~Init()
{
detachShm(start);
if(type == SERVER) delShm(shmid);
}
private:
char *start;
int type; //server or client
int shmid;
};
#endif
client.cc
#include "comm.hpp"
#include
int main()
{
Init init(CLIENT);
char *start = init.getStart();
char c = 'A';
while(c <= 'Z')
{
start[c - 'A'] = c;
c++;
start[c - 'A'] = '\0';
sleep(1);
}
return 0;
}
server.cc
#include "comm.hpp"
#include
int main()
{
Init init(SERVER);
char *start = init.getStart();
int n = 0;
while(n <= 30)
{
cout <<"client -> server# "<< start << endl;
sleep(1);
n++;
}
return 0;
}