Chinaunix首页 | 论坛 | 博客
  • 博客访问: 504576
  • 博文数量: 184
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1172
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-21 13:40
个人简介

技术改变命运

文章分类

全部博文(184)

文章存档

2020年(16)

2017年(12)

2016年(156)

我的朋友

分类: LINUX

2016-08-26 10:17:50

在讲解内核中用于组织内存的数据结构之前,考虑到术语不总是容易理解,所以先来看看几个概念。我们首先考虑NUMA系统,这样,在UMA系统上介绍这些概念就非常容易了。

下图给出内存划分的图示:

首先,内核划分为结点。每个结点关联到系统中的一个处理器,在内核中表示为pa_data_t的实例(稍后定义该数据结构)。各个结点又划分为内存域,是内存的进一步细分。例如,对可用于(ISA设备的)DMA操作的内存区是有限制的。只有钱16MB适用,还有一个高端内存区无法直接映射,在二者之间是通用的“普通”内存区。内核引入下列常量来枚举系统中的所有内存域:


[cpp] view plain copy
  1. enum zone_type {  
  2. #ifdef CONFIG_ZONE_DMA  
  3.     ZONE_DMA,//标记适合DMA的内存域。该区域的长度依赖于处理器的类型。在IA-32计算机上,一般的限制是16MB,这是由古老的ISA设备强加的边界,但更现代的计算机也可能受这一限制的影响  
  4. #endif  
  5. #ifdef CONFIG_ZONE_DMA32  
  6.     ZONE_DMA32,//标记了使用32位地址字可寻址、适合DMA的内存域。显然只有在64位系统上两种DMA内存域才有差别。在32位系统上本内存域是空的。  
  7. #endif  
  8.     ZONE_NORMAL,//标记了可直接映射的内核段的普通内存域。这是在所有体系结构上保证都会存在的唯一内存域,但无法保证该地址范围对应了实际的物理内存。例如,如果AMD64系统有2G内存,那么所有内存都属于ZONE_DMA32范围,而ZONE_NORMAL则为空。  
  9. #ifdef CONFIG_HIGHMEM  
  10.     ZONE_HIGHMEM,//标记了超出内核段的物理内存。  
  11. #endif  
  12.     ZONE_MOVABLE,//它是一个虚拟内存域,在防止物理内存碎片的机制中需要使用该内存域,我会在后面的文章中讲解。  
  13.     MAX_NR_ZONES//充当结束标记。在内核想要迭代系统中所有内存域时,会用到该变量。  
  14. };  

各个内存域都关联了一个数组,用来阻止属于该内存域的物理内存页(在内核中称之为页帧)。对每个页帧,都分配了一个struct page实例以及所需的管理数据。各个内存结点都保存在一个单链表中,供内核遍历。处于性能考虑,在为进程分配内存时,内核总是试图在当前运行的CPU相关联的NUMA结点上进行。但这并不总是可行的,例如,该结点的内存可能已经用尽。对此情况,每个结点都提供了一个备用列表(借助于struct zonelist)。该列表包含了其他结点(和相关的内存域),可用于代替当前结点分配内存,列表项的位置越靠后,就越不适合分配。在UMA系统上,上图中只有一个pg_data_t结点,其他的都不变。

主要数据结构分析:

struct pg_data_t详细分析:


