共享内存
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,只需要通过一些内存访问函数来传递。
共享内存示意图
共享内存是整块内存当中的一块特殊区域,这块区域可以映射到不同的地址空间。A、B进程就可以通过共享内存区传递数据,并且不涉及系统调用。
管道、消息队列与共享内存传递数据对比
这个例子是服务器向客户端传送文件,涉及了4次系统调用
用共享内存传递数据,两次系统调用
共享内存是内存中的一块特殊区域,这块区域可以映射到服务器的地址空间中,也可以映射到客户的地址空间中。只涉及了两次系统调用,一次将数据拷贝到共享内存区,另一次是将共享内存区中的数据写到输出文件。从共享内存区读取数据这个过程是通过内存访问函数来实现的,不需要涉及到内存从内核空间拷贝到用户空间中。所以效率大大提高。
mmap函数
功能:
将文件或者设备空间映射到共享内存区。
原型:
void *mmap(void *addr,size_t len,int prot,int flags,int fd,off_t offset);
参数:
addr:要映射的起始地址,通常指定为NULL,让内核自动选择
len:映射到进程地址空间的字节数
prot:映射区保护方式
fd: 文件描述符;
offset: 从文件头开始的偏移量;
prot | 说明 |
PROT_READ | 页面可读 |
PROT_WRITE | 页面可写 |
PROC_EXEC | 页面可执行 |
PROC_NONE | 页面不可访问 |
flags | 说明 |
MAP_SHARED | 变动是共享的 |
MAP_PRIVATE | 变动是私有的 |
MAP_FIXED | 准确解释addr参数, 如果不指定该参数, 则会以4K大小的内存进行对齐 |
MAP_ANONYMOUS | 建立匿名映射区, 不涉及文件 |
内存映射示意图:
实际上,mmap这个函数在创建内存映射区的时候是以页面为单位来分配的,如果len小于一个页面的大小,mmap创建的共享内存区会大于len,至少是一个页面。
munmap函数
功能:取消mmap函数建立的映射
int munmap(void *addr,size_t len);
参数:
addr:映射的内存起始地址
len:映射到进程地址空间的字节数
返回值:
成功返回0,失败返回-1。
msync函数
功能:对映射的共享内存执行同步操作
int msync(void *addr,size_t len,int flags);
参数:
addr:内存起始地址
len:长度
flags:选项
返回值:
成功返回0,失败返回-1。
如果指定了MS_ASYNC选项,仅仅只是告诉内核要将高速缓冲区中的数据写回到磁盘,然后立刻返回。这是可能内核还未执行就返回了,不会发生阻塞。
如果指定了MS_SYNC选项,告诉内核要将高速缓冲区中的数据写回到磁盘,而且等待内核执行完毕才返回。
如果指定MS_INVALIDATE会使高速缓冲区的数据失效,下次进行读操作时只能对文件进行操作。
map注意点
1.映射不能改变文件的大小
2.可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,而应该以内存页面的大小为准
3.文件一旦被映射后,所有对映射区域的访问实际上是对内存区域的访问。映射区域内容写回文件时,所写内容不能超过文件的的大小。
写程序
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
typedef struct stu
{
char name[4];
int age;
}STU;
//接收一个参数,传递文件名称进来
int main(int argc,char *argv[])
{
if(argc!=2)
{
fprintf(stderr,"Usage: %s <file>\n",argv[0]);
exit(EXIT_FAILURE);
}
int fd;
//以创建的、读写的、清空的方式打开一个文件
fd=open(argv[1],O_CREAT | O_RDWR | O_TRUNC,0666);
if(fd==-1)
ERR_EXIT("open");
lseek(fd,sizeof(STU)*5-1,SEEK_SET);
//产生一个40个字节的文件
write(fd,"",1);
STU *p;
//对文件进行映射
//让内核选择一个合适的地址进行映射,第一个参数为NULL
p=(STU*)mmap(NULL,sizeof(STU)*5,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(p==NULL)
ERR_EXIT("mmap");
char ch='a';
int i;
for(i=0;i<5;i++)
{
//对内存的操作就是对文件的操作
memcpy((p+i)->name,&ch,1);
(p+i)->age=20+i;
ch++;
}
printf("initialize over\n");
//取消mmap函数建立的映射
munmap(p,sizeof(STU)*5);
printf("exit...\n");
return 0;
}
读程序
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
typedef struct stu
{
char name[4];
int age;
}STU;
//接收一个参数,传递文件名称进来
int main(int argc,char *argv[])
{
if(argc!=2)
{
fprintf(stderr,"Usage: %s <file>\n",argv[0]);
exit(EXIT_FAILURE);
}
int fd;
//打开一个文件
fd=open(argv[1],O_RDWR);
if(fd==-1)
ERR_EXIT("open");
STU *p;
//对文件进行映射
//让内核选择一个合适的地址进行映射,第一个参数为NULL
p=(STU*)mmap(NULL,sizeof(STU)*5,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(p==NULL)
ERR_EXIT("mmap");
int i;
for(i=0;i<5;i++)
{
//对内存的读操作就是对文件的读操作
printf("name=%s age=%d\n",(p+i)->name,(p+i)->age);
}
printf("initialize over\n");
//取消mmap函数建立的映射
munmap(p,sizeof(STU)*5);
printf("exit...\n");
return 0;
}