共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据。
目录
一、共享内存的原理
二、使用共享内存
三、共享内存函数
1.shmget(用来创建共享内存)
2.shmat(将共享内存和进程地址空间关联)
3.shmctl(用于控制共享内存)
4.shmdt(将共享内存段与当前进程脱离)
四、共享内存server&client通信测试
①创建共享内存
②关联共享内存和进程
//2-3 进行通信
③取消关联进程和共享内存
④关闭共享内存
一、共享内存的原理
- 1.在物理内存中开辟一块空间
- 2.让不同的进程通过页表将该空间映射到自己的进程虚拟地址空间中
- 3.不同进程通过操作自己进程虚拟空间中的虚拟地址,来操作共享内存。
进程A和B都通过各自的页表将自己的虚拟地址和物理地址进行对应,进程A操作虚拟地址写入数据,保存在物理内存,进程B读取该物理内存。
二、使用共享内存
共享内存在物理地址空间上,是在共享区中。
- 创建共享内存
- 关联进程--将进程的虚拟地址和共享内存的地址通过页表建立映射关系
- 通信
- 取消关联进程--通过将进程中页表的key-value删除
- 共享内存释放
三、共享内存函数
上图表示进程A和进程B可以通过共享内存来通信,同样C和D也可以通过另一块共享内存通信。所以在系统中,一定同时存在多个共享内存,os需要对这些内存块做管理,所以使用一个结构体,里面存放共享内存的各个属性。
所以 共享内存 == 共享内存的内核数据结构 + 真正开辟的物理空间
1.shmget(用来创建共享内存)
原型:int shmget(key_t key,size_t size,int shmflg)
参数:key 这个共享内存段名字 size 共享内存大小 shmflg 由九个权限标志构成,用法的mode一样
返回值:成功返回一个非负整数,即共享内存段的标识码,失败返回-1
其中key_t key 是由ftok算法生成的随机值,具有唯一性,ftok函数如下:
key_t ftok(const char * pathname,int proj_id)
形参:pathname 传入一个地址 proj_id 输入一个数字 这两个值可以任意设置,但是在通信双方必须是相同的,具体地,A进程调用ftok生成key,将key放入struct shm中,B也生成相同的key,B用这个key去struct shm去匹配,匹配成功,就找到了共享内存。
shmflg宏 | 含义 |
IPC_CREAT | 单独使用时,创建一个共享内存,如果不存在就直接创建,如果存在就获取已有的共享内存起始地址返回 |
IPC_EXCL | 需要依赖IPC_CREAT使用,如果不存在则创建共享内存,如果存在立马退出报错返回 |
2.shmat(将共享内存和进程地址空间关联)
原型:void * shmat(int shmid,const void * shmaddr,int shmflg);
参数: shmid 共享内存标识 shmaddr 指定连接的地址 shmflg它的两个可能取值是SHM_RND 和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存的起始地址,失败返回-1(类似于malloc)
说明:shmaddr为null,os自动选择一个地址,
shmaddr不为null,且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为null,且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整 shmlba的整数倍
shmflg = SHM_RDONLY 表示连接操作用来只读共享内存
3.shmctl(用于控制共享内存)
原型: int shmctl(int shmid,int cmd,struct shmid_ds * buf);
参数:shmid由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可能取值如下表)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1
命令 | 说明 |
IPC_STAT | 将shmid_ds结构中的数据设置为共享内存的当前关联值 |
IPC_SET | 在进程有足够权限的前提下,将共享内存的当前关联值设置为shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |
4.shmdt(将共享内存段与当前进程脱离)
原型:int shmdt(const void * shmaddr);
参数:shmaddr:由shmat所返回的指针
成功返回0,失败返回-1
注:将共享内存与当前进程脱离不等于删除共享内存段
四、共享内存server&client通信测试
使用共享内存通信的步骤:创建共享内存,关联进程和共享内存,进行通信,取消关联,删除共享内存
创建4个文件:server.cc,client.cc,common.hpp,makefile。server实现写入,client实现读取
makefile要生成两个目标文件server,client,可以这样写:
.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
common.hpp中声明和定义一些客户端和服务端公有的函数,如下:
这个文件中主要实现创建共享内存,关联进程和取消关联,共享内存释放。
①创建共享内存
- 首先,创建共享内存,使用shmget
- 其中有一个key,使用ftok生成,在两端调用即可\
- 通信的时候,一个进程创建共享内存,一个进程获取.
- 共享内存创建时,是按字节为单位。
//创建共享内存,要知道k和这个共享内存的大小 int createShm(key_t k, int size) { //创建的时候,最好创建全新的 int shmid = shmget(k,gsize,IPC_CREAT|IPC_EXCL); if(shmid == -1) { //创建失败 exit(2); } return shmid; } //创建成功,一个进程获取 int getShm(key_t,int size) { int shmid = shmget(k,gsize,IPC_CREAT); return shmid; }
从上述两个函数,一个创建一个获取,只是shmflg不同,所以可以封装为一个函数
//static 只在本文件内有效 static int createShmHelper(int k, int size,int flag) { int shmid = shmget(k,gsize,flag); if(shmid == -1) { exit(2); } return shmid; } int createShm(key_t k,int size) { //创建的时候注意加权限 umask(0); return createShmHelper(key,gsize,IPC_CREAT|IPC_EXCL|0666); } int getShm(key_t k,int size) { return createShmHelper(key,gsize,IPC_CREAT); }
②关联共享内存和进程
在物理内存中创建好共享内存后,这个共享内存在创建的时候必须有权限才能进行操作。关联共享内存和进程,将共享内存的起始地址经过页表映射放到进程pcb中,具体挂接到pcb的哪里可以自己设定,设置为null让系统自主选择,即完成了关联。
char * attachShm(int shmid) { char * start = (char *)shmat(shmid,nullptr,0); return start; }
//2-3 进行通信
具体地通信可以自己设置
③取消关联进程和共享内存
detach(char * start) { int n = shmdt(start); (void)n; }
④删除共享内存
当运行两个端,发现进程退出后,再次运行无法创建共享内存,说明共享内存没有直接随着进程关闭。
关闭共享内存可以用两种方法
- ipcrm - m命令
- 函数shmctl
void delShm(int shmid) { int n = shmctl(shmid,IPC_RMID,nullptr); assert(n != -1); (void)n; }
server.cc中主要实现创建共享内存,关联共享内存,进行通信(从共享内存读数据),取消关联,删除共享内存。
client.cc中主要实现获取共享内存,关联共享内存,进行通信(写数据到共享内存),取消关联。
接下来,优雅的修改上面的代码,封装起来。
common.hpp:
//前面的方法不变
#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;
};
server.cc
int main()
{
Init init(SERVER);
char * start = init.getStart();
//开始通信
....
//读取
int n = 0;
while(n <= 26)
{
cout<<" "<<start<<endl; //设置start里都是字符串
sleep(1);
}
//因为init是一个临时对象,所以函数跑完会自动调用析构
return 0;
}
client.cc
int main()
{
Init init(CLIENT);
char * start = init.getStart();
//开始通信
...
//往start里写
char c = 'A';
while(c <= 'Z')
{
start[c-'A'] = c;
c++;
start[c] = '\0';
sleep(1);
}
return 0;
}