1 内存节点node
1.1 为什么要用node来描述内存
这点前面是说的很明白了, NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快
Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大. 因此linux内核需要用一种体系结构无关的方式来表示内存.
因此linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu结点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA
1.2 内存结点的概念
CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点
系统的物理内存被划分为几个节点(node), 一个node对应一个内存簇bank,即每个内存簇被认为是一个节点
内存被划分为结点. 每个节点关联到系统中的一个处理器, 内核中表示为pg_data_t
的实例. 系统中每个节点被链接到一个以NULL结尾的pgdat_list
链表中<而其中的每个节点利用pg_data_tnode_next
字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data
的静态pg_data_t结构.
内存中的每个节点都是由pg_data_t描述,而pg_data_t由struct pglist_data定义而来, 该数据结构定义在include/linux/mmzone.h, line 615
在分配一个页面时, Linux采用节点局部分配的策略, 从最靠近运行中的CPU的节点分配内存, 由于进程往往是在同一个CPU上运行, 因此从当前节点得到的内存很可能被用到
1.3 pg_data_t描述内存节点
表示node的数据结构为typedef struct pglist_data pg_data_t
, 这个结构定义在include/linux/mmzone.h, line 615中,结构体的内容如下:
/*
* 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 denotes.
*
* On NUMA machines, each NUMA node would have a pg_data_t to describe
* it's memory layout.
*
* Memory statistics and page replacement data structures are maintained on a
* per-zone basis.
*/
struct bootmem_data;
typedef struct pglist_data {
/* 包含了结点中各内存域的数据结构 , 可能的区域类型用zone_type表示*/
struct zone node_zones[MAX_NR_ZONES];
/* 指点了备用结点及其内存域的列表,以便在当前结点没有可用空间时,在备用结点分配内存 */
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones; /* 保存结点中不同内存域的数目 */
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map; /* 指向page实例数组的指针,用于描述结点的所有物理内存页,它包含了结点中所有内存域的页。 */
#ifdef CONFIG_PAGE_EXTENSION
struct page_ext *node_page_ext;
#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.
*
* pgdat_resize_lock() and pgdat_resize_unlock() are provided to
* manipulate node_size_lock without checking for CONFIG_MEMORY_HOTPLUG.
*
* Nests above zone->lock and zone->span_seqlock
* 当系统支持内存热插拨时,用于保护本结构中的与节点大小相关的字段。
* 哪调用node_start_pfn,node_present_pages,node_spanned_pages相关的代码时,需要使用该锁。
*/
spinlock_t node_size_lock;
#endif
/* /*起始页面帧号,指出该节点在全局mem_map中的偏移
系统中所有的页帧是依次编号的,每个页帧的号码都是全局唯一的(不只是结点内唯一) */
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; /* 全局结点ID,系统中的NUMA结点都从0开始编号 */
wait_queue_head_t kswapd_wait; /* 交换守护进程的等待队列,
在将页帧换出结点时会用到。后面的文章会详细讨论。 */
wait_queue_head_t pfmemalloc_wait;
struct task_struct *kswapd; /* Protected by mem_hotplug_begin/end() 指向负责该结点的交换守护进程的task_struct。 */
int kswapd_max_order; /* 定义需要释放的区域的长度 */
enum zone_type classzone_idx;
#ifdef CONFIG_COMPACTION
int kcompactd_max_order;
enum zone_type kcompactd_classz