转自:http://blog.chinaunix.net/uid-24774106-id-365941.html?utm_source=jiancool
mmap这个领域有很多优秀的书籍 博文介绍,本文做的事情只是将这些东西串起来,以更好懂的方式讲一下。
本文绝非原创,从很多博文及书籍中copy的东西。写成文章,也为了防止自己遗忘。
OK 我们开始
1 mmap的应用
mmap的本质是,把一个
文件或者posix
共享内存区队形映射到调用进程的地址空间。
三个目的
a)使用普通文件提供内存映射IO
b)使用特殊文件提供匿名映射IO
c)使用shm_open 以提供无亲缘关系的进程间的posix共享内存区
mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。
#include
void *mmap(void *addr ,size_t len,int prot,int flags,int fd,off_t offset)
如果成功返回被映射区域的起始地址。
下面挨个解释每个参数的含义
第一个参数 是个地址,指向用户地址空间的一个地址,当然我们的函数就是为了寻找一个可用的地址空间,将文件描述符fd 对应的文件从偏移量 offset 开始,copy文件的len个字符进入用户地址空间。将起始地址返回。为什么第一个参数就是用户地址空间的指针呢?这不是骑驴找驴吗?
这个addr参数可以
NULL,这种情况我们比较容易理解。当
addr不为NULL的时候,这其实是个建议查找地址,用来指导内存区定位的线索,是个用户指定的经验值。
还记得
进程虚拟地址空间之arch_get_unmapped_area中有 如下代码:
1) 如果用户打上了MAP_FIXED标志,表示用户王八吃秤砣,铁了心要addr这个地址作为映射起始地址,
OK,直接把 addr 返回给这个二杆子用户。
OK,直接把 addr 返回给这个二杆子用户。
2)如果没打上 MAP_FIXED标志,则首先在建议地址addr附近寻找合适的区域。
3)addr 为NULL,方法
进程虚拟地址空间之arch_get_unmapped_area中有详细介绍。
从可移植性考虑,一般不能指定MAP_FIXED这个标记。
if (flags & MAP_FIXED)
return addr;
if (addr) {
addr = PAGE_ALIGN(addr);
vma = find_vma(mm, addr);
if (TASK_SIZE - len >= addr &&
(!vma || addr + len <= vma->vm_start))
return addr;
}
第二个参数len,没啥好说的,干脆和
fd、offset 一起介绍了。 这个含义是
将fd对应的文件,从offset位置开始,长为len的内容映射到内存地址空间。
需要解释的是
fd,存在一种情况叫匿名映射,所谓匿名映射,表示不存在fd这么个真实的文件。
1 BSD 提供匿名映射的办法是
fd =-1,同时 flag 指定为
MAP_SHARE|MAP_ANON(hy:MAP_PRIVATE)。
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);
一个作用是用它作为源,产生一个特定大小的空白文件。下面的命令产生出一个bean 文件在当前目录下。
大小为1M。
dd if=/dev/zero of=bean count=1024 bs=1024
OK,言归正传,
第三个参数 prot
,这个是负责保护作用的。
PROT_READ 数据可读
PROT_WRITE 数据可写
PROT_EXEC 数据可执行
PROT_NONE 数据不可访问。
第四个参数 flags
MAP_SHARED 变动是共享的,换言之,如果映射的内存区域的数据变化了,磁盘上的文件也得跟着变
MAP_PRIVATE 变动是私有的,换言之,映射的内存区域 爱怎么折腾怎么折腾,fd对应的文件不变化
MAP_FIXED 这个就是前面提到的二杆子参数,非addr这个地址不可。。
OK 映射完成后,fd对应的文件可以close ,没啥影响。
OK,学习完了mmap,来个直观的感受:
#include
#include
#include
#include
#include
#define FILE_LENGTH 0x400
int main(int argc, char *argv[])
{
int fd;
void *map_memory;
/* Open a file to be mapped. */
fd = open("/tmp/shared_file", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
lseek(fd, FILE_LENGTH-1, SEEK_SET);
write(fd, "", 1);
lseek(fd, 0, SEEK_SET);
/* Create map memory. */
map_memory = mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
/* Write to mapped memory. */
if (strlen(argv[1]) < FILE_LENGTH)
sprintf((char *)map_memory, "%s", argv[1]);
sleep(100);
exit(0);
}
# ./mmap_write hello&
[1] 14667
# cat /proc/14667/maps
08048000-08049000 r-xp 00000000 16:42 213757 /tmp/mmap_write
08049000-0804a000 rw-p 00000000 16:42 213757 /tmp/mmap_write
40000000-40015000 r-xp 00000000 16:42 12828674 /lib/ld-2.3.2.so
40015000-40016000 rw-p 00014000 16:42 12828674 /lib/ld-2.3.2.so
40016000-40017000 rw-p 00000000 00:00 0
40017000-40018000 -w-s 00000000 16:42 213753 /tmp/shared_file
42000000-4212e000 r-xp 00000000 16:42 13991938 /lib/tls/libc-2.3.2.so
4212e000-42131000 rw-p 0012e000 16:42 13991938 /lib/tls/libc-2.3.2.so
42131000-42133000 rw-p 00000000 00:00 0
bfffe000-c0000000 rwxp fffff000 00:00 0
我们看到 /tmp/shared_file 文件已经映射到内存中取了,占用空间0x40018000 - 0x40017000 = 4K。FILE_LENGTH 大小是0x400 = 1K,但是
映射的分配是以页面为单位分配的,即最小分配4K
。