6.4.1 描述虚拟空间的数据结构

前几节介绍的数据结构如存储节点(node)、管理区(zone)、页面(page)及空闲区(free_area)都用于物理空间的管理。这一节主要关注虚拟空间的管理。虚拟空间的管理是以进程为基础的,每个进程都有各自的虚存空间(或叫用户空间,地址空间),除此之外,每个进程的“内核空间”是为所有的进程所共享的。

一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。

1. MM_STRUCT结构

 mm_strcut 用来描述一个进程的虚拟地址空间,在/include/linux/sched.h 中描述如下:

struct mm_struct {

         struct vm_area_struct * mmap;           /* 指向虚拟区间(VMA)链表 */

         rb_root_t mm_rb;         *指向red_black*/

         struct vm_area_struct * mmap_cache;     /* 指向最近找到的虚拟区间*/

         pgd_t * pgd;             *指向进程的页目录*/

              atomic_t mm_users;                   /* 用户空间中的有多少用户*/                                     

              atomic_t mm_count;               /* "struct mm_struct"有多少引用*/                                     

         int map_count;                        /* 虚拟区间的个数*/

         struct rw_semaphore mmap_sem;

      spinlock_t page_table_lock;        /* 保护任务页表和 mm->rss */                                              

         struct list_head mmlist;            /*所有活动(activemm的链表 */

         unsigned long start_code, end_code, start_data, end_data;

         unsigned long start_brk, brk, start_stack;

         unsigned long arg_start, arg_end, env_start, env_end;

         unsigned long rss, total_vm, locked_vm;

         unsigned long def_flags;

         unsigned long cpu_vm_mask;

         unsigned long swap_address;

 

         unsigned dumpable:1;

 

         /* Architecture-specific MM context */

         mm_context_t context;

};

对该结构进一步说明如下:

·    在内核代码中,指向这个数据结构的变量常常是mm

·    每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。

·    一个进程的虚拟空间中可能有多个虚拟区间(参见下面对vm_area_struct描述),对这些虚拟区间的组织方式有两种,当虚拟区较少时采用单链表,由mmap指针指向这个链表,当虚拟区间多时采用“红黑树(red_black tree)”结构,由mm_rb指向这颗树。在2.4.10以前的版本中,采用的是AVL树,因为与AVL树相比,对红黑树进行操作的效率更高。

·    因为程序中用到的地址常常具有局部性,因此,最近一次用到的虚拟区间很可能下一次还要用到,因此,把最近用到的虚拟区间结构应当放入高速缓存,这个虚拟区间就由mmap_cache指向。

·    指针pgt指向该进程的页目录(每个进程都有自己的页目录,注意同内核页目录的区别),当调度程序调度一个程序运行时,就将这个地址转成物理地址,并写入控制寄存器(CR3)。

·    由于进程的虚拟空间及其下属的虚拟区间有可能在不同的上下文中受到访问,而这些访问又必须互斥,所以在该结构中设置了用于PV操作的信号量mmap_sem。此外,page_table_lock也是为类似的目的而设置。

·    虽然每个进程只有一个虚拟地址空间,但这个地址空间可以被别的进程来共享,如,子进程共享父进程的地址空间(也即共享mm_struct结构)。所以,用mm_usermm_count进行计数。类型atomic_t实际上就是整数,但对这种整数的操作必须是“原子”的。

·    另外,还描述了代码段、数据段、堆栈段、参数段以及环境段的起始地址和结束地址。这里的段是对程序的逻辑划分,与我们前面所描述的段机制是不同的。

·    mm_context_t是与平台相关的一个结构,对i386 几乎用处不大。

在后面对代码的分析中对有些域给予进一步说明。

