内存映射简介


1 内存映射简介

Linux 允许一个进程把文件映射到它的地址空间。这样的映射创建了文件数据和映射内存区域之间的一一对应的关系,称之为内存映射。


2 内存映射的用途:

2.1 高速文件访问。一般的i/o技术,如 read write的操作,需要内核通过缓冲区来复制数据,而不是设备文件和用户态进程之间直接进行。内存映射消除了中间缓冲区,为文件保存一个内存副本。
2.2 可执行文件可以被映射到一个程序的内存中,从而允许程序动态地加载可执行部分。这其实就是动态加载的实现原理。
2.3 可以通过映射/dev/zero的一部分,使用这种技术来分配内存。
2.4 内存映射分配出的新内存可以变成可执行的,使得新内存被可以执行的机器指令所充满
2.5 可以像内存一样处理文件,使用指针代替系统调用来读取文件内容,可以免去使用read write 的依赖,简化程序读写操作。
2.6 内存映射允许进程在进程的创建和消亡的整个过程中共享内存区域,内存的内容存储在被映射文件中,从而使它独立于任何进程。

3 页大小获取及页对齐

页大小:
在操作系统中,系统的内存被分成许多块,每一块就是一页(page)。每页的大小随着操作系统的体系结构不同而不同,页大小通常是由操作系统来定义。
可以通过
#include < unistd.h>
size_t getpagesize(void);//返回页中的字节大小
对于系统中的每个页,内核都会告诉硬件各种进程可能会访问的页,当进程违反内核规定的时候访问其它页,则会触发 一般保护性错误及段错误
页对齐:
例 在一个页大小为2k的系统上,当我们访问的页地址是页大小的整数倍,我们称之为页对齐。
0 2048 4096 … 分别为第0 1 2 页的起始页地址。

4 内存映射的建立

内存映射是通过mmap函数来进行建立的
4.1 函数头文件
#include < sys/mman.h>
4.2 函数原型
caddr_t mmap(caddr_t address,size_t length,int protection,int flags,int fd,off_t offset);
4.3 函数参数说明
address 指定了数据应该被映射到内存的位置,通常 address 为空,则进程分配地址由内核任意挑选地址,如果指定address 必须为页对齐的地址;
length 需要指定多少字节映射到内存中;
protection: 进程可以控制对内存区域的访问类型:PROT_READ(被映射区域可能被读) 、PROT_WRITE(被映射区域可能被写)、PROT_EXEC(被映射区域可能被执行)、PROT_NONE(禁止对映射区域进行任何访问);
flags:
MAP_ANONYMOUS:返回一个匿名映射,而不是映射一个文件。看起来像一个正常的映射,但是不涉及任何物理文件。虽然这个内存区域不能跟其他进程共享,也不能自动保存到一个文件中,但匿名映射允许进程为了私有用途而分配新的内存,这样的映射经常用来实现malloc,使用此标志 fd将被忽略 。
MAP_FIXED:如果address不合法,则失败
MAP_PRIVATE:写对进程是私有的
MAP_SHARED: 写入数据被复制到文件中
MAP_DENYWRITE:不允许写入到文件中
MAP_GROWSDOWN:向下扩展内存区域
MAP_LOCKED:将页面锁定在内存中
offset:页面的偏移量
这个值必须是页的整数倍或0。
4.4 函数返回值说明
mmap如果发生错误,则返回一个等同于-1的地址,成功则返回一个映射地址。

例:
#include < fcntl.h>
#include < sys/mman.h>
#include < sys/stat.h>
#include < sys/types.h>
#include < stdio.h>
#include < unistd.h>

int main(int argc,const char ** argv){
int fd;
struct stat sb;
void *region;

if((fd = open(argv[1],O_RDONLY)) <0 ){ printf("file read error !\n"); return 1; } if(fstat(fd,&sb)){ printf("stat read error!\n"); return 1; } region = mmap(NULL,sb.st_size,PROT_READ,MAP_SHARED,fd,0); if(region == ((caddr_t)-1)){ printf("mmap create error!\n"); return 1; } close(fd); if(write(1,region,sb.st_size) !=sb.st_size){ printf("write error!\n"); return 1; } return 0; }
5 撤销内存映射

在进程结束一个内存映射后,它可以通过munmap()函数来撤销内存区域,撤销后的内存区域,如果继续访问,将会产生段错误。
#include < sys/mman.h>
int munmap(caddr_t addr, int length);