Linux虚拟内存组织结构()

在前一篇文章中我们介绍了Linux虚拟内存在逻辑上的组织结构,现在就让我们从源代码入手,从程序级仔细看看各个数据结构体的内部组成如何,源代码来自于最新的kernel2.6.26.5,分析过程中主要参考了《Understanding the linux virtual memory》这本书,有兴趣的朋友可以去阅读一下。

 

一、节点的数据表示

在内核中,节点由结构体pg_data_t来表示,它是由结构体pglist_data通过typedef来定义的,位于文件<linux/mmzone.h>,其内容如下:

typedef struct pglist_data {

   struct zone node_zones[MAX_NR_ZONES];

   struct zonelist node_zonelists[MAX_ZONELISTS];

   int nr_zones;

#ifdef CONFIG_FLAT_NODE_MEM_MAP

   struct page *node_mem_map;

#endif

   struct bootmem_data *bdata;

#ifdef CONFIG_MEMORY_HOTPLUG

   spinlock_t node_size_lock;

#endif

   unsigned long node_start_pfn;

   unsigned long node_present_pages; /* total number of physical pages */

   unsigned long node_spanned_pages; /* total size of physical page

                        range, including holes */

   int node_id;

   wait_queue_head_t kswapd_wait;

   struct task_struct *kswapd;

   int kswapd_max_order;

} pg_data_t;

 

各字段含义如下:

node_zones:包含本节点内区的描述结构体,一般而言MAX_NR_ZONES为3。

node_zonelists:这个数组指明了在分配内存时区的选择顺序。

nr_zones:指明本节点内区的个数,这一般都是1到3中的一个数字。并不是所有的节点都有3个区,比如有些节点就没有ZONE_DMA区。

node_mem_map:指向全局内存描述数组中的某一项,该元素描述了本节点的第一页物理内存。

bdata:指向bootmem_data结构体,该结构体用于系统启动时的内存分配器分配、管理该节点的内存。该内存分配器在系统启动完毕后不再使用。

node_size_lock:该自旋锁用于保护node_start_pfn、node_present_pages以及node_spanned_pages,当你要取得这些变量的值时,必须首先获取这个自旋锁。当你调用pfn_valid()前也应当获得这个自旋锁。并且注意必须在zone->lock和zone->size_seqlock前获取这个自旋锁。

node_start_pfn:这个是该节点内第一页物理内存在全局数组mem_map中的序号。

node_present_pages:当前该节点内可获得的物理页总数。

node_spanned_pages:节点内可访问的所有物理页数,包含可能存在的空洞页。

node_id:当前节点的编号,从0开始。

kswapd_wait:kswapd线程的等待队列。kswapd是一个内核线程,当系统中内存页较少时它负责回收物理内存页,这个线程常常处于睡眠状态。

kswapd:指向kswapd线程的进程描述符。

kswapd_max_order:kswapd线程要释放内存块大小取对数后的值。

 

二、区的数据表示

每个区由结构体struct zone来描述,其中包含了页使用统计、空闲页数、琐等信息,这个结构体在<linux/mmzone.h>中定义:

struct zone {

   unsigned long       pages_min, pages_low, pages_high;

   unsigned long       lowmem_reserve[MAX_NR_ZONES];

#ifdef CONFIG_NUMA

   int node;

   unsigned long       min_unmapped_pages;

   unsigned long       min_slab_pages;

   struct per_cpu_pageset  *pageset[NR_CPUS];

#else

   struct per_cpu_pageset  pageset[NR_CPUS];

#endif

   spinlock_t      lock;

#ifdef CONFIG_MEMORY_HOTPLUG

   seqlock_t       span_seqlock;

#endif

   struct free_area    free_area[MAX_ORDER];

 

#ifndef CONFIG_SPARSEMEM

   unsigned long       *pageblock_flags;

#endif /* CONFIG_SPARSEMEM */

 

   spinlock_t      lru_lock;  

   struct list_head    active_list;

   struct list_head    inactive_list;

   unsigned long       nr_scan_active;

   unsigned long       nr_scan_inactive;

   unsigned long       pages_scanned;     /* since last reclaim */

   unsigned long       flags;

   atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];

   int prev_priority;

   wait_queue_head_t   * wait_table;

   unsigned long       wait_table_hash_nr_entries;

   unsigned long       wait_table_bits;

   struct pglist_data  *zone_pgdat;

   unsigned long       zone_start_pfn;

   unsigned long       spanned_pages; 

   unsigned long       present_pages; 

   const char      *name;

} ____cacheline_internodealigned_in_smp;

 

各字段含义如下:

