linux中VM虚拟内存区域及mmap方法实现
html
文件映射是虚存的中心概念, 文件映射一方面给用户提供了一组措施, 好似用户将文件映射到本身地址空间的某个部分, 使用简单的内存访问指令读写文件;另外一方面, 它也能够用于内核的基本组织模式, 在这种模式种, 内核将整个地址空间视为诸如文件之类的一组不一样对象的映射. 中的传统文件访问方式是, 首先用open系统调用打开文件, 而后使用read, write以及lseek等调用进行顺序或者随即的I/O. 这种方式是很是低效的, 每一次I/O操做都须要一次系统调用. 另外, 若是若干个进程访问同一个文件, 每一个进程都要在本身的地址空间维护一个副本, 浪费了内存空间. 而若是可以经过必定的机制将页面映射到进程的地址空间中, 也就是说首先经过简单的产生某些内存管理数据结构完成映射的建立. 当进程访问页面时产生一个缺页中断, 内核将页面读入内存而且更新页表指向该页面. 并且这种方式很是方便于同一副本的共享。node
VM是面向对象的方法设计的, 这里的对象是指内存对象: 内存对象是一个软件抽象的概念, 它描述内存区与后备存储之间的映射. 系统可使用多种类型的后备存储, 好比交换空间, 本地或者远程文件以及帧缓存等等. VM系统对它们统一处理, 采用同一操做集操做, 好比读取页面或者回写页面等. 每种不一样的后备存储均可以用不一样的方法实现这些操做. 这样, 系统定义了一套统一的接口, 每种后备存储给出本身的实现方法. 这样, 进程的地址空间就被视为一组映射到不一样数据对象上的的映射组成. 全部的有效地址就是那些映射到数据对象上的地址. 这些对象为映射它的页面提供了持久性的后备存储. 映射使得用户能够直接寻址这些对象。linux
一个进程的内存区域能够经过 /proc/pid/maps查看.pid为具体的进程号.api
08048000-0804f000 r-xp 00000000 08:01 573748 /sbin/rpc.statd #text数组
...............缓存
每一行的域为:数据结构
start_end perm offset major:minior inodeapp
start:该区域的起始虚拟地址ide
end:该区域的结束虚拟地址函数
perm:读、写和执行权限;表示对这个区域,容许进程作什么。这个域的最后一个字符要么是p 表示私有的。要么是s表示共享的。
offset:被映射部分在文件中的起始地址
major minor :主次设备号
inode:索引结点
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这些操做.
一、内核内部的虚拟内存管理系统经过应用程序的调用的mmap()的变量,生产vm_area_struct结构体,从而内存映射,而后初始化该结构体的域,最后才传递的过程。二、应用程序锁调用mmap()函数中使用的多数变量值由vma内核处理为vm_area_struct结构体形式,而后传递到设备驱动程序上。
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……
mmap调用实际上就是一个内存对象vma的建立过程, mmap的调用格式是:
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
其中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_t length):
该调用能够看做是mmap的一个逆过程. 它将进程中从start开始length长度的一段区域的映射关闭, 若是该区域不是刚好对应一个vma, 则有可能会分割几个或几个vma.
msync(void * start, size_t length, int flags):
把映射区域的修改回写到后备存储中. 由于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的函数声明:
头文件:
< unistd.h>
< sys/mman.h>
原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize);
返回值: 成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1).
参数:
addr: 指定映射的起始地址, 一般设为NULL, 由系统指定.
length: 将文件的多大长度映射到内存.
prot: 映射区的保护方式, 能够是:
PROT_EXEC: 映射区可被执行.
PROT_READ: 映射区可被读取.
PROT_WRITE: 映射区可被写入.
PROT_NONE: 映射区不能存取.
flags: 映射区的特性, 能够是:
MAP_SHARED: 对映射区域的写入数据会复制回文件, 且容许其余映射该文件的进程共享.
MAP_PRIVATE: 对映射区域的写入操做会产生一个映射的复制(copy-on-write), 对此区域所作的修改不会写回原文件.
此外还有其余几个flags不很经常使用, 具体查看linux C函数说明.
fd: 由open返回的文件描述符, 表明要映射的文件.
offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 一般为0, 表示从文件头开始映射.
下面说一下内存映射的步骤:
用open系统调用打开文件, 并返回描述符fd.
用mmap创建内存映射, 并返回映射首地址指针start.
对映射(文件)进行各类操做, 显示(printf), 修改(sprintf).
用munmap(void *start, size_t lenght)关闭内存映射.
用close系统调用关闭文件fd.
注意事项:
在修改映射的文件时, 只能在原长度上修改, 不能增长文件长度, 由于内存是已经分配好的.
Linux-mmap函数介绍(转)
mmap函数是unix/linux下的系统调用,来看《Unix Netword programming》卷二12.2节对mmap的介绍:
The mmap function maps either a file or a Posix shared memory object into the address space of a process.We use this function for three purposes:
1. with a regular file to provide memory-mapped I/O
2. with special files to provide anonymous memory mappings
3. with shm_open to provide Posix shared memory between unrelated processes
mmap系统调用并非彻底为了用于共享内存而设计的。它自己提供了不一样于通常对普通文件的访问方式,进程能够像读写内存同样对普通文件的操做。而 Posix或系统V的共享内存IPC则纯粹用于共享目的,固然mmap()实现共享内存也是其主要应用之一。
mmap系统调用使得进程之间经过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程能够像访问普通内存同样对文件进行访问,没必要再 调用read(),write()等操做。
咱们的程序中大量运用了mmap,用到的正是mmap的这种“像访问普通内存同样对文件进行访问”的功能。实践证实,当要对一个文件频繁的进行访问,而且 指针来回移动时,调用mmap比用常规的方法快不少。
来看看mmap的定义:
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
参数fd为即将映射到进程空间的文件描述字,通常由open()返回,同时,fd能够指定为-1,此时须指定flags参数中的MAP_ANON,代表进 行的是匿名映射(不涉及具体的文件名,避免了文件的建立及打开,很显然只能用于具备亲缘关系的进程间通讯)。
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指定文件应被映射到进程空间的起始地址,通常被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空 间的地址,进程可直接操做起始地址为该值的有效地址。
看看下面这个图(来自《Unix Netword programming》卷二12.2节),对mmap进一步加深印象:
这里再也不详细介绍mmap的参数,读者可参考mmap手册页或者《Unix Netword programming》卷二12.2节得到进一步的信息。
最后,举个例子来结束本节。4.2节说过,Fileinformation数组是以二进制的形式写进一个叫inforindex的文件中。那么,当要访问 Fileinformation数组时,代码相似这样:
struct stat st;
char buffer=” inforindex”;
Fileinformation *_fileinfoIndexptr = NULL;
if(stat(buffer,&st)<0)
{
fprintf(stderr,"error to stat %s\n",buffer);
exit(-1);
}
// mmap the inforindex to _fileinfoIndexptr
int fd=open(buffer, O_RDONLY);
if(fd<0)
{
printf("error to open %s\n",buffer);
exit(-1);
}
_fileinfoIndexptr = (Fileinformation*)mmap(NULL,st.st_size, PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == _fileinfoIndexptr)
{
printf("error to mmap %s\n",buffer);
close(fd);
exit(-1);
}
close(fd);
示例代码:http://zhoulifa.bokee.com/6614538.html
下面这个例子显示了把文件映射到内存的方法 源代码是: