在NUMA架构中存在多个内存节点,每个内存节点在Linux内核用pg_data_t类型(实际是struct pglist_data)来表示表示;
各个内存节点又各自有各自的内存区(即struct zone),这些内存区由pg_data_t的成员struct zone node_zones[MAX_NR_ZONES]来存放;
而在分配内存时不仅可以从自己的内存节点分配内存,还可从其他内存节点分配内存,选择哪个节点分配内存也是提前有策略设计好了的,这个是通过Linux内核一些数据结构的巧妙设计来决策,这个就是由pg_data_t的成员struct zonelist node_zonelists[MAX_ZONELISTS]来实现的。
typedef struct pglist_data { /* * node_zones contains just the zones for THIS node. Not all of the * zones may be populated, but it is the full list. It is referenced by * this node's node_zonelists as well as other node's node_zonelists. */ struct zone node_zones[MAX_NR_ZONES]; //存放该内存节点的内存区zones /* * node_zonelists contains references to all zones in all nodes. * Generally the first zones will be references to this node's * node_zones. */ struct zonelist node_zonelists[MAX_ZONELISTS]; //zonelist装着系统中所有内存节点的zones ...... } pg_data_t; struct zonelist { struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1]; //一个zonelist最多可以有(MAX_ZONES_PER_ZONELIST + 1)个struct zoneref }; //一个zoneref对应一个zone /* * This struct contains information about a zone in a zonelist. It is stored * here to avoid dereferences into large structures and lookups of tables */ struct zoneref { struct zone *zone; /* Pointer to actual zone */ int zone_idx; /* zone_idx(zoneref->zone) */ };
上面就是一个节点与其他节点zones发生关系的一些相关数据结构。还是来一张图清楚一点:
图1 struct pglist_data与struct zone、struct zonelist关系示意图
pg_data_t->node_zones[MAX_NR_ZONES]:该内存节点的MAX_NR_ZONE个内存区(struct zone)。
具体多少视系统支持哪些类型的struct zone,例如ZONE_DMA, ZONE_DMA32, ZONE_NORMAL, ZONE_HIGH.....。有的架构可能没有ZONE_DMA,有的可能没有ZONE_DMA32,有的可能没有ZONE_HIGH。
pg_data_t->node_zonelists[MAX_ZONELISTS]:定义了该内存节点分配内存时选择从struct zone分配内存的先后顺序。
在支持NUMA的内核中最多两个选择:ZONELIST_FALLBACK和ZONELIST_NOFALLBACK,如下所示
enum { ZONELIST_FALLBACK, /* zonelist with fallback */ #ifdef CONFIG_NUMA /* * The NUMA zonelists are doubled because we need zonelists that * restrict the allocations to a single node for __GFP_THISNODE. */ ZONELIST_NOFALLBACK, /* zonelist without fallback (__GFP_THISNODE) */ #endif MAX_ZONELISTS };
从注释可以了解到:ZONELIST_NOFALLBACK是在NUMA架构中,如果分配内存时使用__GFP_THISNODE了标志时则尝试从pg_data_t->node_zonelists[ZONELIST_NOFALLBACK]中去分配内存;否则尝试从pg_data_t->node_zonelists[ZONELIST_FALLBACK]中去分配内存。
在系统初始化阶段,Linux内核会遍历系统中的各个内存节点,并根据其他邻居节点到该节点"距离"的"远""近"关系安插邻居节点的struct zone到pg_data_t->node_zonelists[ZONELIST_FALLBACK]的数组中,即zonelist->_zonerefs[MAX_ZONES_PER_ZONELIST+1]这个数组中:
struct zonelist { struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1]; //一个zonelist最多可以有(MAX_ZONES_PER_ZONELIST + 1)个struct zoneref };
"距离"越近的邻居节点所属的struct zone,在_zonerefs[]数组中的位置越靠前;同一个邻居节点中zones按照从高到低的顺序排列(即ZONE_HIGH、ZONE_NORMAL...ZONE_DMA)。
这样在分配内存时,优先从最近的邻居内存节点的最高index的内存zone分配内存,这样就达到了前文所说的分配顺序策略的遴选情况。