共享内存
如上图所示,多个进程之间通过自己的页表,映射到物理内存的共享区,现在这些进程都可以对这个共享区域的数据进行操作,且一个进程操作了这块空间的数据,其他的进程也可以“看得到”。
和管道/消息队列比起来
- 共享内存的效率是比较高的
管道和消息队列在执行数据的读写时,需要将数据从用户区拷贝到内核区,再将数据从内核区拷贝至数据区。消耗比较大,所以效率比较低。
消息队列不需要进行这两次拷贝,不需要进行内核区和用户区的交互,效率较高,速度较快。
- 安全性比较低
没有提供任何的保护机制,多个进程操作共享内存,可能出现二义性的问题。
共享内存函数
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] 删除
共享内存总结
- 没有面向字节流/数据报的概念,和普通内存一样,可以随机访问
- 没有同步互斥机制,多个进程可同时读写,不安全
- 可以双向通信
- 适用于任何进程之间的通信
- 生命周期随内核