Linux通过将一个虚拟存储器区域与磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容,这个过程称为存储器映射.虚拟存储器可以映射到两种类型对象中的一种:
1):文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件.文件区被分成页大小的片,每一片包含一个虚拟页面的初始化内容.因为按需进行页面调度, 所以这些虚拟页面没有实际交换进入物理存储器,直到CPU第一次引用到该页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内),如果区域比文件区要大,那么就用0来填充这个区域余下的部分.
2):匿名文件:一个区域页可以映射到一个匿名文件,匿名文件是由内核创建,包含的全是二进制零,CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理存储器中找到一个合适的牺牲页面,如果已经被修改,就写回磁盘,用二进制的零覆盖牺牲页面并更新页表.
共享区域
进程都有自己私有的虚拟地址空间,可以防止其它进程修改其数据.但是,许多进程都有相同的只读文本区域,例如内核代码,数据等,如果每个进程运行时都拷贝一份,那就是极端的浪费了.因此,存储器映射正好解决了这个问题,当一个进程将一个共享对象映射到它的虚拟存储器的一个区域时,那么这个进程对这个区域的任何写操作,对于其它把这个共享对象页映射到自己的虚拟存储器也是可见的.
私有区域
对于一个映射到私有对象的区域做的改变,对于其它进程来说是不可见的,并且进程对这个区域所做的任何写操作都不会反映在磁盘上的对象中.私有对象使用一种叫做写时拷贝的技术被映射到虚拟存储器中的.例如,进程1和进程2都将一个私有对象映射到它们的虚拟存储器的不同区域,但是共享的这个对象的同一个物理拷贝,如果进程1和进程2不对这个私有对象进行修改,那么它们就相当于共享了这个私有对象在物理存储器中的拷贝,但是当某个进程要修改这个对象时,这个写操作就会触发一个保护故障,当故障处理程序检测到保护异常是由于进程试图写私有的写时拷贝区域中的一个页面而引起时,它就会在物理存储器中创建这个页面的一个新的拷贝,更新页表条目指向这个新的拷贝,然后恢复这个页面的可写权限.当故障处理程序结束时,CPU重新执行这个这个写操作就可以正常执行了.通过延迟私有对象的拷贝直到最后可能的时刻,写时拷贝最充分地使用了稀有的物理存储器.
fork函数
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新的进程创建虚拟存储器,它将当前进程的mm_struct,区域结构,页表都拷贝一份,它将两个进程中的每个页面标为只读,并将两个进程中的区域结构都标为写时拷贝.当fork函数返回时,新进程的虚拟存储器和调用fork时存在的虚拟存储器相同,当这两个进程中的任何一个执行写操作时,写时拷贝机制就会创建新的页面.
execve函数
当当前进程执行execve("a.out",NULL.NULL)时,用a.out程序有效取代了当前程序,加载a.out需要以下几个步骤:
1.删除已存在的用户区域:删除当前进程虚拟地址的用户部分的已经存在的区域结构.
2.映射私有区域:为新程序的文本,数据,bss和栈区域创建新的区域结构.所有这些新的区域都是私有的,写时拷贝的,文本和数据区域被映射到a.out文件中的文本和数据区.bss区域是请求二进制零,映射到匿名文件.
3.映射共享区域:如果a.out程序与共享对象链接,比如标准库中的函数,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址空间中的共享区域内.
4.设置程序计数器:execve做的最后一件事就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点.下一次调度这个进程时,它将从这个入口点开始执行.
使用mmap函数的用户级别存储器映射
进程可以使用mmap函数来创建新的虚拟存储器域,并将对象映射到这些区域中.
#include <unistd.h> #include <sys/mman.h> void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset); 若执行成功则为指向映射区域的指针,若出错则为MAP_FAILED(-1).
mmap函数要求内核创建一个新的虚拟存储器区域,最好是从地址starrt开始的一个区域,并将文件描述符fd指定的对象的一个连续的片映射到这个新的区域,连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始,start通常设置为NULL.
参数prot包含描述新映射的虚拟存储器区域的访问权限(在相应区域结构中的vm_prot位)
PROT_EXEC:这个区域中的页面可以被CPU执行的指令组成.
PROT_READ:这个区域内的页面可读;
PROT_WRITE:这个区域中的页面可写.
PROT_NONE:这个区域内的页面不能被访问.
参数flags由描述被映射对象类型的位组成,如果设置了MAP_ANON标记位.那么被映射的对象就是一个匿名对象,而对应的虚拟页面是请求二进制零的,MAP_PRIVATE表示被映射的对象是一个私有的,写时拷贝的对象,而MAP_SHARED表示是一个共享对象.
munmap函数删除虚拟存储器的区域:
#include <unistd.h> #include <sys/mman.h> int munmap(void *start,size_t length) munmap删除虚拟地址从start开始的, 由接下来length字节组成的区域.