[cpp] view plain copy
  1. typedef struct pglist_data {  
  2.     struct zone node_zones[MAX_NR_ZONES];//是一个数组,包含了结点中各内存域的数据结构  
  3.     struct zonelist node_zonelists[MAX_ZONELISTS];//指点了备用结点及其内存域的列表,以便在当前结点没有可用空间时,在备用结点分配内存  
  4.     int nr_zones;//保存结点中不同内存域的数目  
  5. #ifdef CONFIG_FLAT_NODE_MEM_MAP  
  6.     struct page *node_mem_map;//指向page实例数组的指针,用于描述结点的所有物理内存页,它包含了结点中所有内存域的页。  
  7. #endif  
  8.     struct bootmem_data *bdata;//在系统启动期间,内存管理子系统初始化之前,内核页需要使用内存(另外,还需要保留部分内存用于初始化内存管理子系统)。为解决这个问题,内核使用了前面文章讲解的自举内存分配器。bdata指向自举内存分配器数据结构的实例。  
  9.   
  10. #ifdef CONFIG_MEMORY_HOTPLUG  
  11.     spinlock_t node_size_lock;  
  12. #endif  
  13.     unsigned long node_start_pfn;//该NUMA结点第一个页帧的逻辑编号。系统中所有的页帧是依次编号的,每个页帧的号码都是全局唯一的(不只是结点内唯一)。  
  14.     unsigned long node_present_pages; //结点中页帧的数目  
  15.     unsigned long node_spanned_pages;//该结点以页帧为单位计算的长度,包含内存空洞。  
  16.     int node_id;//全局结点ID,系统中的NUMA结点都从0开始编号  
  17.     wait_queue_head_t kswapd_wait;//交换守护进程的等待队列,在将页帧换出结点时会用到。后面的文章会详细讨论。  
  18.     struct task_struct *kswapd;//指向负责该结点的交换守护进程的task_struct。  
  19.     int kswapd_max_order;//定义需要释放的区域的长度。  
  20. } pg_data_t;  

struct zone详细分析:


