为你解析Linux虚拟存储管理

来源:互联网 发布:mac桌面两个窗口 编辑:程序博客网 时间:2024/06/06 20:47

Linux操作系统是一种多用户多任务、支持多种平台的开源的类Unix操作系统,其支持多种平台,在服务端可与其它商用类Unix系统媲美,在客户端则向Windows系列发出了强有力的挑战。自Linux诞生以来,发展迅猛,已经受到了全球开源社区和许多商业科技巨头的大力支持以及政府、教育机构、科研单位的重视。本文介绍了Linux虚拟存储管理技术的特点,并结合操作系统原理和Linux内核源码,通过分析虚拟存储管理所需的主要数据结构及其相互关系,来更深入地了解Linux虚拟存储管理机制。本文所援引的Linux内核源代码版本为1.0。

  Linux虚拟存储管理概述

  虚拟存储器

  在实存储器的管理模式中,要求作业在运行前全部装入内存,之后就一直驻留在内存中直到运行结束,其中某些程序并没有一直处于运行状态,却长期占用着内存资源,从而降低了内存的利用率,为此,引入了虚拟存储器。

  虚拟存储器并不以物理的方式存在,而是从逻辑上对内存容量进行扩充,提供了一个比真实内存空间大得多的逻辑地址空间的逻辑存储器。虚拟存储技术把用户地址空间和实际的存储空间区分开,在程序运行时通过动态重定位的地址映射机制将逻辑地址转换为物理地址。

  所谓的“动态重定位”是指在目标程序执行过程中,在CPU访问内存之前,由硬件地址映射机构来完成将要访问的指令或数据的逻辑地址向内存的物理地址的转换。由于这种地址转换是在程序执行期间随着每条指令的数据访问自动连续地进行,所以称为“动态重定向”。

  在386保护模式下,Linux虚拟存储管理可以提供的逻辑地址空间为4GB。Linux的全局描述符表定义了如何分配逻辑地址空间:0GB – 3GB分配为用户空间,用户进程可以直接访问;3GB – 4GB分配为内核空间,用户进程不能访问。

  段页式存储管理

  Linux虚拟存储管理中的内存管理技术采用的是段页式虚存技术。它将一个进程中的程序、数据、堆栈分成若干“段”来处理,每段有一个8字节的段描述符,指出该段的起始地址、长度和存取权限等信息,这些段描述符的集合构成段描述符表,通过一个寄存器指出该表的起始位置。为了便于段长的动态变化,每段分为若干页,将需要的内容以页面为单位调入内存的物理块中,暂不执行的页面仍留在外存,以保证比实际内存容量需求更大的进程能够正常使用内存。

  Linux的分段机制

  Linux虚拟存储管理的分段机制就是将线性地址空间分段,利用这些段来存储代码和数据,通过对段的保护来提供一种对数据或代码的保护。根据每个段的作用和存储内容的不同,分为三类进程段:代码段、数据段和堆栈段;两类系统段:TSS段(任务状态段)和LDT段(局部描述符表段)。

  在保护模式下,逻辑地址空间可达4GB。从逻辑地址到线性地址的转换由分段机制管理。段寄存器CS、DS、ES、SS、FS或GS标识一个段。这些段寄存器作为段选择器,用来选择该段的描述符。

  进程使用的是48位的逻辑地址,其中高16位是段选择符,低32位是段内的偏移量。通过段选择符在GDT(全局描述符表)或LDT(局部描述符表)中索引相应的段描述符,以得到该段的基地址,再加上偏移量得到逻辑地址对应的线性地址。然后通过分页地址的转换,将线性地址转换为物理地址,最后通过物理地址访问内存。

  如图1所示,为分段逻辑地址到线性地址的转换图。

 

分段逻辑地址到线性地址的转换图

 

  图 1 分段逻辑地址映射到线性地址

  Linux的分页机制

  分页机制是在段机制之后进行的,它进一步将线性地址转换为物理地址。Linux虚拟存储管理一般使用4K字节大小的页(PAGE_SIZE,include/linux/page.h,通过修改PAGE_SHIFT定义的左移位数来修改页面大小),且每页的起始地址都被4K整除。因此,Linux把4GB的线性地址空间划分为1M个页面,采用了两级表结构。

  两级表的第一级表称为页目录,存储在一个4K字节的页中,页目录表共有1K个表项,每个表项为4个字节,线性地址最高的10位(22-31位)用来产生第一级表索引,由该索引得到的表项中的内容定位了二级表中的一个表的地址,即下级页表所在的内存块号。

  第二级表称为页表,存储在一个4K字节页中,它包含了1K字节的表项,每个表项包含了一个页的物理地址。二级页表由线性地址的中间10位(12-21位)进行索引,定位页表表项,获得页的物理地址。页物理地址的高20位与线性地址的低12位形成最后的物理地址。

  如图2所示,为两级页表的地址转换图。

 

