最近转载了不少文章,自己也读了ULK3 MM章节数遍,小有体会。写一些心得,以免自己忘记。
之前一些virtual address到physical address的映射的基本MM机制,可以参看博客转载的其他文章,不再一一赘叙。本文打算主要focus在源码和一些细节的理解上。另外提一句,我看的kernel版本是2.4.0
在描述之前要对两个看起来很简单的基本概念进行澄清,其实不是看起来那么简单。page and page frame。
根据ULK3 P.50描述,线性地址被分成以固定长度为单位的组,称为page。说明page是一个线性地址,也就是Linux中的虚拟地址的概念。而且page既指一组线性地址(4KB),又包括在这组地址中的数据。(我目前的理解)但是在函数alloc_pages()中的返回值struct page*为一个物理地址的指针。我们可以用void* page_address(struct page *page)将其转化为逻辑地址。(这个意义上说page又是指物理地址...)
而page frame是指分页单元把所有RAM分成固定长度的frame,每个page frame包括一个page。这是一个physical memory的概念。
现在我们考虑NUMA。Linux 把物理内存划分为3个层次来管理:存储节点(Node)、管理区(Zone)和页面(Page),并用3 个相应的数据结构来描述。为什么这样划分,后面会讲到。
先看看整体的结构:
all_mem
/ | \
/ | \
pglist_data pglist_data pglist_data
/ | \
/ | \
zone_dma zone_normal zone_high
|
page
|
page
整个memory分成不同的pglist_data,每个pglist_data代表一个Node,每个Node又分为三个zone,分别是ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM。每个zone里面由若干个page组成。
我们结合代码看一下这几个结构声明。
1)Page
先说说我们比较熟悉的page,对一个page的描述在文件 include/linux/mm.h struct page中。贴过来看一下。
typedef struct page {
struct list_head list;
struct address_space *mapping;
unsigned long index;
struct page *next_hash;
atomic_t count;
unsigned long flags; /* atomic flags, some possibly updated asynchronously */
struct list_head lru;
unsigned long age;
wait_queue_head_t wait;
struct page **pprev_hash;
struct buffer_head * buffers;
void *virtual; /* non-NULL if kmapped */
struct zone_struct *zone;
} mem_map_t;
系统在每个物理页面都有一个page结构,根据陈莉君教授在kernel宝典中的描述,初始化时候会根据内存大小建立一个page结构的数组mem_page。
2) Node
这个地方就和NUMA有关系了。因为不同的内存经过不同的总线被访问到,或者一些类似的原因,使得CPU对不同内存单元的访问时间不一样。因此Kernel把所有物理内存分为几个Node,每个Node中,可以理解成是由同一种介质的内存组成,所以相同CPU访问页面所需要的时间相同。(我个人猜想如果有其他的内存插在另外一台通过系统总线连接的服务器上,这样Kernel就会相应分成两个Node的意思吧。)一般我们在一台PC机上不需要考虑多个Node的问题,都是UMA,也就是只有一个Node。
Node的数据结构为pglist_data,描述于include/linux/mmzone.h 中:
typedef struct pglist_data {
zone_t node_zones[MAX_NR_ZONES];
zonelist_t node_zonelists[NR_GFPINDEX];
struct page *node_mem_map;
unsigned long *valid_addr_bitmap;
struct bootmem_data *bdata;
unsigned long node_start_paddr;
unsigned long node_start_mapnr;
unsigned long node_size;
int node_id;
struct pglist_data *node_next;
} pg_data_t;
extern int numnodes;
extern pg_data_t *pgdat_list;
若干存储节点的pglist_data 数据结构可以通过node_next 形成一个单链表队列。
每个结构中的node_mem_map 指向具体节点的page 结构数组,而数组node_zone[]就是该节点的最多3 个页面管理区。
3) Zone
Linux 又把物理页面划分为3个区:
• 专供DMA 使用的ZONE_DMA 区(小于16MB);
• 常规的ZONE_NORMAL 区(大于16MB 小于896MB);
• 内核不能直接映射的区ZONE_HIGMEM 区(大于896MB)。
每个zone都用struct zone_struct 结构来表示, 描述于include/linux/mmzone.h:
typedef struct zone_struct {
/*
* Commonly accessed fields:
*/
spinlock_t lock;
unsigned long offset;
unsigned long free_pages;
unsigned long inactive_clean_pages;
unsigned long inactive_dirty_pages;
unsigned long pages_min, pages_low, pages_high;
/*
* free areas of different sizes
*/
struct list_head inactive_clean_list;
free_area_t free_area[MAX_ORDER];
/*
* rarely used fields:
*/
char *name;
unsigned long size;
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;
unsigned long zone_start_paddr;
unsigned long zone_start_mapnr;
struct page *zone_mem_map;
} zone_t;
对struct zone_struct结构中每个域的描述如下:
lock :用来保证对该结构中其它域的串行访问
free_pages :在这个区中现有空闲页的个数
pages_min、pages_low及 pages_high是对这个区最少、此少及最多页面个数的描述
need_balance:与kswapd合在一起使用
free_area:在伙伴分配系统中的位图数组和页面链表
zone_pgdat:本管理区所在的存储节点
zone_mem_map:该管理区的内存映射表
zone_start_paddr:该管理区的起始物理地址
zone_start_mapnr:在mem_map中的索引(或下标)
name:该管理区的名字
size:该管理区物理内存总的大小
其中,free_area_t定义为:
#difine MAX_ORDER 10
type struct free_area_struct {
struct list_head free_list
unsigned int *map
} free_area_t
zone_struct结构中的free_area[MAX_ORDER]是一组“空闲区间”链表。为什么要定义一组而不是一个空闲队列呢?这是因为常常需要成块地在物理空间分配连续的多个页面,所以要按块的大小分别加以管理。因此,在管理区数据结构中既要有一个队列来保持一些离散(连续长度为1)的物理页面,还要有一个队列来保持一些连续长度为2的页面块以及连续长度为4、8、16、…、直至2^MAX_ORDER(即4M字节)的队列。