mmap:
功能描述:mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。 基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime
在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。
用法:
#include
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *start, size_t length);
mmap能够把磁盘文件映射进内存,这样对文件的操作就可以直接像操作内存那样,而不用read()/write()函数了,那么通过mmap映射的空间和内存、磁盘的IO性能上有何差异呢?为此,写了如下程序测试mmap映射的空间、内存已经磁盘的读写速度。
程序代码如下(mmap_test.c):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SIZE 50*1024*1024
int main(int argc, char** argv)
{
FILE *fp;
char c = 'a';
int i;
char *pmap;
int fd;
char *mem;
struct timeval t1, t2;
long spend_us;
printf("write size: %dMB/n", SIZE/1024/1024);
if((fp = fopen("disk_file", "w")) == NULL)
{
printf("fopen error!/n");
return -1;
}
gettimeofday(&t1, NULL);
for(i = 0; i < SIZE; i++)
{
if(fwrite(&c, sizeof(char), 1, fp) != 1)
printf("file write error!/n");
}
gettimeofday(&t2, NULL);
spend_us = 1000000 * (t2.tv_sec - t1.tv_sec) + (t2.tv_usec - t1.tv_usec);
printf("write disk file,spend time:%ld us/n", spend_us);
fclose(fp);
fd = open("mmap_file", O_RDWR);
if(fd < 0){
perror("open mmap_file");
return -1;
}
pmap = (char *)mmap(NULL, sizeof(char) * SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(pmap == MAP_FAILED){
perror("mmap");
return -1;
}
gettimeofday(&t1, NULL);
for(i = 0; i < SIZE; i++)
{
memcpy(pmap + i, &c, 1);
}
gettimeofday(&t2, NULL);
spend_us = 1000000 * (t2.tv_sec - t1.tv_sec) + (t2.tv_usec - t1.tv_usec);
printf("write mmap file,spend time:%ld us/n", spend_us);
close(fd);
munmap(pmap, sizeof(char) * SIZE);
mem = (char *)malloc(sizeof(char) * SIZE);
if(!mem)
{
printf("mem malloc error!/n");
return -1;
}
gettimeofday(&t1, NULL);
for(i = 0; i < SIZE; i++)
{
memcpy(mem + i, &c, 1);
}
gettimeofday(&t2, NULL);
spend_us = 1000000 * (t2.tv_sec - t1.tv_sec) + (t2.tv_usec - t1.tv_usec);
printf("write mem,spend time:%ld us/n", spend_us);
free(mem);
return 0;
}
编译程序:gcc –o mmap_test mmap_test.c
运行之前在当前目录创建disk_file和mmap_file
touch disk_file
dd if=/dev/zero of=mmap_file bs=1M count=50
运行程序:./mmap_test
运行结果:
write size: 50MB
write disk file,spend time:3020481 us
write mmap file,spend time:793141 us
write mem,spend time:399923 us
计算写的速度:
写磁盘速度:16.55M/s
写mmap映射的空间速度:63.04M/s
写内存的速度:125.02M/s
由此可见,由mmap映射的空间的读写速度比直接读写磁盘的速度快。
那么,为什么会快呢?这就需要从mmap实现的原理来分析了。
在此图中,“起始地址”是mmap的返回值。在图中,映射存储区位于堆和栈之间。
以下摘自博客
http://hi.baidu.com/%D4%BC%D0%DE%D1%C7ing/blog/item/ee1664d899820ae138012f11.html
内核怎样保证各个进程寻址到同一个共享内存区域的内存页面
1、 page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。
2、文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。
3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。
4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。
注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。
5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。