两级页表的地址转换图

 

  图 2 两级页表转换

  Linux进程与段页式管理

  每当启动一个新的进程,Linux都为其创建一个进程控制块(task_struct, include/linux/sched.h)。在创建过程中,每个进程(根据需要)创建并初始化新页目录,设置页目录基地址寄存器,在GDT中添加进程对应的TSS项和LDT项,创建并初始化该进程的LDT。

  Linux虚拟存储管理采用“按需调页”的原则来分配内存页面,执行进程的页面总会在外存与内存之间不断交换,从而避免页表过多占用存储空间。创建一个进程时页面分配的情况大致是这样的:进程控制块(1页),内存态堆栈(1页),页目录(1页),页表(需要的n页)。在进程以后执行的执行中,再根据需要逐渐分配更多的内存页面。

  Linux交换空间

  交换空间是在外存中开辟一定的空间来临时存放从内存中调出的页面,其存储区域自然也是按页划分的。Linux采用了块设备和交换文件两种形式来保存换出的页面,但是这两种形式的内部结构是一致的。有时候,为了优化系统性能,会定义不止1个交换空间,因而Linux虚拟存储管理实现了并行管理多个交换空间,这些交换空间均定义在同一个数据结构中(swap_info_struct[MAX_SWAPFILES],mm/swap.c,其中MAX_SWAPFILES定义为最大的交换空间数量)。

  Linux虚拟存储管理的数据结构模型

  Linux虚拟存储管理模型

  根据虚拟内存抽象模型,每个进程都可以互不干扰的使用所有虚拟地址。进程的虚拟内存空间被划分为小的虚拟内存区域来使用。每个内存区域是一段具有相同属性的虚拟地址空间。Linux用vm_area_struct(include/linux/mm.h)来描述一个虚拟内存区域。一个进程的所有内存区域组织成一个双向链表。进程用了一个指向vm_area_struct链表的指针,来描述进程虚拟内存空间的一个区域,包括对该区域的起始和终止地址的描述。进程可以通过vm_operation_struct(include/linux/mm.h)对这些区域进行操作。

  当加载关于进程虚拟地址空间的页面时,一系列的vm_area_struct将自动生成,每一个vm_area_struct描述进程的一部分,如执行代码、数据等。Linux虚拟存储管理支持了多数标准的虚拟内存操作,如读取、关闭、共享、缺页等。一旦vm_area_struct结构生成,就可以通过该结构中的指向vm_operation_struct的指针进行虚拟内存操作了。

  如图3所示,为虚存管理数据结构之间的关系。

 

虚存管理数据结构之间的关系

 

  图 3 虚拟存储管理的数据结构关系

  数据结构介绍

  vm_area_struct

  Linux虚拟存储管理采用了虚拟存储区域的方式来管理虚拟存储空间,一个虚拟存储区域是某个进程的一段虚拟存储空间,该结构由vm_are_struct定义。

  struct vm_area_struct {

  struct task_struct * vm_task; /* VM area parameters */

  /* 虚存区的起始地址 */

  unsigned long vm_start;

  /* 虚存区的终止地址 */

  unsigned long vm_end;

  /* 进程对应于虚存区的访问权限 */

  unsigned short vm_page_prot;

  /* linked list,在链表中指向下一个虚拟内存区域*/

  struct vm_area_struct * vm_next;

  /* linked list,在链表中指向共享区域*/

  struct vm_area_struct * vm_share;

  /* 指向虚存区所在文件的inode结构,若不涉及文件,则为NULL */

  struct inode * vm_inode;

  /* 虚存区相对于文件或设备在inode结构中的偏移量 */

  unsigned long vm_offset;

  /* 指向vm_operation_struct结构 */

  struct vm_operations_struct * vm_ops;

  };

  vm_operation_struct

  Linux虚拟存储管理对于虚存区的操作定义在vm_operation_struct数据结构中,通过在vm_area_struct结构中使用指针vm_ops来确定该虚存区可以进行的一系列操作。

  struct vm_operations_struct {

  /* 打开操作,当内核生成一个虚存区后或者当虚存区被复制后,就用该命令打开。*/

  void (*open)(struct vm_area_struct * area);

  /* 关闭操作,当内核销毁一个虚存区时,就调用该命令。*/

  void (*close)(struct vm_area_struct * area);

  /* 处理缺页异常,当进程访问一个不属于内存的有效页面时,就会调用该命令,

  /* 返回该页的物理地址。*/

  void (*nopage)(int error_code,

  struct vm_area_struct * area, unsigned long address);

  /* 处理写保护异常,当往一个被保护的页面上写入数据时,就会调用该命令。*/

  void (*wppage)(struct vm_area_struct * area, unsigned long address);

  int (*share)(struct vm_area_struct * from, struct vm_area_struct * to, unsigned long address);

  /* 取消映射操作,当内核取消虚存区的部分或者全部映射时,调用该命令;

  /* 当取消全部映射后,内核就会自动调用close()进行关闭操作。*/

  int (*unmap)(struct vm_area_struct *area, unsigned long, size_t);

  };

  总结

  Linux是一个功能强大的实际的操作系统,相对于操作系统原理,每个技术环节都有其自身的特点。限于篇幅,本文仅简单介绍Linux在虚拟存储管理上的技术特点与实现的数据结构之间的关系,作为深入探讨Linux虚拟存储管理源码细节的入门性文章,希望能起到抛砖引玉的作用。

原创粉丝点击