[cpp] view plain copy
  1. struct zone {  
  2.     /* Fields commonly accessed by the page allocator */  
  3.     unsigned long       pages_min, pages_low, pages_high;//如果空闲页多于pages_high,则内存域的状态时理想的;如果空闲页的数目低于pages_low,则内核开始将页换出到硬盘;如果空闲页低于pages_min,那么页回收工作的压力就比较大,因为内核中急需空闲页。  
  4.     /* 
  5.      * We don't know if the memory that we're going to allocate will be freeable 
  6.      * or/and it will be released eventually, so to avoid totally wasting several 
  7.      * GB of ram we must reserve some of the lower zone memory (otherwise we risk 
  8.      * to run OOM on the lower zones despite there's tons of freeable ram 
  9.      * on the higher zones). This array is recalculated at runtime if the 
  10.      * sysctl_lowmem_reserve_ratio sysctl changes. 
  11.      */  
  12.     unsigned long       lowmem_reserve[MAX_NR_ZONES];//分别为各种内存域指定了若干页,用于一些无论如何都不能失败的关键性内存分配。  
  13.   
  14. #ifdef CONFIG_NUMA  
  15.     int node;  
  16.     /* 
  17.      * zone reclaim becomes active if more unmapped pages exist. 
  18.      */  
  19.     unsigned long       min_unmapped_pages;  
  20.     unsigned long       min_slab_pages;  
  21.     struct per_cpu_pageset  *pageset[NR_CPUS];  
  22. #else  
  23.     struct per_cpu_pageset  pageset[NR_CPUS];//这个数组用于实现每个CPU的热/冷页帧列表。内核使用这些列表来保存可用于满足实现的“新鲜”页。但冷热页帧对应的高速缓存状态不同:有些页帧很可能在高速缓存中,因此可以快速访问,故称之为热的;未缓存的页帧与此相对,称之为冷的。  
  24. #endif  
  25.     /* 
  26.      * free areas of different sizes 
  27.      */  
  28.     spinlock_t      lock;  
  29. #ifdef CONFIG_MEMORY_HOTPLUG  
  30.     /* see spanned/present_pages for more description */  
  31.     seqlock_t       span_seqlock;  
  32. #endif  
  33.     struct free_area    free_area[MAX_ORDER];//用于实现伙伴系统,每个数组元素都表示某种固定长度的一些连续内存区,对于包含在每个区域中的空闲内存页的管理,free_area是一个起点。  
  34.   
  35. #ifndef CONFIG_SPARSEMEM  
  36.     /* 
  37.      * Flags for a pageblock_nr_pages block. See pageblock-flags.h. 
  38.      * In SPARSEMEM, this map is stored in struct mem_section 
  39.      */  
  40.     unsigned long       *pageblock_flags;  
  41. #endif /* CONFIG_SPARSEMEM */  
  42.   
  43.   
  44.     ZONE_PADDING(_pad1_)  
  45.   
  46.     //这一部分涉及的结构成员,用来根据活动情况对内存域中使用的页进行编目,如果页访问频繁,则内核认为它是活动的;而不活动的页则显然相反。在需要换出页时,这种区别是很重要的,如果可能的话,频繁使用的页应该保持不动,而多余的不活动的页则可以换出而没有什么影响。  
  47.     spinlock_t      lru_lock;     
  48.     struct list_head    active_list;//活动页的集合  
  49.     struct list_head    inactive_list;//不活动页的集合  
  50.     unsigned long       nr_scan_active;//在回收内存时,需要扫描的活动页的数目  
  51.     unsigned long       nr_scan_inactive;//在回收内存时,需要扫描的不活动页的数目  
  52.     unsigned long       pages_scanned;//指定了上次换出一页一来,有多少页未能成功扫描  
  53.     unsigned long       flags;//描述了内存域的当前状态  
  54.   
  55.     /* Zone statistics */  
  56.     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];//维护了大量有关该内存域的统计信息  
  57.   
  58.     /* 
  59.      * prev_priority holds the scanning priority for this zone.  It is 
  60.      * defined as the scanning priority at which we achieved our reclaim 
  61.      * target at the previous try_to_free_pages() or balance_pgdat() 
  62.      * invokation. 
  63.      * 
  64.      * We use prev_priority as a measure of how much stress page reclaim is 
  65.      * under - it drives the swappiness decision: whether to unmap mapped 
  66.      * pages. 
  67.      * 
  68.      * Access to both this field is quite racy even on uniprocessor.  But 
  69.      * it is expected to average out OK. 
  70.      */  
  71.     int prev_priority;//存储了上一次扫描操作扫描该内存域的优先级  
  72.   
  73.   
  74.     ZONE_PADDING(_pad2_)  
  75.     /* Rarely used or read-mostly fields */  
  76.   
  77.     /* 
  78.      * wait_table       -- the array holding the hash table 
  79.      * wait_table_hash_nr_entries   -- the size of the hash table array 
  80.      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits) 
  81.      * 
  82.      * The purpose of all these is to keep track of the people 
  83.      * waiting for a page to become available and make them 
  84.      * runnable again when possible. The trouble is that this 
  85.      * consumes a lot of space, especially when so few things 
  86.      * wait on pages at a given time. So instead of using 
  87.      * per-page waitqueues, we use a waitqueue hash table. 
  88.      * 
  89.      * The bucket discipline is to sleep on the same queue when 
  90.      * colliding and wake all in that wait queue when removing. 
  91.      * When something wakes, it must check to be sure its page is 
  92.      * truly available, a la thundering herd. The cost of a 
  93.      * collision is great, but given the expected load of the 
  94.      * table, they should be so rare as to be outweighed by the 
  95.      * benefits from the saved space. 
  96.      * 
  97.      * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the 
  98.      * primary users of these fields, and in mm/page_alloc.c 
  99.      * free_area_init_core() performs the initialization of them.*/  
  100.     //一下三个变量实现了一个等待队列,可用于等待某一页变为可用的进程,进程排成一个队列,等待某些条件,在条件变为真时,内核会通知进程恢复工作。  
  101.     wait_queue_head_t   * wait_table;  
  102.     unsigned long       wait_table_hash_nr_entries;  
  103.     unsigned long       wait_table_bits;  
  104.   
  105.     /* 
  106.      * Discontig memory support fields. 
  107.      */  
  108.     struct pglist_data  *zone_pgdat;//建立内存域和父结点之间的关联  
  109.     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */  
  110.     unsigned long       zone_start_pfn;//内存域第一个页帧的索引  
  111.   
  112.     /* 
  113.      * zone_start_pfn, spanned_pages and present_pages are all 
  114.      * protected by span_seqlock.  It is a seqlock because it has 
  115.      * to be read outside of zone->lock, and it is done in the main 
  116.      * allocator path.  But, it is written quite infrequently. 
  117.      * 
  118.      * The lock is declared along with zone->lock because it is 
  119.      * frequently read in proximity to zone->lock.  It's good to 
  120.      * give them a chance of being in the same cacheline. 
  121.      */  
  122.     unsigned long       spanned_pages;//指定内存域中页的总数,但并非所有的都可用,因为有空洞  
  123.     unsigned long       present_pages;//指定了内存域中实际上可用的页数目  
  124.   
  125.     /* 
  126.      * rarely used fields: 
  127.      */  
  128.     const char      *name;//保存该内存域的惯用名称,目前有3个选项可用NORMAL DMA HIGHMEM  
  129. }____cacheline_internodealigned_in_smp;  
