1、特点:
① 进程相关的
② 与XSI共享内存一样,需要与同步原语一起使用
③ 只能是有共同祖先的进程才能使用
2、使用
系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:
适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()
典型调用代码如下:
fd=open(name, flag, mode); if(fd<0) ...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,可以参看UNIX网络编程第二卷。【3】
(2)使用特殊文件提供匿名内存映射:
适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
3、说明
(1)void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t offset );
把文件或设备映射或解除映射到内存中
0)flag:必须有MAP_SHARED 标志
MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
MAP_ANONYMOUS建立匿名共享。此时会忽略参数fd(fd可以指定为-1),不涉及文件,而且映射区域无法和其他进程共享(只能用于具有亲缘关系的进程间通信)。
映射/dev/zero可为调用程序提供零填充的虚拟内存块。
1)start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
2)length:代表将文件中多大的部分映射到内存。
3)offset 必须是页面大小的整数倍。页面大小由 getpagesize(2)得到。
4)被映射的文件大小应是页面大小的整数倍。如一个文件大小不是页面大小的整数倍,映射时多出来的区域将被赋为0,对这些区域的写不会被写回到文件中。
5)munmap()系统调用将删除指定地址范围内的映射区域。随后对这个范围内区域的引用将产生非法的内存引用。当这个进程终止后,这个区域也会被删除。另一方面,关闭文件描述符并不会删除映射区域。
6)fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
7)若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1)。
(2) munmap
int munmap( void * addr, size_t len )
在进程地址空间中解除一个映射关系,当映射关系解除后,对原来映射地址的访问将导致段错误发生。
void * addr :调用mmap()时返回的地址
size_t len :映射区的大小
(3)int msync ( void * addr , size_t len, int flags)
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以调用msync()实现磁盘上文件与共享内存区的内容一致。
void * addr :调用mmap()时返回的地址
size_t len :映射区的大小
int flags :MS_ASYN: 异步写,MS_SYN : 同步写,MS_INVALIDAT : 无效的cache 数据。
4、示例
示例1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/mman.h>
int main()
{
int fd,aaa,i;
char a[15]="kkkkkKKKKKKK";
char *pp;
pid_t pid;
fd = open("./aaa", O_RDWR);
//開啟一個名為aaa的檔案在/aaa 內容可自己先打
pp = (char*)mmap((void*)a, 15, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//映對檔案到記憶體,擁有可讀寫的權限
pid = fork();
if(pid == -1)
{
printf("Can't fork.\n");
exit(1);
}
if(pid == 0)
{
int seed;
seed = time(NULL);
srand(seed);
for(i=0; i < 50; i++)
pp[i] = 'b';
return 0;
}
else
{
wait();
printf("P%s\n", pp);
}
return 0;
}
示例2
#include<stdio.h>
#include <string.h>
#include <stdlib.h>
#include<math.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/mman.h>
typedef struct
{
char str[512];
long lval;
double dval;
}SSS;
#define NUMBER (1000)
int main()
{
int fd;
char c;
long psize, size;
SSS *ptr;
long i,lval;
double dval;
char buf[512];
/* マップ用ファイルオープン */
if((fd = open("./MapFile",O_RDWR|O_CREAT, 0666)) == -1)
{
perror("open");
exit(-1);
}
/* ページサイズで境界合わせを行なったサイズを計算 */
// #ifdef BSD
// psize=getpagesize();
// #else
// psize=sysconf(_SC_PAGE_SIZE);
// #endif
psize = getpagesize();
size = (NUMBER * sizeof(SSS)/psize + 1) * psize;
/* ファイルの必要サイズ分先にシークし、0を書き込み */
/* ファイルのサイズをマップしたいサイズにする為 */
if(lseek(fd, size, SEEK_SET) < 0)
{
perror("lseek");
exit(-1);
}
if(read(fd, &c, sizeof(char))==-1)
{
c='\0';
}
if(write(fd, &c, sizeof(char))==-1)
{
perror("write");
exit(-1);
}
/* マップ */
ptr=(SSS *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if((int)ptr==-1)
{
perror("mmap");
exit(-1);
}
/* テスト */
while(1)
{
/* 標準入力からデータ読み込み */
gets(buf);
if(feof(stdin))
{
break;
}
lval = atoi(buf);
dval = atof(buf);
/* 全データの値をセット */
for(i=0;i < NUMBER; i++)
{
strcpy(ptr[i].str, buf);
ptr[i].lval=lval;
ptr[i].dval=dval;
}
/* 実際にファイルに書き込みたい場合はmsync()する */
/* msync(ptr,size,MS_ASYNC); */
}
/* 実際にファイルに書き込み、同期を取る */
msync(ptr,size,0);
/* アンマップ */
if(munmap(ptr,size)==-1)
{
perror("munmap");
}
/* ファイルクローズ */
close(fd);
return 1;
}
#include <stdio.h>
#include <math.h>
#include <fcntl.h>
#include <math.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
typedef struct
{
char str[512];
long lval;
double dval;
}SSS;
#define NUMBER (1000)
int main()
{
int fd;
long psize,size;
SSS *ptr;
long i;
/* マップ用ファイルオープン */
if((fd = open("./MapFile",O_RDWR))== -1)
{
perror("open");
exit(-1);
}
// /* ページサイズで境界合わせを行なったサイズを計算 */
// #ifdef BSD
// psize=getpagesize();
// #else
// psize=sysconf(_SC_PAGE_SIZE);
// #endif
psize = getpagesize();
size=(NUMBER*sizeof(SSS)/psize+1)*psize;
/* マップ */
ptr=(SSS*)mmap(0, size, PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
/* テスト */
while(1)
{
/* 全データの表示 */
for(i=0;i < NUMBER;i++)
{
if(i%100==0)
{
printf("{%5d:%d,%g,%s}\n",i,ptr[i].lval,ptr[i].dval,ptr[i].str);
}
}
printf("\n");
/* 1秒待ち */
sleep(1);
}
return 0;
}
5、其他
1)进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。
2)一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。
3)mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
4)最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小。文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。
参考
【1】 http://download.oracle.com/docs/cd/E19253-01/819-7052/6n91ag9r2/index.html#chap6mem-35956
【2】 对定义作了详细的讲解
http://hi.baidu.com/zmjdx/blog/item/ede7e52ac0734892023bf678.html
【3】 unix网络编程中讲解
http://blog.chinaunix.net/space.php?uid=9185047&do=blog&cuid=2282785
【4】 台湾某大学的网页
http://pws.niu.edu.tw/~ttlee/2010.os.day/memory.map2/
【5】 举的一个非常形象的例子
http://www.ncad.co.jp/~komata/c-kouza20.htm
【6】 http://blog.csdn.net/sjmw888/article/details/5318412
【7】 http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html