Linux虚拟存储系统

Linux虚拟存储系统

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

image

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

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

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

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

1. 检查虚拟地址A是合法的吗?换句话说就是A是不是在某个区域内?所以缺页异常处理程序首先搜索区域结构的链表,把A和每个区域结构中的vm_start和vm_end做比较。(涉及一点点内核知识,等会上一个图)。

2. 检查存储器访问是否合法?就是说,该进程是否有读,或者写或执行这个区域内页面的权限。如果试图进行访问是不合法的,那么缺页处理程序会触发一个保护异常,从而终止这个进程。

3. 此刻,内核知道了这个缺页是由于对合法的虚拟地址进行合法的访问操作造成的。那个内核就选择牺牲一个页面,如果牺牲的这个页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回的时候,CPU将冲洗启动引起缺页的那个指令,这条指令将再次发送地址A到MMU。这次MMU就能正常的翻译A,而不会产生缺页中断了。

1 存储器映射

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

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

    进程这一抽象的概念能够为每个进程提供自己私有的虚拟地址空间,可以避免受其他进程的错误读写。不过有许多进程有同样的只读文本区域,而且许多程序需要访问只读运行时库代码的相同拷贝。比如每个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);
}

 

转载于:https://www.cnblogs.com/nathan-1988/archive/2012/07/18/2598422.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值