该结构比较特殊的方面是它由ZONE_PADDING分割为几个部分。这是因为对zone结构的访问非常频繁。在多处理器系统上,通常会有不同的CPU试图同时访问结构成员。因此使用锁(后面的博客会详细介绍)防止它们彼此干扰,避免错误和不一致。由于内核对该结构的访问非常频繁,因此会经常性地获取该结构的两个自旋锁zone->lock和zone->lru_lock。


如果数据保存在CPU高速缓存中,那么会处理得更快速。高速缓存分为行,每一行负责不同的内存区。内核使用ZONE_PADDING宏生成“填充”字段添加到结构中,以确保每个自旋锁都处于自身的缓存行中。还使用了编译关键字__cacheline_internodealigned_in_smp,用以实现最优的高速缓存对齐方式。

该结构的最后两个部分也通过填充字段彼此分隔开来。两者都不包含锁,主要目的是将数据保持在一个缓存行中,便于快速访问,从而无需从内存加载数据。由于填充字段造成结构长度的增加是可以忽略的,特别是在内核内存中zone结构的实例相对很少。

struct page详细分析:


[cpp] view plain copy
  1. struct page {  
  2.     unsigned long flags;//存储了体系结构无关的标志,用于描述页的属性       
  3.     atomic_t _count;//是一个使用计数,表示内核中应用该页的次数。在其值到达0时,内核就知道page实例当前不使用,因此可以删除;如果其值大于0,该实例绝不会从内存删除。  
  4.     union {  
  5.         atomic_t _mapcount;//内存管理子系统中映射的页表项计数,表示在页表中有多少项指向该页  
  6.         unsigned int inuse;//用于SLUB分配器,对象的数目  
  7.     };  
  8.     union {  
  9.         struct {  
  10.         unsigned long private;//是一个指向“私有”数据的指针,虚拟内存管理会忽略该数据。根据页的用途,可以用不用的方式使用该指针,大多数情况下它用于将页与缓冲区关联起来。  
  11.         struct address_space *mapping;//mapping默认情况下是指向address_space的,但如果使用技巧将其最低位置1,mapping就指向anon_vma对象  
  12.         };  
  13. #if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS  
  14.         spinlock_t ptl;  
  15. #endif  
  16.         struct kmem_cache *slab;//用于SLUB分配器,指向slab的指针  
  17.         struct page *first_page;//用于复合页的尾页,指向首页  
  18.     };  
  19.     union {  
  20.         pgoff_t index;//在映射内的偏移量  
  21.         void *freelist;     /* SLUB: freelist req. slab lock */  
  22.     };  
  23.     struct list_head lru;//是一个表头,用于在各种链表上维护该页,一遍将页按不用类别分组,最重要的类别是活动页和不活动页  
  24.     /* 
  25.      * On machines where all RAM is mapped into kernel address space, 
  26.      * we can simply calculate the virtual address. On machines with 
  27.      * highmem some memory is mapped into kernel virtual memory 
  28.      * dynamically, so we need a place to store that address. 
  29.      * Note that this field could be 16 bits on x86 ... ;) 
  30.      * 
  31.      * Architectures with slow multiplication can define 
  32.      * WANT_PAGE_VIRTUAL in asm/page.h 
  33.      */  
  34. #if defined(WANT_PAGE_VIRTUAL)  
  35.     void *virtual;//用于高端内存区域中的页,换言之,即无法直接映射到内核内存中的页,virtual用于存储该页的虚拟地址。  
  36. #endif /* WANT_PAGE_VIRTUAL */  
  37. };  

上述结构中使用了大量的union结构,考虑一个例子:一个物理内存页能够通过多个地方的不同页表映射到虚拟地址空间,内核想要跟踪有多少地方映射了该页,为此,struct page中有一个计数器用于计算映射的数目。如果一页用于slab分配器(后面的博客会详细介绍),那么可以确保只有内核会使用该页,而不会有其它地方使用,因此映射计数信息就是多余的,因此内核可以重新解释该字段,用来表示该页被细分为多少个小的内存对象使用,联合体就很适用于该问题。


阅读(1522) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~