首页 > 代码库 > Linux - 内存映射
Linux - 内存映射
内存映射原理
既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。
mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,如上图过程2所示。
前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如上图过程3所示。
如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如上图过程4所示。
注:在进程的地址空间中,栈的下方是内存映射段,内核直接将文件的内容映射到内存,通过虚存管理机构将进程中映射的地址转换为内存中的物理地址。
read系统调用原理
内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
addr: 指定映射的起始地址, 通常设为NULL, 由系统指定。
length: 映射到内存的文件长度,即可用访问的数据量
prot: 映射区的保护方式,它是下列常值的按位OR结果
PROT_EXEC: 映射区可被执行
PROT_READ: 映射区可被读取
PROT_WRITE: 映射区可被写入
PROT_NONE 映射区不可访问.
flags: 映射区的特性, 可以是: MAP_SHARED、MAP_PRIVATE、MAP_FIXED
MAP_SHARED:对此区域所做的修改内容奖写入文件内;允许其他映射该文件的进程共享,意思是:n个mmap.out程序在运行,这n个进程的“虚拟内存区域”的物理空间空间都相同。
MAP_PRIVATE:对此区域所做的修改不会更改原来的文件内容,对映射区的写入操作会产生一个映射区的复制(copy-on-write);意思是:n个mmap.out程序在运行,但是虚拟内存区域的物理地址会被内核另外分配。
fd: 由open返回的文件描述符, 代表要映射的文件。
offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。
返回值:返回成功----函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址,该内存区域与可以通过一个打开的文件描述符访问的文件内容相关联。如果失败返回MAP_FAILED(-1),错误原因存于errno 中。
start 修改部分的起始地址,length为长度。
flags则有三个:
MS_ASYNC : 采用异步写方式,请Kernel快将资料写入,发出回写请求后立即返回。
MS_SYNC : 采用同步写范式,在msync结束返回前,将资料写入。
MS_INVALIDATE:通知使用该共享区域的进程,数据已经修改,在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值。
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
typedef struct {
int integer;
char string[24];
}RECORD;
#define NRECORDS (100)
int main(int argc,char**argv)
{
RECORD record,*mapped;
int i,f;
FILE *fp;
//打开初始化文件
fp = fopen("record.dat","w+");
for (i=0;i<NRECORDS;i++)
{
record.integer = i;
sprintf(record.string,"RECORD-%d",i);
fwrite(&record,sizeof(record),1,fp);
}
fclose(fp);
//把第43条记录中的整数值由43修改为143,并把它写入第43条记录中的字符串
fp = fopen("record.dat","r+");
fseek(fp,43*sizeof(record),SEEK_SET);
fread(&record,sizeof(record),1,fp);
record.integer = 143;
sprintf(record.string,"RECORD-%d",record.integer);
fseek(fp,43*sizeof(record),SEEK_SET);
fwrite(&record,sizeof(record),1,fp);
fclose(fp);
//把这些记录映射到内存中,然后访问第43条记录,把它的整数值修改为243
f = open("record.dat",O_RDWR);
mapped = (RECORD*)mmap(0,NRECORDS*sizeof(record),PROT_READ|PROT_WRITE,MAP_SHARED,f,0);
mapped[43].integer = 243;
sprintf(mapped[43].string,"RECORD-%d",mapped[43].integer);
msync((void*)mapped,NRECORDS*sizeof(record),MS_ASYNC);
munmap((void*)mapped,NRECORDS*sizeof(record));
close(f);
exit(0);
return 0;
}
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char**argv)
{
int fd; //文件描述符
char* mapped, *p;
int flength = 1024;
void *start_addr = 0;
if (argc < 2)
{
printf("argc less than 2\n");
exit(-1);
}
fd = open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
flength = lseek(fd,1,SEEK_END);
write(fd,"\0",1); //在文件末尾添加一个空字符
lseek(fd,0,SEEK_SET);
mapped = mmap(start_addr,flength,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
printf("%s\n",mapped);
while((p=strstr(mapped,"test")))
{
memcpy(p,"map",3);
p+=3;
}
munmap((void*)mapped,flength);
close(fd);
return 0;
}
Linux - 内存映射