共享内存
共享内存是Linux下俩进程通信的一种方式,
主要功能是让两个进程的虚拟地址都映射到同一片物理地址上,就可以通过这片物理地址进行数据的交互
常用实现方法
mmap()
mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start开始的一个区域,并将文件描述符df指定的对象的一个连续的片映射到这个新的区域,连续的对象片大小为length字节,从距文件开始处偏移量为offser字节的地方开始.start地址仅仅是个表示,通常定义为NULL]
mamap()实际上是把磁盘上的文件映射在内存上,那么对文件的写入读出就可以不用IO函数 read(),write()而直接可以在内存上进行操作
如果多个进程映射同一个文件到自己的虚拟地址上,那么他们就可以同时访问这一片物理内存
就实现了利用mmap()函数完成多个进程的通信
其中由于是把文件映射入虚拟地址空间,第一次进行IO操作肯定会触发缺页异常,会把磁盘文件载入物理内存中然后通过页表建立映射关系
还有如何将多个进程的虚拟地址空间映射在同一物理空间,详见 https://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html
总结来讲mmap()的主要作用是将磁盘文件载入内存中
由于其上述的功能,可以实现进程共享一段物理内存空间,所以可以实现进程间的通信
System v的系统调用
与mmap()不同,以下函数是专门进行共享内存通讯的函数
shmget()
功能:用来创建共享内存
原型:
int shmget(key_t key, size,size_t size,int shemfg)
key:通过ftok函数生成唯一的标识码
size:共享内存的大小
shmflg:类似文件创建的权限
返回值:成功返回shmid,即该段内存的唯一标识码
此函数可以说是在物理内存上开辟了一段空间,并返回了该空间的唯一标识码
要想使用该段空间,那么就要将某个进程的虚拟地址空间通过页表映射到这段物理内存上,会用到的函数就是shmat
shmat
原形:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
说明:
将一片物理空间编号为shmid的内存通过页表映射到该进程的以void* shmaddr为首的连续空间内,如果该参数为NULL操作系统自动分配该进程接受地址
shmflg取值可能为SHM_RND和SHM_RDONLY
详情参考man手册
shmdt
int shmdt(const void *shmaddr);
shmaddr是通过shmat函数返回的地址
函数是将该进程与共享内存段解除关系
注意 不是删除共享内存段
shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
此函数是共享内存的控制函数
通常与Sytem v进程通信其他的形式,比如消息队列的接口风格保持一致,不做过多详细介绍.
这里只是使用此函数删除共享内存
cmd即是 IPC_RMID 最后一个参数不关心 设置为NULL即可
一个例子
一个进程向共享内存区域写入a ab abc ….
另一个进程从共享内存中读出 a ab abc ….
comm.h
#ifndef __COMM_H__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "/usr/"
#define PROJ_ID 0x666
//通过flags确定是create还是get
//函数给出大小就行了
//返回值是commShm函数里的shmid
//重视封装
int createShm(int size);
int getShm(int size);
int destroyShm(int shmid);
//sharedMemoryAttach函数不用封装
#endif
comm.c
#include"comm.h"
static int commShm(size_t size,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0 )
{
perror("fork");
return -1;
}
int shmid = shmget(key,size,flags);
if(shmid < 0)
{
perror("shmid");
return -2;
}
return shmid;
}
int createShm(int size)
{
return commShm(size,IPC_CREAT|IPC_EXCL|0x666);
}
int getShm(int size)
{
return commShm(size,IPC_CREAT);
}
int destroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) < 0)
{
perror("shmid");
return -2;
}
}
server.c
#include"comm.h"
int main()
{
int shmid = createShm(4096);
//
char* addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i++ < 26)
{
printf("%s\r",addr);
fflush(stdout);
sleep(1);
}
shmdt(addr);
destroyShm(shmid);
sleep(2);
return 0;
}
client.c
#include"comm.h"
int main()
{
int shmid = getShm(4096);
//
char* addr = shmat(shmid,NULL,0);
int i = 0;
sleep(2);
while(i < 27)
{
addr[i] = 'a' + i;
i++;
//a0 ab0
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
共享内存的一些总结
首先,共享内存是所有ipc通信中最快的一种,因为当进程和一段内存映射后,访问该内存就可以在用户态进行,而不需要返回内核态通过操作系统完成,相反,比如管道和消息队列都是操作系统维护的
其次,共享内存虽然是临界资源,但是在这片内存不会有任何限制,所以不提供管道和消息队列的同步与互斥的概念,所以如果不加限制,这片内存中的数据是不安全的
我们可以通过加上信号量等方法对临界区的资源进行限制