2. VM_AREA_STRUCT 结构

 vm_area_struct描述进程的一个虚拟地址区间,在/include/linux/mm.h中描述如下:

 

 struct vm_area_struct

         struct mm_struct * vm_mm;       /* 虚拟区间所在的地址空间*/

         unsigned long vm_start;         /* vm_mm中的起始地址*/

         unsigned long vm_end;           /*vm_mm中的结束地址 */

 

         /* linked list of VM areas per task, sorted by address */

         struct vm_area_struct *vm_next;

 

         pgprot_t vm_page_prot;          /* 对这个虚拟区间的存取权限 */

         unsigned long vm_flags;         /* 虚拟区间的标志. */

 

         rb_node_t vm_rb;

 

         /*

          * For areas with an address space and backing store,

          * one of the address_space->i_mmap{,shared} lists,

          * for shm areas, the list of attaches, otherwise unused.

          */

         struct vm_area_struct *vm_next_share;

         struct vm_area_struct **vm_pprev_share;

 

         /*对这个区间进行操作的函数 */

         struct vm_operations_struct * vm_ops;

 

         /* Information about our backing store: */

         unsigned long vm_pgoff;         /* Offset (within vm_file) in PAGE_SIZE

                                            units, *not* PAGE_CACHE_SIZE */

         struct file * vm_file;          /* File we map to (can be NULL). */

         unsigned long vm_raend;         /* XXX: put full readahead info here. */

         void * vm_private_data;         /* was vm_pte (shared mem) */

 };

vm_flag是描述对虚拟区间的操作的标志,其定义和描述如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 6.1 虚拟区间的标志

标志名                    描述

VM_DENYWRITE              在这个区间映射一个打开后不能用来写的文件。

VM_EXEC                   页可以被执行。

VM_EXECUTABLE             页含有可执行代码。

VM_GROWSDOWN              这个区间可以向低地址扩展。

VM_GROWSUP            这个区间可以向高地址扩展。

VM_IO                     这个区间映射一个设备的I/O地址空间。

VM_LOCKED                页被锁住不能被交换出去。

VM_MAYEXEC                VM_EXEC  标志可以被设置。

VM_MAYREAD                VM_READ  标志可以被设置。

VM_MAYSHARE               VM_SHARE 标志可以被设置。

VM_MAYWRITE               VM_WRITE 标志可以被设置。

VM_READ            页是可读的。

VM_SHARED                 页可以被多个进程共享。

VM_SHM                        页用于IPC共享内存。
VM_WRITE                  页是可写的。

 

较高层次的结构vm_area_structs是由双向链表连接起来的,它们是按虚地址的降顺序来排列的,每个这样的结构都对应描述一个相邻的地址空间范围。之所以这样分割,是因为每个虚拟区间可能来源不同,有的可能来自可执行映象,有的可能来自共享库,而有的则可能是动态分配的内存区,所以对每一个由vm_area_structs结构所描述的区间的处理操作和它前后范围的处理操作不同。因此Linux 把虚拟内存分割管理,并利用了虚拟内存处理例程(vm_ops)来抽象对不同来源虚拟内存的处理方法。不同的虚拟区间其处理操作可能不同,Linux这里利用了面向对象的思想,即把一个虚拟区间看成一个对象,用vm_area_structs描述了这个对象的属性,其中的vm_operation结构描述了在这个对象上的操作,其定义在/includelinuxmm.h中:

  /*

  * These are the virtual MM functions - opening of an area, closing and

  * unmapping it (needed to keep files on disk up-to-date etc), pointer

  * to the functions called when a no-page or a wp-page exception occurs.

  */

struct vm_operations_struct {

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

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

struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);

};

 

vm_operations结构中包含的是函数指针;其中,openclose分别用于虚拟区间的打开、关闭,而nopage用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数。如图6.14给出虚拟区间的操作集。

               

 


6.14虚拟地址区间的操作集

 3.红黑树结构

     Linux内核从2.4.10开始,对虚拟区的组织不再采用AVL树,而是采用红黑树,这也是出于效率的考虑,虽然AVL树和红黑树很类似,但在插入和删除节点方面,采用红黑树的性能更好一些,下面对红黑树给予简单介绍。

   一颗红黑树是具有以下特点的二叉树:

(1)   每个节点着有颜色,或者为红,或者为黑

(2)   根节点为黑色

(3)    如果一个节点为红色,那么它的子节点必须为黑色

(4)    从一个节点到叶子节点上的所有路径都包含有相同的黑色节点数

 6.15就是一颗红黑树。

                    

 

 


                  6.15 一颗红黑树      

红黑树的结构在include/linux/rbtree.h中定义如下:

typedef struct rb_node_s

{

         struct rb_node_s * rb_parent;

         int rb_color;

#define RB_RED          0

#define RB_BLACK        1

        struct rb_node_s * rb_right;

        struct rb_node_s * rb_left;

} rb_node_t;