共享内存

如上图所示,多个进程之间通过自己的页表,映射到物理内存的共享区,现在这些进程都可以对这个共享区域的数据进行操作,且一个进程操作了这块空间的数据,其他的进程也可以“看得到”。
和管道/消息队列比起来
- 共享内存的效率是比较高的
管道和消息队列在执行数据的读写时,需要将数据从用户区拷贝到内核区,再将数据从内核区拷贝至数据区。消耗比较大,所以效率比较低。
消息队列不需要进行这两次拷贝,不需要进行内核区和用户区的交互,效率较高,速度较快。
- 安全性比较低
没有提供任何的保护机制,多个进程操作共享内存,可能出现二义性的问题。
共享内存函数
shmget - 创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字 //获取方法同消息队列的key,利用ftok
size:共享内存⼤⼩ //size是一个向上取整至页的倍数的整数,例如页表大小4k,当前用户设置的size为 4097,那么系统会分配 2 * 4k的大小
shmflg:由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
shmat - 建立映射关系(将共享内存的段连接到虚拟地址空间)
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址 //一般都设置为 NULL,系统会自动分配一个地址返回
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
shmdt - 解除映射关系 (将共享内存段与当前进程脱离)
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl - 控制贡献个内存 (常用于删除共享内存)
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
//删除共享内存 IPC_RMID
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
//删除共享内存时设置为NULL
返回值:成功返回0;失败返回-1
代码演示
创建一块共享内存,客户端向共享内存中写数据,服务器将数据读出来,并输出至标准输出。
//com.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
int CreateShm(int size);
int GetShm(int size);
void DestoryShm(int shmid);
//com.c
#include "com.h"
int Command(int size,int flag)
{
key_t key = ftok(".",0666);
if(key == -1)
{
perror("ftok");
}
int shmid = shmget(key,size,flag);
if(shmid < 0)
{
perror("shmid");
}
return shmid;
}
int CreateShm(int size)
{
return Command(size,IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(int size)
{
return Command(size,IPC_CREAT);
}
void DestoryShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,0) < 0)
{
perror("shmctl");
exit(1);
}
return;
}
//service.c
#include "com.h"
int main()
{
int shmid = CreateShm(1024);
char* addr = (char*)shmat(shmid,NULL,0);
int i = 0;
while(i < 26)
{
printf("client say : %s\n",addr);
sleep(1);
++i;
}
shmdt(addr);
DestoryShm(shmid);
return 0;
}
//client.c
#include "com.h"
int main()
{
int shmid = GetShm(1024);
char* addr = (char*)shmat(shmid,NULL,0);
int i = 0;
while(i < 26)
{
addr[i] = 'A' + i;
addr[i+1] = '\0';
sleep(1);
++i;
}
shmdt(addr);
return 0;
}
service 先运行,创建共享内存,client再运行,并向共享内存中发送数据,service从共享内存中读取并且输出到屏幕上。
同消息队列一样,共享内存也可通过 ipcs -m 指令查看
也可以通过 ipcrm -m [shmid] 删除
共享内存总结
- 没有面向字节流/数据报的概念,和普通内存一样,可以随机访问
- 没有同步互斥机制,多个进程可同时读写,不安全
- 可以双向通信
- 适用于任何进程之间的通信
- 生命周期随内核