分类: LINUX
2016-05-03 18:53:28
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
1.1 页面管理
先以一幅图开始介绍linux页面管理的主要概念:
这幅图仅仅是一个不严肃的示意图。它是一棵典型的树型图。
虽然我们分析的A9并不支持NUMA,但是由于NUMA在x86上已经很普遍,并且从代码来看,NUMA和UMA并没有太大的差别,因此我们就以NUMA为例进行分析。
在NUMA系统中,将内存分为不同的NODE,某些CPU访问其中一些NODE最快,因此优先从这些NODE中获得内存。但是当内存不足时,也可能从其他NODE分配内存。每个NODE在内核中的数据结构用pg_data_t来表示。
在每一个NODE中,又将内存分为不同的区。每个区用一个zone结构来表示,有几种不同的区,用zone_type来表示区的类型。
在每一个区中,又将内存分为不同的块,用free_area来表示。每个free_area中包含了同样大小的内存块。一般情况下,有11个这样的free_area。每个free_area中的内存块都是相同大小的。这11个free_area中包含的内存块分别包含2^0,2^1,2^2……2^11个页面。内核在初始化时,将页面尽量放到大块中去。如果要分配小的内存块,并且相应的free_area中没有可用页面时,就将大的块分割成小的块。
每个free_area中管理的页面都代表一个页帧,用page结构表示。一般来说,一个页帧的大小是4K。但是某些系统上可以配置页帧大小。
1.1.1 pg_data_t
/**
* 在NUMA系统中,每一个NUMA节点使用本结构来表示。
*/
typedef struct pglist_data {
/**
* 该节点内的内存区。可能的区域类型用zone_type表示。
*/
struct zone node_zones[MAX_NR_ZONES];
/**
* 该节点的备用内存区。当节点没有可用内存时,就从备用区中分配内存。
*/
struct zonelist node_zonelists[MAX_ZONELISTS];
/**
* 可用内存区数目,即node_zones数据中保存的最后一个有效区域的索引。
*/
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
/**
* 在平坦型的内存模型中,它指向本节点第一个页面的描述符。
*/
struct page *node_mem_map;
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
struct page_cgroup *node_page_cgroup;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
/**
* 在内存子系统初始化以前,即boot阶段也需要进行内存管理。
* 此结构用于这个阶段的内存管理。
*/
struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
/*
* Must be held any time you expect node_start_pfn, node_present_pages
* or node_spanned_pages stay constant. Holding this will also
* guarantee that any pfn_valid() stays that way.
*
* Nests above zone->lock and zone->size_seqlock.
*/
/**
* 当系统支持内存热插拨时,用于保护本结构中的与节点大小相关的字段。
* 哪调用node_start_pfn,node_present_pages,node_spanned_pages相关的代码时,需要使用该锁。
*/
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 */
/**
* 节点编号。全局内唯一,从0开始。
*/
int node_id;
/**
* 等待该节点内的交换守护进程的等待队列。将节点中的页帧换出时会用到。
*/
wait_queue_head_t kswapd_wait;
/**
* 负责该节点的交换守护进程。
*/
struct task_struct *kswapd;
/**
* 由页交换子系统使用,定义要释放的区域大小。
*/
int kswapd_max_order;
/**
* 临时变量,用于内存回收时使用。记录上一次回收时,扫描的最后一个区域。
*/
enum zone_type classzone_idx;
} pg_data_t;
1.1.2 Zone
每个zone代表了一个NUMA节点内可用的内存区域。将内存区域分别管理的原因是:不同的内存区域可以被用于不同的目的。要了解这一点,可以看看zone_type的定义:
/**
* 内存管理区类型
*/
enum zone_type {
#ifdef CONFIG_ZONE_DMA
/**
* 当外部设备不能对所有的内存区域进行DMA访问时,需要使此区域。
* 该区域内的内存可以被内核使用,但是应当尽量保留起来,分配给外部设备作为DMA区域。
* 不同体系结构中,这个区域的范围不一样。
*/
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
/**
* 对x86_64体系结构来说,一部分外设只能访问0-16M的内存。
* 而另一部分的设备则可以访问4G范围的内存。这就需要一个新的内存区域ZONE_DMA32。
*/
ZONE_DMA32,
#endif
/**
* 内核可以直接访问的内存区域,在内核初始化,已经为这些区域建立了内存映射。
* 这个区域内的物理地址可以加上一个固定的偏移值(如0xC0000000),形成一个虚拟地址,内核用这个虚拟地址来访问物理地址。
*/
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
/**
* 对32位体系结构来说,虚拟地址空间只有4G。而内核使用的虚拟地址空间只有1G(这仅仅是对一般的情况来说,有些体系结构并不是这样)。
* 这1G虚拟地址空间中,有一部分可以由内核直接访问(如i386中,896M以下的内存,PPC中768M以下的内存),超过这一部分的内存由ZONE_HIGHMEM区域管理。
* 对64位体系结构来说,不存在本区域。
*/
ZONE_HIGHMEM,
#endif
/**
* 这是一个伪内存段。为了防止形成物理内存碎片,可以将虚拟地址对应的物理地址进行迁移。
*/
ZONE_MOVABLE,
/**
* __MAX_NR_ZONES并不代表一个真实的内存区域,它仅仅表示最大的内存区域类型。
*/
__MAX_NR_ZONES
};
/**
* 内存管理区
*/
struct zone {
/**
* 本管理区的三个水线值:高水线(比较充足)、低水线、MIN水线。
*/
unsigned long watermark[NR_WMARK];
/**
* 当空闲内存低于此值时,要计算本节点能够访问的所有内存区中的空闲内存作为真实的可用内存。
* 否则可能使内存水线失效。
*/
unsigned long percpu_drift_mark;
/**
* 当高端内存、normal内存区域中无法分配到内存时,需要从normal、DMA区域中分配内存。
* 为了避免DMA区域被消耗光,需要额外保留一些内存供驱动使用。
* 该字段就是指从上级内存区退到回内存区时,需要额外保留的内存数量。
*/
unsigned long lowmem_reserve[MAX_NR_ZONES];
#ifdef CONFIG_NUMA
/**
* 所属的NUMA节点。
*/
int node;
/**
* 当可回收的页超过此值时,将进行页面回收。
*/
unsigned long min_unmapped_pages;
/**
* 当管理区中,用于slab的可回收页大于此值时,将回收slab中的缓存页。
*/
unsigned long min_slab_pages;
#endif
/**
* 每CPU的页面缓存。
* 当分配单个页面时,首先从该缓存中分配页面。这样可以:
* 避免使用全局的锁
* 避免同一个页面反复被不同的CPU分配,引起缓存行的失效。
* 避免将管理区中的大块分割成碎片。
*/
struct per_cpu_pageset __percpu *pageset;
/**
* 该锁用于保护伙伴系统数据结构。即保护free_area相关数据。
*/
spinlock_t lock;
/**
* 当管理区中的所有页面都不可回收时设置此页。
* 当页面回收函数扫描完管理区都没有回收到页时设置此标志。
*/
int all_unreclaimable; /* All pages pinned */
#ifdef CONFIG_MEMORY_HOTPLUG
/* see spanned/present_pages for more description */
/**
* 用于保护spanned/present_pages等变量。这些变量几乎不会发生变化,除非发生了内存热插拨操作。
* 这几个变量并不被lock字段保护。并且主要用于读,因此使用读写锁。
*/
seqlock_t span_seqlock;
#endif
/**
* 伙伴系统的主要变量。这个数组定义了11个队列,每个队列中的元素都是大小为2^n的页面块。
*/
struct free_area free_area[MAX_ORDER];
/**
* 本管理区里的页面标志数组。
*/
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
/**
* 这两个字段用于内存紧缩。其目的是为了避免内存外碎片。
* 通过页面迁移,将已经分配的页面合并到一起,未分配页面合并到一起,这样可用页面将形成大的块,减少外碎片。
* 这两个标志用于判断是否需要在本管理区进行内在紧缩。
*/
unsigned int compact_considered;
unsigned int compact_defer_shift;
#endif
/**
* 填充的未用字段,确保后面的字段是缓存行对齐的。
*/
ZONE_PADDING(_pad1_)
/* Fields commonly accessed by the page reclaim scanner */
/**
* lru相关的字段用于内存回收。这个字段用于保护这几个回收相关的字段。
* lru用于确定哪些字段是活跃的,哪些不是活跃的,并据此确定应当被写回到磁盘以释放内存。
*/
spinlock_t lru_lock;
/**
* 匿名活动页、匿名不活动页、文件活动页、文件不活动页链表头。
*/
struct zone_lru {
struct list_head list;
} lru[NR_LRU_LISTS];
/**
* 页面回收状态。
*/
struct zone_reclaim_stat reclaim_stat;
/**
* 自本次回收页面以来,已经扫描过的页面数。
*/
unsigned long pages_scanned; /* since last reclaim */
/**
* 管理区标志。
*/
unsigned long flags; /* zone flags, see below */
/* Zone statistics */
/**
* 一些统计信息,如可用页面数。
*/
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
/**
* 将匿名活动页向匿名非活动页中移动的比率。
*/
unsigned int inactive_ratio;
/**
* 缓存对齐,未用字段。
*/
ZONE_PADDING(_pad2_)
/* Rarely used or read-mostly fields */
/**
* 这个哈希表描述了等待管理区中页面可用的进程。
* wait_table是所有等待队列的桶。
* wait_table_hash_nr_entries描述了wait_table的大小。
* 2^wait_table_bits=wait_table_hash_nr_entries
*/
wait_queue_head_t * wait_table;
unsigned long wait_table_hash_nr_entries;
unsigned long wait_table_bits;
/**
* 管理区所属性的节点。通过此字段可以找到同一节点的其他的管理区。
*/
struct pglist_data *zone_pgdat;
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
/**
* 本管理区管理的第一个页面的页帧号。
*/
unsigned long zone_start_pfn;
/**
* 本管理区中可用的页面,包含空洞。
*/
unsigned long spanned_pages; /* total size, including holes */
/**
* 本管理区中可用的页面,不含空洞。
*/
unsigned long present_pages; /* amount of memory (excluding holes) */
/*
* rarely used fields:
*/
/**
* 管理区名称。
*/
const char *name;
} ____cacheline_internodealigned_in_smp;
1.1.3 page
/**
* 页帧。由于每个页面都有这样一个结构,为了避免占用太多内存,本结构的字段有复用的情况。
* 即一个字段中不同的位表示不同的含义;在不同的情况下,同一个字段可能被用作其他意义。
*/
struct page {
/* 页面标志,同时也有一些位用于表示页面所处的管理区编号 */
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/* 使用计数。 */
atomic_t _count; /* Usage count, see below. */
union {
/**
* 映射计数,即有多少个pte映射到此页面。
* 小于0表示没有映射,=0表示有一个进程在对该页面进行私有映射,大于0表示有多个进程对它进程了共享映射。
*/
atomic_t _mapcount;
struct { /* SLUB */
u16 inuse;
u16 objects;
};
};
union {
struct {
/**
* 如果页面是文件系统中的缓冲页,则它指向页面内的第一个缓存块(buffer_head)。
* 如果页面是交换页,则指向swp_entry_t
* 如果页是空闲的,则该字段由伙伴系统使用。
* 当用于伙伴系统时,如果该页是一个2^k的空闲页块的第一个页,那么它的值就是k.
*/
unsigned long private;
/**
* 如果页面映射到一个文件,那么其最低位为0,并且值是文件节点的地址空间对象。
* 如果是一个匿名页,则指向匿名页的anon_vma对象。
*/
struct address_space *mapping;
};
#if USE_SPLIT_PTLOCKS
spinlock_t ptl;
#endif
/**
* 在被SLUB页面管理算法用来指向一个slab。
*/
struct kmem_cache *slab; /* SLUB: Pointer to slab */
/**
* 如果属于伙伴系统,并且不是伙伴系统中的第一个页(一个伙伴中最多可能有2^11个页),则指向第一个页
*/
struct page *first_page; /* Compound tail pages */
};
union {
/**
* 如果是文件映射,那么表示本页面在文件中的位置(偏移)
*/
pgoff_t index; /* Our offset within mapping. */
void *freelist; /* SLUB: freelist req. slab lock */
};
/**
* 通过此字段,将页面链接到LRU链表中去。由zone->lru_lock保护。
*/
struct list_head lru; /* Pageout list, eg. active_list
* protected by zone->lru_lock !
*/
#if defined(WANT_PAGE_VIRTUAL)
/**
* 如果可以通过内核可以直接访问此页面,那么此字段就是它的虚拟地址。
*/
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
/**
* 最后两个字段都用于调试。
*/
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
unsigned long debug_flags; /* Use atomic bitops on this */
#endif
#ifdef CONFIG_KMEMCHECK
/*
* kmemcheck wants to track the status of each byte in a page; this
* is a pointer to such a status block. NULL if not tracked.
*/
void *shadow;
#endif
};
本文主要讲述了页面管理的主要数据结构,下一节我们将讲述页面分配的函数。