几个概念
非一致内存访问 Non-Uniform Memory Access (NUMA)
一致内存访问(UMA)
参考:
NUMA和UMA
在Linux中,内存的每个bank被认为是一个节点(node)
,并用struct pg_data_t
来表示。
系统中的每个node
都被一个pgdat_list链表
管理,此链表以NULL结尾。
pg_data_t结构体的node_next字段用于链接到下一个节点。
PC这种UMA结构的机器,只使用一个静态pg_data_t变量:contig_page_data
。
内存中,每个node被分成很多管理区(zone)
由struct zone_struct
描述(zone_t
)
zone的类型有:
ZONE_DMA,一般是低端地址的内存,用于设备驱动使用。(x86机器中,16M)
ZONE_NORMAL,由内核直接映射到线性地址空间。(x86机器中,16M~896M)
ZONE_HIGHMEM,预留空间,内核不会直接映射这一部分。(x86机器中,896M~末尾)
内存被划分为页面(page)
进行管理
由结构struct page
描述,所有的页面结构都存储在一个全局的mem_map数组
中。
这个数组通常存放在ZONE_NORMAL的首部。
节点node
linux/mmzone.h
/*
* The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM
* (mostly NUMA machines?) to denote a higher-level memory zone than the
* zone_struct denotes. 比zone更高一个维度的结构。
*
* On NUMA machines, each NUMA node would have a pg_data_t to describe
* it's memory layout.
*
* XXX: we need to move the global memory statistics (active_list, ...)
* into the pg_data_t to properly support NUMA.
*/
typedef struct pglist_data {
zone_t node_zones[MAX_NR_ZONES];
zonelist_t node_zonelists[GFP_ZONEMASK+1];
int nr_zones;
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;
- node_zones:该节点包含的管理区,可能有 ZONE_ HIGHMEM,ZONE_ NORMAL和ZONE_ DMA。
- node_zonelists:分配内存时,分别从不同的管理区去尝试。三个zone有一个排列顺序排列。
在调用free_area_init_core()时,通过mm/page_alloc.c文件中的 build_zonelists()建立顺序。
分配内存时,如果在ZONE_HIGHMEM中分配失败,就到ZONE_ NORMAL,再到ZONE_ DMA中去分配。 - nr_zones:该节点中的管理区数目(1~3)。并不是所有的节点都有3个管理区。
- node_mem_map:指struct page数组中的
第一个页面
,struct page代表该节点中的物理页面。
它将被放置在全局mem_map数组的某个位置。 - valid_addr_bitmap:一张描述内存节点中“空洞”的位图,空洞代表并没有实际的内存空间存在。(x86不含空洞)
- bdata:指向内存引导期间分配器程序(boot mem allocator),在第5章中有介绍。
- node_start_paddr:节点的起始物理地址。无符号长整型并不是最佳选择,因为它会在ia32上被物理地址拓展(PAE)拆散。PAE在第2.7节有讨论。
更好的解决方法是用页面帧号(PFN)记录该节点的起始物理地址。一个PFN就是一个物理页面的索引号。物理地址转换成PFN:page_phys_addr>>PAGE_SHIFT(物理地址右移22位,一个页的大小是4K即1<<22)。 - node_start_mapnr:它指出该节点在全局mem_map中的页面偏移。
在free_area_init.core()中,通过计算mem_map与该节点的局部mem_map(称为lmem_map)之间的页面数,从而得到页面偏移。 - node_size:这个管理区中的页面总数。
- node_id:节点的ID号(NID),从0开始。
- node_next:指向下一个节点,所在的链表以NULL结尾。
管理区zone
zone用于跟踪页面使用情况统计、空闲区域信息、锁信息等。
/*
* On machines where it is needed (eg PCs) we divide physical memory
* into multiple physical zones. On a PC we have 3 zones:
*
* ZONE_DMA < 16 MB ISA DMA capable memory
* ZONE_NORMAL 16-896 MB direct mapped by the kernel
* ZONE_HIGHMEM > 896 MB only page cache and user processes
*/
typedef struct zone_struct {
/*
* Commonly accessed fields:
*/
spinlock_t lock;
unsigned long free_pages;
unsigned long pages_min, pages_low, pages_high;
int need_balance;
/*
* free areas of different sizes
*/
free_area_t free_area[MAX_ORDER];
/*
* wait_table -- the array holding the hash table
* wait_table_size -- the size of the hash table array
* wait_table_shift -- wait_table_size
* == BITS_PER_LONG (1 << wait_table_bits)
*
* The purpose of all these is to keep track of the people
* waiting for a page to become available and make them
* runnable again when possible. The trouble is that this
* consumes a lot of space, especially when so few things
* wait on pages at a given time. So instead of using
* per-page waitqueues, we use a waitqueue hash table.
*
* The bucket discipline is to sleep on the same queue when
* colliding and wake all in that wait queue when removing.
* When something wakes, it must check to be sure its page is
* truly available, a la thundering herd. The cost of a
* collision is great, but given the expected load of the
* table, they should be so rare as to be outweighed by the
* benefits from the saved space.
*
* __wait_on_page() and unlock_page() in mm/filemap.c, are the
* primary users of these fields, and in mm/page_alloc.c
* free_area_init_core() performs the initialization of them.
*/
wait_queue_head_t * wait_table;
unsigned long wait_table_size;
unsigned long wait_table_shift;
/*
* Discontig memory support fields.
*/
struct pglist_data *zone_pgdat;
struct page *zone_mem_map;
unsigned long zone_start_paddr;
unsigned long zone_start_mapnr;
/*
* rarely used fields:
*/
char *name;
unsigned long size;
} zone_t;
- lock:并行访问时保护该管理区的自旋锁。
- free_pages:该管理区中
空闲页面的总数
。 - pages_min,pages_low,pages_high:管理区极值。
- need_balance:该标志位通知页面换出进程kswapd平衡该管理区。当可用页面的数量达到管理区极值的某一个值时,就需要平衡该管理区了。
- free_area:空闲区域位图,由
伙伴分配器
使用。 - wait_table:等待队列的哈希表,该等待队列由等待页面释放的进程组成。它对wait_on_page()和 unlock_page()非常重要。虽然所有的进程都可以在一个队列中等待,但这可能会导致所有等待进程在被唤醒后,都去竞争依旧被锁的页面。大量的进程像这样去尝试竞争一个共享资源,有时被称为
惊群效应thundering herd
。 - wait_table_size:该哈希表的大小(等待队列个数),它是2的幂。
- wait_table_shift:定义为一个 long 型所对应的位数减去上述表大小的以2为底的对数。
- zone_pgdat:指向父pg_data_t。
- zone_mem_map:在全局mem_map中该zone引用的第一个struct page。
- zone_start_paddr :同node_start_paddr。
- zone_start_mapnr:同node_start_mapnr。(该zone在 struct page* mem_map 中的以页面结构struct page 为大小的偏移数)
- name:该管理区的字符串名字,“DMA”,“Normal”或者“HighMem”。
- size:该管理区的大小,以页面数计算。
管理区极值watermarks
当系统中的可用内存很少时,页面换入换出守护进程kswapd
被唤醒开始释放页面(见第10章)。
如果内存压力很大,伙伴分配器也开始释放内存 ,有时候这种情况被引用为direct-reclaim路径。
每个管理区都有三个极值,分别称为pages_low, pages_min 和 pages_high,这些极值用于跟踪一个管理区承受了多大的压力。
pages_min的页面数量在内存初始化阶段由函数free_area_init_core()计算出来,并且是基于页面的管理区大小的一个比率。计算值初始化为ZoneSizeInPages/128。它所能取的最小值是20页(在x86上是80 KB),最大值是255页(在x86上是1 MB)。
每个极值在表示内存不足时的行为都互不相同。
- pages_low:在空闲页面数达到pages_low时,伙伴分配器就会唤醒kswapd并开始释放页面。
pages_low的默认值是pages_min的两倍。 - pages_min:当达到pages_min时,
伙伴分配器也开始释放页面(和kswapd的工作一样),这种情况被引用为direct-reclaim路径。 - pages_high:kswpad被唤醒并开始释放页面后,在pages_high个页面被释放以前,是不会认为该管理区已经“平衡”的。当达到这个极值后,已经平衡,kswapd就再次睡眠。
pages_high的默认值是pages_min的三倍.