Linux虚拟存储系统

Linux虚拟存储系统 

2012-07-19 22:25:27|  分类: Linux |  标签:linux  存储系统  虚拟内存  |字号订阅linux

 
 

 

 

    Linux为每个进程单独维护了一个单独的虚拟地址空间,形式如图所示。面试

    其中内核虚拟存储器包含内核中的代码和数据结构。内核虚拟存储器中的某些区域被映射到全部进程共享的物理页面。例如每一个进程共享内核的代码全局数据结构数据结构

    内核虚拟存储器的其余区域包含每一个进程都不相同的数据,好比页表,内核在进程上下文中执行代码时用到的栈。tcp

 

    Linux将虚拟存储器组织成一些区域(也叫作段)的集合。一个区域就是已经存在的(已分配的)虚拟存储器的连续片,这些页是以某种方式相关联的。好比说代码段,数据段,堆,共享库段以及用户栈都是不一样的区域。每一个存在的虚拟页面都保存在某个区域中,而不属于某个区域的虚拟页是不存在的,而且不能被进程使用。ide

    在Linux系统中发生缺页异常时,致使控制转移到内核的异常处理程序,这个处理程序将执行如下步骤:函数

  1. 检查虚拟地址A是合法的吗?换句话说就是A是否是在某个区域内?因此缺页异常处理程序首先搜索区域结构的链表,把A和每一个区域结构中的vm_start和vm_end作比较。若是这个地址是不合法的,那么缺页处理程序就会发出一个段错误,从而终止这个进程。
  2. 检查存储器访问是否合法?就是说,该进程是否有读,或者写或执行这个区域内页面的权限。若是试图进行访问是不合法的,那么缺页处理程序会触发一个保护异常,从而终止这个进程。
  3. 若是不知足条件1和2,那么此刻,内核知道了这个缺页是因为对合法的虚拟地址进行合法的访问操做形成的。那个内核就选择牺牲一个页面,若是牺牲的这个页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回的时候,CPU将冲洗启动引发缺页的那个指令,这条指令将再次发送地址A到MMU。此次MMU就能正常的翻译A,而不会产生缺页中断了。

 

存储器映射

    Linux经过将虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容,这个过程就叫作存储器映射ui

    一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫作交换空间。须要意识到的很重要的一点是,在任什么时候候,交换空间都限制着当前运行着的进程可以分配的虚拟页面的总数。spa

共享对象翻译

    存储器映射的概念其实来源于一个聪明的发现:若是虚拟存储器系统能够集成到传统的文件系统中,那么就可以提供一种简单而高效的把程序和数据加载到存储器中的方法。orm

    进程这一抽象的概念可以为每一个进程提供本身私有的虚拟地址空间,能够避免受其余进程的错误读写。不过有许多进程有一样的只读文本区域,并且许多程序须要访问只读运行时库代码的相同拷贝。好比每一个C程序都须要来自标准C库的诸如printf这样的函数。那么若是每一个进程都在物理存储器中保持这些经常使用代码的复制拷贝,那就是一种极大的浪费。因此,咱们须要存储器映射给咱们提供一种清晰的机制,来控制多个进程如何共享对象。

   一个对象能够被映射到虚拟存储器的一个区域,要么做为共享对象,要么做为私有对象。若是一个进程将一个共享对象映射到它的虚拟之地空间的一个区域内,那么这个进程对这个区域的任何写操做,对于那些也把这个共享对象映射到他们虚拟存储器的其余的进程来讲也是可见的。并且这些变化会反应在磁盘上的原始对象中。

    对一个映射到私有对象的区域作改变的时候,对于其余进程来讲是不可见的,而且进程对这个区域所作的任何写操做都不会反应在磁盘上的对象中。

fork函数

   理解了虚拟存储器和存储器映射以后,咱们就能够清晰的知道fork函数是如何建立一个带有本身独立虚拟地址空间的新进程的了。

    当fork函数被当前进程(也就是父进程)调用的时候,内核为新进程(子进程)建立各类数据结构,并分配给它一个惟一的PID。为了给这个新进程建立虚拟存储器,它建立了当前进程的mm_struct结构、区域结构、和页表的原样拷贝。它将两个进程的页面都标记为只读,并将两个进程中的每一个区域结构都标记为私有的写时拷贝。

    当fork在新进程中返回的时候,新进程如今的虚拟存储器恰好和调用fork时存在的虚拟存储器相同。当这父子进程中的任何一个后来进行写操做的时候,写时拷贝机制就会建立新页面,所以,子进程对任何数据的操做(好比更改数据大小),对父进程是没有影响的。

execve函数

    虚拟存储器和存储器映射在将程序加载到存储器到的过程当中扮演者重要的角色。假设当前的进程中执行了以下的调用: execve(“a.out”, NULL, NULL);

execve函数在当前的进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效的替代了当前程序。加载并运行a.out须要如下几个步骤:

  1. 删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构。
  2. 映射私有区域:为新程序的文本,数据,bss,和栈区建立新的区域结构。全部这些新的区域都是私有的,写时拷贝的。文本和数据区域被映射为a.out文件中的文本和数据区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为0.
  3. 映射共享区域:若是a.out程序和共享对象(或目标)连接,好比标准C库libc.so,那么这些对象都是动态连接到这个程序的,而后在映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器(PC):exevce作的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。
mmap函数(用户级存储映射)
    Unix进程可使用mmap函数来建立新的虚拟存储器区域,并将对象映射到这些区域中。
#include <unistd.h> 
#include <sys/mman.h> 
void *mmap(void *start, size_t length, int prot, int flag 
           int fd, off_t offset); 

mmap函数要求内核建立一个新的虚拟存储器区域,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片chunk映射到这个新的区域。连续的对象的片的大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,一般被定义为NULL(表明由内核来决定)。

参数prot包含描述新映射的虚拟存储器区域的访问权限位(在相应结构中的vm_prot位)

  • PROT_EXEC:这个区域内的页面由能够被CPU执行的指令组成。
  • PROT_READ:这个区域内的页面可读。
  • PROT_WRITE:这个区域内的页面可写。
  • PROT_NONE:这个区域内的页面不能被访问。

参数flag由描述被映射对象类型的位组成。若是设置了MAP_ANON标记为,那么被映射的对象就是一个匿名对象,而相应的虚拟页面是请求二进制零的。MAP_PRIVATE表示被映射的对象是一个私有的,写时拷贝的对象。而MAP_SHARED表示是一个共享的对象。

能够写一个C程序,使用mmap函数将一个任意大小的磁盘文件拷贝到stdout。

int main(int argc, char **argv) 
{ 
  struct stat stat; 
  int fd; 
  if(argc != 2) 
  { 
    printf("Usage Wrong"); 
    exit(0); 
  } 
  fd = Open(argv[1], O_RDONLY, 0); 
  fstat(fd, &stat); 
  char * bufp; 
  bufp = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, 
              fd, 0); 
  Write(1, bufp, stat.st_size); 
  exit(0); 
}