在述说文件映射的概念时,不可防止的要牵扯到虚存(SVR4的VM).实际上,文件映射是虚存的中心概念,文件映射一方面给用户提供了一组举措,就像用户将文件映射到自己地址空间的某个部份,使用简单的显存访问指令读写文件;另一方面,它也可以用于内核的基本组织模式,在这些模式种,内核将整个地址空间视为例如文件之类的一组不同对象的映射.中的传统文件访问方法是,首先用open系统调用打开文件,之后使用read,write以及lseek等调用进行次序或则随后的I/O.这些方法是特别低效的,每一次I/O操作都须要一次系统调用.另外,倘若若干个进程访问同一个文件,每位进程都要在自己的地址空间维护一个副本,浪费了显存空间.而假如才能通过一定的机制将页面映射到进程的地址空间中,也就是说首先通过简单的形成个别显存管理数据结构完成映射的创建.当进程访问页面时形成一个缺页中断,内核将页面读入显存而且更新页表指向该页面.并且这些方法十分方以便同一副本的共享.
VM是面向对象的方式设计的,这儿的对象是指显存对象:显存对象是一个软件具象的概念,它描述显存区与后备储存之间的映射.系统可以使用多种类型的后备储存,例如交换空间,本地或则远程文件以及帧缓存等等.VM系统对它们统一处理,采用同一操作集操作,例如读取页面或则回写页面等.每种不同的后备储存都可以用不同的方式实现这种操作.这样,系统定义了一套统一的插口,每种后备储存给出自己的实现方式.这样,进程的地址空间就被视为一组映射到不同数据对象上的的映射组成.所有的有效地址就是这些映射到数据对象上的地址.这种对象为映射它的页面提供了持久性的后备储存.映射促使用户可以直接轮询这种对象.
值得提出的是,VM体系结构独立于Unix系统,所有的Unix系统语义,如正文,数据及堆栈区都可以建构在基本VM系统之上.同时,VM体系结构也是独立于储存管理的,储存管理是由操作系统施行的,如:到底采取哪些样的对换和恳求调页算法,到底是采取分段还是分页机制进行储存管理,到底是怎样将虚拟地址转换成为化学地址等等(Linux中是一种叫ThreeLevelPageTable的机制),这种都与显存对象的概念无关.
下边介绍Linux中VM的实现.
一个进程应当包括一个mm_struct(memorymanagestruct),该结构是进程虚拟地址空间的具象描述,上面包括了进程虚拟空间的一些管理信息:start_code,end_code,start_data,end_data,start_brk,end_brk等等信息.另外,也有一个指向进程虚存区表(vm_area_struct:virtualmemoryarea)的表针,该链是根据虚拟地址的下降次序排列的.在Linux进程的地址空间被分作许多区(vma),每位区(vma)都对应虚拟地址空间上一段连续的区域,vma是可以被共享和保护的独立实体,这儿的vma就是上面提及的显存对象.下边是vm_area_struct的结构,其中,前半部份是公共的,与类型无关的一些数据成员,如:指向mm_struct的表针,地址范围等等,后半部份则是与类型相关的成员,其中最重要的是一个指向vm_operation_struct向量表的表针vm_ops,vm_pos向量表是一组虚函数,定义了与vma类型无关的插口.每一个特定的泛型,即每种vma类型都必须在向量表中实现这种操作.这儿包括了:open,close,unmap,protect,sync,nopage,wppage,swapout这种操作.
structvm_area_struct{
/*公共的,与vma类型无关的*/
structmm_struct*vm_mm;
unsignedlongvm_start;
unsignedlongvm_end;
structvm_area_struct*vm_next;
pgprot_tvm_page_prot;
unsignedlongvm_flags;
shortvm_avl_height;
structvm_area_struct*vm_avl_left;
structvm_area_struct*vm_avl_right;
structvm_area_struct*vm_next_share;
structvm_area_struct**vm_pprev_share;
/*与类型相关的*/
structvm_operations_struct*vm_ops;
unsignedlongvm_pgoff;
structfile*vm_file;
unsignedlongvm_raend;
void*vm_private_data;
};
vm_ops:open,close,no_page,swapin,swapout……
介绍完VM的基本概念后,我们可以述说mmap和munmap系统调用了.mmap调用实际上就是一个显存对象vma的创建过程,mmap的调用格式是:
void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset);
其中start是映射地址,length是映射厚度,倘若flags的MAP_FIXED不被置位,则该参数一般被忽视,而查找进程地址空间中第一个宽度符合的空闲区域;Fd是映射文件的文件句柄,offset是映射文件中的偏斜地址;prot是映射保护权限,可以是PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE,flags则是指映射类型,可以是MAP_FIXED,MAP_PRIVATE,MAP_SHARED,该参数必须被指定为MAP_PRIVATE和MAP_SHARED其中之一,MAP_PRIVATE是创建一个写时拷贝映射(copy-on-write),也就是说假如有多个进程同时映射到一个文件上,映射构建时只是共享同样的储存页面,并且某进程试图更改页面内容,则复制一个副本给该进程私用,它的任何更改对其它进程都不可见.而MAP_SHARED则无论更改与否都使用同一副本,任何进程对页面的更改对其它进程都是可见的.
mmap系统调用的实现过程是:
1.先通过文件系统定位要映射的文件;
2.权限检测,映射的权限不会超过文件打开的形式,也就是说假如文件是以只读方法打开,这么则不容许构建一个可写映射;
3.创建一个vma对象,并对之进行初始化;
4.调用映射文件的mmap函数,其主要工作是给vm_ops向量表形参;
5.把该vma链入该进程的vma数组中,假如可以和前后的vma合并则合并;
6.若果是要求VM_LOCKED(映射区不被换出)形式映射,则发出缺页恳求,把映射页面读入显存中.
munmap(void*start,size_tlength):
该调用可以看作是mmap的一个逆过程.它将进程中从start开始length宽度的一段区域的映射关掉,假如该区域不是正好对应一个vma,则有可能会分割几个或几个vma.
msync(void*start,size_tlength,intflags):
把映射区域的更改回讲到后备储存中.由于munmap时并不保证页面回写,倘若不调用msync,这么有可能在munmap后遗失对映射区的更改.其中flags可以是MS_SYNC,MS_ASYNC,MS_INVALIDATE,MS_SYNC要求回写完成后才返回,MS_ASYNC发出回写恳求后立刻返回,MS_INVALIDATE使用回写的内容更新该文件的其它映射.该系统调用是通过调用映射文件的sync函数来完成工作的.
brk(void*end_data_segement):
将进程的数据段扩充到end_data_segement指定的地址,该系统调用和mmap的实现方法非常相像,同样是形成一个vma,之后指定其属性.不过在此之前须要做一些合法性检测,例如该地址是否小于mm->end_code,end_data_segement和mm->brk之间是否还存在其它vma等等.通过brk形成的vma映射的文件为空,这和匿名映射形成的vma相像,关于匿名映射不做进一步介绍.库函数malloc就是通过brk实现的.
Linux提供了显存映射函数mmap,它把文件内容映射到一段显存上(确切说是虚拟显存上),通过对这段显存的读取和更改,实现对文件的读取和更改,先来看一下mmap的函数申明:
头文件:
原型:void*mmap(void*addr,size_tlength,intprot,intflags,intfd,off_toffsize);
返回值:成功则返回映射区起始地址,失败则返回MAP_FAILED(-1).
参数:
addr:指定映射的起始地址,一般设为NULL,由系统指定.
length:将文件的多大宽度映射到显存.
prot:映射区的保护方法,可以是:
PROT_EXEC:映射区可被执行.
PROT_READ:映射区可被读取.
PROT_WRITE:映射区可被写入.
PROT_NONE:映射区不能存取.
flags:映射区的特点,可以是:
MAP_SHARED:对映射区域的写入数据会复制回文件,且容许其他映射该文件的进程共享.
MAP_PRIVATE:对映射区域的写入操作会形成一个映射的复制(copy-on-write),对此区域所做的更改不会写回原文件.
据悉还有其他几个flags不很常用,具体查看linuxC函数说明.
fd:由open返回的文件描述符,代表要映射的文件.
offset:以文件开始处的偏斜量,必须是分页大小的整数倍,一般为0,表示从文件头开始映射.
下边说一下显存映射的步骤:
用open系统调用打开文件,并返回描述符fd.
用mmap构建显存映射,并返回映射首地址表针start.
对映射(文件)进行各类操作,显示(printf),更改(sprintf).
用munmap(void*start,size_tlenght)关掉显存映射.
用close系统调用关掉文件fd.
注意事项:
在更改映射的文件时,只能在原宽度上更改,不能降低文件厚度,由于显存是早已分配好的.
Linux-mmap函数介绍
mmap函数是unix/linux下的系统调用,来看《UnixNetwordprogramming》卷二12.2节对mmap的介绍:
ThemmapfunctionmapseitherafileoraPosixsharedmemoryobjectintotheaddressspaceofaprocess.Weusethisfunctionforthreepurposes:
1.witharegularfiletoprovidememory-mappedI/O
2.withspecialfilestoprovideanonymousmemorymappings
3.withshm_opentoprovidePosixsharedmemorybetweenunrelatedprocesses
mmap系统调用并不是完全为了用于共享显存而设计的。它本身提供了不同于通常对普通文件的访问方法,进程可以像读写显存一样对普通文件的操作。而Posix或系统V的共享显存IPC则纯粹用于共享目的,其实mmap()实现共享显存也是其主要应用之一。
mmap系统调用促使进程之间通过映射同一个普通文件实现共享显存。普通文件被映射到进程地址空间后,进程可以像访问普通显存一样对文件进行访问,毋须再调用read(),write()等操作。
我们的程序中大量运用了mmap,用到的正是mmap的这些“像访问普通显存一样对文件进行访问”的功能。实践证明,当要对一个文件频繁的进行访问,但是表针来回联通时,调用mmap比用常规的方式快好多。
来瞧瞧mmap的定义:
void*mmap(void*addr,size_tlen,intprot,intflags,intfd,off_toffset);
参数fd为正式映射到进程空间的文件描述字,通常由open()返回mmap linux 文件,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名深度linux,防止了文件的创建及打开,很似乎只能用于具有亲缘关系的进程间通讯)。
len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot参数指定共享显存的访问权限。可取如下几个值的或:PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。
flags由以下几个常值指定:MAP_SHARED,MAP_PRIVATE,MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必选其二,而MAP_FIXED则不推荐使用。
倘若指定为MAP_SHARED,则对映射的显存所做的更改同样影响到文件。若果是MAP_PRIVATE,则对映射的显存所做的更改仅对该进程可见,对文件没有影响。
offset参数通常设为0,表示从文件头开始映射。
参数addr指定文件应被映射到进程空间的起始地址,通常被指定一个空表针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。
最后,举个反例来结束本节。4.2节说过,Fileinformation字段是以二补码的方式写进一个叫inforindex的文件中。这么,当要访问Fileinformation链表时,代码类似这样:
structstatst;
charbuffer=”inforindex”;
Fileinformation*_fileinfoIndexptr=NULL;
if(stat(buffer,&st)
{
fprintf(stderr,"errortostat%sn",buffer);
exit(-1);
}
//mmaptheinforindexto_fileinfoIndexptr
intfd=open(buffer,O_RDONLY);
if(fd
{
printf("errortoopen%sn",buffer);
exit(-1);
}
_fileinfoIndexptr=(Fileinformation*)mmap(NULL,st.st_size,PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED==_fileinfoIndexptr)
{
printf("errortommap%sn",buffer);
close(fd);
exit(-1);
}
close(fd);
下边这个事例显示了把文件映射到显存的方式
源代码是:
/************关于本文档********************************************
*filename:mmap.c
*purpose:说明调用mmap把文件映射到显存的方式
*wroteby:zhoulifa()周立发()
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2008-01-2718:59广州下雪天,听说是多年不遇
*Note:任何人可以任意复制代码并运用这种文档,其实包括你的商业用途
*但请遵守GPL
*Thanksto:
*Ubuntu本程序在Ubuntu7.10系统上测试完全正常
*我一般通过google搜索发觉许多有用的资料
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*科技站在巨人的右臂上进步更快!谢谢有开源高手的贡献!
*********************************************************************/
#include/*formmapandmunmap*/
#include/*foropen*/
#include/*foropen*/
#include/*foropen*/
#include/*forlseekandwrite*/
#include
intmain(intargc,char**argv)
{
intfd;
char*mapped_mem,*p;
intflength=1024;
void*start_addr=0;
fd=open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
flength=lseek(fd,1,SEEK_END);
write(fd,"",1);/*在文件最后添加一个空字符,便于下边printf正常工作*/
lseek(fd,0,SEEK_SET);
mapped_mem=mmap(start_addr,flength,PROT_READ,//容许读
MAP_PRIVATE,//不容许其它进程访问此显存区域
fd,0);
/*使用映射区域.*/
printf("%sn",mapped_mem);/*为了保证这儿工作正常mmap linux 文件,参数传递的文件名最好是一个文本文件*/
close(fd);
munmap(mapped_mem,flength);
return0;
}
编译运行此程序:
gcc-Wallmmap.c
./a.outtext_filename
里面的方式由于用了PROT_READ,所以只能读取文件里的内容linux培训班,不能更改,倘若换成PROT_WRITE就可以更改文件的内容了。又因为用了MAAP_PRIVATE所以只能此进程使用此显存区域,倘若换成MAP_SHARED,则可以被其它进程访问,例如下边的:
#include/*formmapandmunmap*/
#include/*foropen*/
#include/*foropen*/
#include/*foropen*/
#include/*forlseekandwrite*/
#include
#include/*formemcpy*/
intmain(intargc,char**argv)
{
intfd;
char*mapped_mem,*p;
intflength=1024;
void*start_addr=0;
fd=open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
flength=lseek(fd,1,SEEK_END);
write(fd,"",1);/*在文件最后添加一个空字符,便于下边printf正常工作*/
lseek(fd,0,SEEK_SET);
start_addr=0x80000;
mapped_mem=mmap(start_addr,flength,PROT_READ|PROT_WRITE,//容许写入
MAP_SHARED,//容许其它进程访问此显存区域
fd,0);
/*使用映射区域.*/
printf("%sn",mapped_mem);/*为了保证这儿工作正常,参数传递的文件名最好是一个文本文*/
while((p=strstr(mapped_mem,"Hello"))){/*此处来更改文件内容*/
memcpy(p,"Linux",5);
p+=5;
}
close(fd);
munmap(mapped_mem,flength);
return0;
}
man-ammap看更详尽的信息