linux下mmap()函数

linux下mmap()函数
http://apps.hi.baidu.com/share/detail/6087910
最近看完了Beginning Linux Programming的文件系统部分, 感觉还是有颇多收获, 对系统调用有了一个初步的概念, 同时也了解了标准I/O库和Linux系统调用函数的关系. 不过关于mmap的内存映射机制理解的不透彻, 毕竟书上讲的东西有限, 于是找到一篇解说的挺详细的文章, 学习和分享一下. 


mmap: memory map

在讲述文件映射的概念时, 不可避免的要牵涉到虚存(SVR 4的VM). 实际上, 文件映射是虚存的中心概念, 文件映射一方面给用户提供了一组措施, 用户将文件映射到自己地址空间的某个部分, 使用简单的内存访问指令读写文件;另一方面, 它也可以用于内核的基本组织模式, 在这种模式种, 内核将整个地址空间视为诸如文件之类的一组不同对象的映射中的传统文件访问方式是, 首先用open系统调用打开文件, 然后使用read, write以及lseek等调用进行顺序或者随即的I/O. 这种方式是非常低效的, 每一次I/O操作都需要一次系统调用. 另外, 如果若干个进程访问同一个文件, 每个进程都要在自己的地址空间维护一个副本, 浪费了内存空间. 而如果能够通过一定的机制将页面映射到进程的地址空间中, 也就是说首先通过简单的产生某些内存管理数据结构完成映射的创建. 当进程访问页面时产生一个缺页中断, 内核将页面读入内存并且更新页表指向该页面. 而且这种方式非常方便于同一副本的共享. 

VM是面向对象的方法设计的, 这里的对象是指内存对象: 内存对象是一个软件抽象的概念, 它描述内存区与后备存储之间的映射. 系统可以使用多种类型的后备存储, 比如交换空间, 本地或者远程文件以及帧缓存等等. VM系统对它们统一处理, 采用同一操作集操作, 比如读取页面或者回写页面等. 每种不同的后备存储都可以用不同的方法实现这些操作. 这样, 系统定义了一套统一的接口, 每种后备存储给出自己的实现方法. 这样, 进程的地址空间就被视为一组映射到不同数据对象上的的映射组成. 所有的有效地址就是那些映射到数据对象上的地址. 这些对象为映射它的页面提供了持久性的后备存储. 映射使得用户可以直接寻址这些对象. 

值得提出的是, VM体系结构独立于Unix系统, 所有的Unix系统语义, 如正文, 数据及堆栈区都可以建构在基本VM系统之上. 同时, VM体系结构也是独立于存储管理的, 存储管理是由操作系统实施的, 如: 究竟采取什么样的对换和请求调页算法, 究竟是采取分段还是分页机制进行存储管理, 究竟是如何将虚拟地址转换成为物理地址等等(Linux中是一种叫Three Level Page Table的机制), 这些都与内存对象的概念无关.


下面介绍Linux中VM的实现. 

一个进程应该包括一个mm_struct(memory manage struct), 该结构是进程虚拟地址空间的抽象描述, 里面包括了进程虚拟空间的一些管理信息: start_code, end_code, start_data, end_data, start_brk, end_brk等等信息. 另外, 也有一个指向进程虚存区表(vm_area_struct: virtual memory area)的指针, 该链是按照虚拟地址的增长顺序排列的. 在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这些操作. 

struct vm_area_struct { 

/*公共的, 与vma类型无关的 */ 

struct mm_struct * vm_mm; 
unsigned long vm_start; 
unsigned long vm_end; 
struct vm_area_struct *vm_next; 
pgprot_t vm_page_prot; 
unsigned long vm_flags; 
short vm_avl_height; 
struct vm_area_struct * vm_avl_left; 
struct vm_area_struct * vm_avl_right; 
struct vm_area_struct *vm_next_share; 
struct vm_area_struct **vm_pprev_share; 

/* 与类型相关的 */ 

struct vm_operations_struct * vm_ops; 
unsigned long vm_pgoff; 
struct file * vm_file; 
unsigned long vm_raend; 
void * vm_private_data;
}; 
vm_ops: open, close, no_page, swapin, swapout……

//************************************************

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
//start 指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回
//参数length代表将文件中多大的部分对应到内存。
//参数prot代表映射区域的保护方式
//fd文件描述符
//参数offset为文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小
//的整数倍,映射文件的起动位移量受系统虚存页长度的限制,那么如果映射区的长度不是页长度的整数
//倍时,将如何呢?假定文件长12字节,系统页长为512字节,则系统通常提供512字节的映射区,其中
//后500字节被设为0。可以修改这500字节,但任何变动都不会在文件中反映出来

//若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1)
内核空间调用

int (*mmap) (struct file *filp, struct vm_area_struct *vma);


4.mmap执行的顺序
      a.在用户进程创建一个vma区域
      b.驱动程序获得页
      c.将获得的页分配给vma区域

5.下面说一下内存映射的步骤:

用open系统调用打开文件, 并返回描述符fd. 
用mmap建立内存映射, 并返回映射首地址指针start. 
对映射(文件)进行各种操作, 显示(printf), 修改(sprintf). 
用munmap(void *start, size_t lenght)关闭内存映射. 
用close系统调用关闭文件fd. 
看了这么多知道了,mmap是linux下用来给特定的驱动文件分配一个虚拟内存页的操作,其目的和以前没什么两样,就是来把底层的数据读到用户空间来。而以前是用read和write等操作,现在是先用mmap分配一个虚拟内存页并返回一个起始地址,然后再对该虚拟内存地址操作就相当于对文件操作了。这样做只是为了提高数据传输速率和规模。(初步理解)

你可能感兴趣的:(Linux)