目录
共享内存
共享内存允许两个或者多个进程共享物理内存的同一块区域 (通常被称为段) 。由于一个共享内存段会称为一个进程用户空间的一部分,需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。
共享内存通信的原理就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。进程写入的东西,另外⼀个进程马上就能看到,不需要经过拷贝,大大提高了进程间通信的效率:
共享内存接口:
int shmget(key_t key, size_t size, int shmflg); //功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。新创建的内存段中的数据都会被初始化为0
- 参数:
- key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。一般使用16进制表示,非0值
- size: 共享内存的大小
- shmflg: 属性
- 访问权限
- 附加属性:创建 / 判断共享内存是不是存在
- 创建:IPC_CREAT
- 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
IPC_CREAT | IPC_EXCL | 0664
- 返回值:
失败:-1 并设置错误号
成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。
void *shmat(int shmid, const void *shmaddr, int shmflg); //和当前的进程进行关联
- 参数:
- shmid : 共享内存的标识(ID),由shmget()返回值获取
- shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
- shmflg : 对共享内存的操作
- 读 : SHM_RDONLY, 必须要有读权限
- 读写: 0
- 返回值:
成功:返回共享内存的首(起始)地址。 失败: (void *) -1
int shmdt(const void *shmaddr); //解除当前进程和共享内存的关联
- 参数:
shmaddr:共享内存的首地址
- 返回值:成功 0, 失败 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进程被销毁了对共享内存是没有任何影响。
- 参数:
- shmid: 共享内存的ID
- cmd : 要做的操作
- IPC_STAT : 获取共享内存的当前的状态
- IPC_SET : 设置共享内存的状态
- IPC_RMID: 标记共享内存被销毁
- buf:需要设置或者获取的共享内存的属性信息(对应cmd参数属性)
- IPC_STAT : buf存储数据
- IPC_SET : buf中需要初始化数据,设置到内核中
- IPC_RMID : 没有用,NULL
key_t ftok(const char *pathname, int proj_id); //根据指定的路径名,和int值,生成一个共享内存的key
- 参数:
- pathname:指定一个存在的路径
/home/Linux/a.txt
- proj_id: int类型的值,但是这系统调用只会使用其中的1个字节
范围 : 0-255 一般指定一个字符 'a'
定义两个文件 write.c , read.c 代表两个进程,write.c文件里创建一个共享内存,write.c往共享内存里写数据, read.c获取共享内存,并从共享内存里读数据:
write.c :
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main() {
int shmid = shmget(100 , 4096 , IPC_CREAT | 0664); // 创建一个共享内存
void *ptr = shmat(shmid ,NULL , 0); // 和当前进程进行关联,调用attach会返回共享内存的首地址
// 写数据
char * str = "hello, i am process one !!! ";
memcpy(ptr ,str , strlen(str)+1 );
printf("按任意键继续\n");
getchar(); //让程序停在这
shmdt(ptr); // detach解除关联
shmctl(shmid , IPC_RMID , NULL); // 删除共享内存
return 0;
}
read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main() {
int shmid = shmget(100 , 0 ,IPC_CREAT); // 获取定义好的共享内存
void *ptr = shmat(shmid , NULL , 0); //和当前进程进行关联
// 读数据
printf("%s\n" , (char*) ptr);
printf("按任意键继续\n"); //暂停一下
getchar();
shmdt(ptr); // 解除关联
shmctl(shmid, IPC_RMID, NULL); // 删除共享内存
return 0;
}
编译运行 write.c :
新建终端编译运行 read.c :
可以看到简单的完成了两个进程间的通信。
IPC操作命令
linux上可以直接通过 ipcs命令来操作有关于进程通信的各种信息:
ipcs-a //打印当前系统中所有的进程间通信方式的信息
ipcs -m // 打印出使用共享内存进行进程间通信的信息
ipcs -q // 打印出使用消息队列进行进程间通信的信息
ipcs -s //打印出使用信号进行进程间通信的信息
再次编译运行上面的 write.c 文件,然后输入 ipcs -m :
可以看到 key:0x00000064(16进制),其实就是我们一开始shmget( )函数的第一个参数 10(二进制) ,key用来在系统中唯一标识一块共享内存,和进程的PID类似。
nattch 代表当前与这个共享内存关联的进程数, 1 也就是指的是 write.c这个进程。编译运行read.c 再次查看:
当我们终止掉 write.c 进程时:
可以看到key值变为了0 ,代表的是当前共享内存处于标记删除状态 ,因为还有read.c与之关联。
总结
1 .对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减1,挂架成功时,计数器加1,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
2. 所有进程操作的是同一块共享内存,因为直接在内存上操作,所以共享内存的速度也提高。
共享内存与内存映射区别
1. 内存映射是在磁盘上建立一个文件,每个进程地址空间中都会开辟出一块空间进行 文件-内存的映射 ;共享内存每个进程最终会映射到同一块物理内存。共享内存保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。(mmap每个进程都会有自己的内存映射区,shm是映射到同一物理内存)
2. 共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外,但其只能用在有血缘关系的进程间)
3. 当机器宕机,因为内存映射把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以内存映射不会丢失,但是共享内存就会丢失。