mmap匿名映射
一、原理
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);来实现内存映射。
从原型可知,存在一个参数为fd,根据fd,存在一种情况叫匿名映射,所谓匿名映射,表示不存在fd这么个真实的文件。实现匿名映射的方式主要有以下两种:
1、BSD 提供匿名映射的办法是fd =-1,同时 flag 指定为MAP_SHARE|MAP_ANON。
ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,
MAP_SHARED|MAP_ANON,-1,0);
2、SVR4 提供匿名映射的办法是 open /dev/zero设备文件,把返回的文件描述符,作为mmap的fd参数。
fd = open("/dev/zero",O_RDWR);
/dev/zero 是一个特殊的文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00)
一个作用是用它作为源,产生一个特定大小的空白文件。
匿名内存映射适用于具有亲属关系的进程之间;由于父子进程之间的这种特殊的父子关系,在父进程中先调用mmap(),然后调用fork(),那么,在调用fork() 之后,子进程继承了父进程的所有资源,当然也包括匿名映射后的地址空间和mmap()返回的地址,这样父子进程就可以通过映射区域进行通信了;
这里不是一般的继承关系,一般来说,子进程单独维护从父进程继承下来的一些变量,而mmap()返回的地址却是由父子进程共同维护的;对于具有亲属关系的进程之间实现共享内存的最好方式应该是采用匿名映射的方式。此时,不必指定具体的条件,只要设置相应的标志即可。
二、代码
下面分别以匿名映射方式举例说明父子进程之间的通信:
1、fd=-1
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <semaphore.h>
struct file_content
{
sem_t st;
void *pv;
}file_content;
#define NINT 16
int main (int argc, char *argv[])
{
struct file_content *pfc;
pfc =mmap (NULL, sizeof (file_content) + sizeof (int) * NINT,
PROT_READ | PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
if (pfc == MAP_FAILED)
{
printf ("map failed!\n");
exit(3);
}
//信号量初始化为1,用于互斥
sem_init (&(pfc->st), 1, 1);
//pv指向结构体下一个字节的地址
pfc->pv = (void *) ((char *) pfc + sizeof (struct file_content));
printf ("结构体地址:\t\t%x\n结构体下一位置地址:\t\t%x\n", (int)pfc, (int)pfc->pv);
// 不加上这句的话,可能输出会卡在那里!
setbuf(stdout,NULL);
//子进程
if (fork () == 0)
{
int i;
while (1)
{
sem_wait (&(pfc->st));
printf ("Child process\n");
int *p = pfc->pv;
for (i = 0; i < NINT; i++)
{
p[i] = 2 * i;
}
for (i = 0; i < NINT; i++)
{
printf ("%d ", p[i]);
}
printf ("\n");
sem_post (&(pfc->st));
sleep(2);
}
}
// 父进程
else
{
int i;
while (1)
{
sem_wait (&(pfc->st));
printf ("Father process\n");
int *p = pfc->pv;
/*
for (i = 0; i < NINT; i++)
{
p[i] = 3 * i;
}
*/
for (i = 0; i < NINT; i++)
{
printf ("%d ", p[i]);
}
printf ("\n");
sem_post (&(pfc->st));
sleep(2);
}
}
if (munmap (pfc, sizeof (file_content) + sizeof (int) * NINT) == -1)
{
printf ("ummap!\n");
exit (2);
}
return 0;
}
2、使用/dev/zero
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <semaphore.h>
struct file_content
{
sem_t st;
void *pv;
}file_content;
#define NINT 16
#define FILE_PATH "/dev/zero"
int main (int argc, char *argv[])
{
int fd;
fd = open(FILE_PATH,O_RDWR);
if(fd < 0)
{
printf("open zero fail \n");
exit(2);
}
struct file_content *pfc;
pfc =mmap (NULL, sizeof (file_content) + sizeof (int) * NINT,
PROT_READ | PROT_WRITE, MAP_SHARED|MAP_ANON, fd, 0);
close(fd);
if (pfc == MAP_FAILED)
{
printf ("map failed!\n");
exit(3);
}
//信号量初始化为1,用于互斥
sem_init (&(pfc->st), 1, 1);
//pv指向结构体下一个字节的地址
pfc->pv = (void *) ((char *) pfc + sizeof (struct file_content));
printf ("结构体地址:\t\t%x\n结构体下一位置地址:\t\t%x\n", (int)pfc, (int)pfc->pv);
// 不加上这句的话,可能输出会卡在那里!
setbuf(stdout,NULL);
//子进程
if (fork () == 0)
{
int i;
while (1)
{
sem_wait (&(pfc->st));
printf ("Child process\n");
int *p = pfc->pv;
for (i = 0; i < NINT; i++)
{
p[i] = 2 * i;
}
for (i = 0; i < NINT; i++)
{
printf ("%d ", p[i]);
}
printf ("\n");
sem_post (&(pfc->st));
sleep(2);
}
}
// 父进程
else
{
int i;
while (1)
{
sem_wait (&(pfc->st));
printf ("Father process\n");
int *p = pfc->pv;
/*
for (i = 0; i < NINT; i++)
{
p[i] = 3 * i;
}
*/
for (i = 0; i < NINT; i++)
{
printf ("%d ", p[i]);
}
printf ("\n");
sem_post (&(pfc->st));
sleep(2);
}
}
if (munmap (pfc, sizeof (file_content) + sizeof (int) * NINT) == -1)
{
printf ("ummap!\n");
exit (2);
}
return 0;
}
通过上述代码即可通过匿名内存映射的方式实现父子进程之间的通信,程序执行结果如下图所示:
父子进程对于匿名映射空间的修改都会被彼此所见,通过这样一段共享内存,实现了父子进程之间的通信。