pages_min:管理区中保留的页数,当区中空闲的页框数达到这个值时kswapd线程会被唤醒并以同步的方式回收区中的页框。

pages_low:回收页框的下限,当区中空闲的页框数达到这个值时,伙伴分配器会唤起kswapd线程开始回收页框。

pages_high:回收页框的上限,当kswapd线程被唤醒回收页框时,它会一直回收页框直到区中空闲的页框数达到这个值为止。

lowmem_reserve:指明在靠近内存低端的区内需要保留的页框数,这个主要是为了防止这样一种情况:当内存低端的页框全部被分配后,如果这时候有新的页分配请求就可能会产生OOM,即使此刻在高端内存我们还有大量空闲的页框。

node:指明这个区所属的节点编号。

pageset:用于实现每cpu高速缓存的数组,每个cpu在该数组内占有一项。由于内核经常请求和释放单个页框,因此每cpu高速缓存包含一些预先分配的页框,它们用于满足本地cpu发出的单一内存请求。

lock保护该描述符的自旋锁。

span_seqlock:保护zone_start_pfn、spanned_pages和present_pages的顺序锁,之所以使用顺序锁来保护这3个变量是因为我们经常需要在没有获得zone->lock的情况下读取它们的值,同时我们很少更改这些值。

free_area:标识出区中空闲页框快,这个结构是伙伴分配器的主要数据结构,伙伴分配器就是根据该结构体来对区中内存进行分配、回收等操作的。

pageblock_flags:一个内存块的标志,该内存块大小为pageblock_nr_pages。

lru_lock:活动及非活动页链表使用的自旋锁,主要在页框回收时使用。

active_list:区的活动页链表。

inactive_list:区的非活动页链表。

nr_scan_active:回收页框要扫描的活动页数目。

nr_scan_inactive:回收页框要扫描的非活动页数目。

pages_scanned:页框回收时使用的计数器。

flags:区的标志,目前有三个标志可以设置:

ZONE_ALL_UNRECLAIMABLE——区中所有页框被锁定不能回收

ZONE_RECLAIM_LOCKED——不能并发回收区中页框

ZONE_OOM_LOCKED——区处于OOM的区链表中

vm_stat:包含区的统计信息。

prev_priority:区扫描优先级,主要用于页框回收过程中。

wait_table:进程等待队列的hash表,表中的进程在等待区中的某一页内存。

wait_table_hash_nr_entries:等待队列hash表数组的大小。

wait_table_bits:等待队列hash表大小,1<<wait_table_bits。

zone_pgdat:指向区所属节点的指针。

zone_start_pfn:区中第一个页框在mem_map数组中的编号。

spanned_pages:区中的页框数,包含空洞。

present_pages:区中包含的页框数,不包含空洞。

name:指向区的名称,这个域很少使用。

 

三、页的数据表示

这个结构体是描述物理内存的最小单位,它描述了每个物理页框的属性,定义于文件<linux/mm_types.h>:

struct page {

   unsigned long flags;       

   atomic_t _count;   

   union {

       atomic_t _mapcount;

       struct {       

           u16 inuse;

           u16 objects;

       };

   };

   union {

       struct {

       unsigned long private;     

       struct address_space *mapping; 

       };

#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS

       spinlock_t ptl;

#endif

       struct kmem_cache *slab;

       struct page *first_page;

   };

   union {

       pgoff_t index;      /* Our offset within mapping. */

       void *freelist;     /* SLUB: freelist req. slab lock */

   };

   struct list_head lru;      

 

#if defined(WANT_PAGE_VIRTUAL)

   void *virtual;         

#endif

#ifdef CONFIG_CGROUP_MEM_RES_CTLR

   unsigned long page_cgroup;

#endif

};

各字段含义如下:

flags:一组原子型标志,有些标志可以异步更新,这个域功能繁多,有兴趣的可以进一步看看源代码。

_count:页框的引用计数器。如果该字段为-1,则相应页框空闲,并可被分配给任一进程或内核本身;如果该字段大于或等于0,则说明该页框被分配给了一个或多个进程,或用于存放一些内核数据结构。

_mapcount:本页框对应的页表项数目,本页框若未被映射则为-1。

inuse、objects:这2个标志在SLUB分配器中使用。

private:可用于正在使用该页的内核成分使用,如果页是空闲的则该字段由伙伴系统使用。

mapping:当页被插入页高速缓存中时使用。

slab:指向slab的指针。

first_page:

index、freelist:作为不同的含义被几种内核成分使用。

lru:将页链接到最近最少使用双向链表中。

virtual:指向本页框的内核虚拟地址。

page_cgroup:这个域只有在内存资源控制器起作用的情况下使用,用于统计处于内存资源控制器下的页框。

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