您的位置:首页 > 运维架构 > 网站架构

《深入Linux内核架构》笔记:(N)UMA模型中的内存组织

2011-01-26 22:05 246 查看
1.概述

在Linux中,内核对一致和非一致内存访问系统使用相同的数据结构,因此针对各种不同形式的内存布局,各个算法几乎没有什么差别。在UMA系统上,只使用一个NUMA结点来管理整个系统内存。

首先内在划分为结点。每个结点关联到系统中的一个处理器,在内核中表示为pg_data_t的结构。

各个结点又划分为内存域,是内存的进一步细分。例如,对可用于(ISA设备的)DMA操作的内存区是有限制的。只有前16M适用,还有一个高端内存区域无法直接映射。在二者之间是通用的“普通”内存区。因此一个结点最多有3个内存域组成,如下图


内核引入了下列常量来枚举系统中的所有内存域:

<mmzone.h>
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA    ,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32  ,
#endif
ZONE_NORMAL ,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
MAX_NR_ZONES
};


◆ ZONE_DMA标记适合DMA的内存域。该区域的长度依赖于处理器类型。

◆ ZONE_DMA32标记了使用32位地址字可寻址、适合DMA的内存域。显然只有在64位系统上两种DMA内存域才有差别。

◆ ZONE_NORMAL标记了可直接映射到内核段的普通内存域。这是在所有体系结构上保证都会存在的唯一内存域,但无法保证该地址范围对应了实际的物理内存。

◆ ZONE_HIGHMEM标记了走出内核段的物理内存。

此外内核定义了一个伪内存域ZONE_MOVABLE,在防止物理内存碎片的机制中需要使用该内存域。

MAX_NR_ZONES充当结束标记,在内核想要迭代系统中的所有内存域时,会用到该常量。

各个内在域都关联了一个数组,用来组织属于该内存域的物理内存页(内核中称为页帧)。对每个页帧,都分配了一个struct page的结构以及所需的管理数据。

出于性能考虑,在为进程分配内存时,内核总是试图在当前运行的CPU相关系的NUMA结点上进行。但这并不总是可行的,例如,该结点的内存可能已经用尽。对此类情况,每个结点都提供了一个备用列表。该列表包含了其他结点,可用于代替当前结点分配内存。列表项的位置越靠后,就越不适合分配。

2.数据结构

●结点管理

pg_data_t是用于表示结点的基本元素,定义如下:

]<mmzone.h>
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
struct page *node_mem_map;
struct bootmem_data *bdata;
unsigned long node_start_pfn;
// 物理内存页总数
unsigned long node_present_pages;
// 物理内存页的总长度,包含洞在内
unsigned long node_spanned_pages;
int node_id;
struct pglist_data *pgdat_next;
wait_queue_head_t kswapd_wait;
struct task_struct *kswapd;
int kswapd_max_order;
} pg_data_t;


◆ node_zones 是一个数组,包含了结点中各个内存域的数据结构

◆ node_zonelists 指定了备用结点及其内存域的列表,以便在当前结点没有可用窨时,在备用结点分配内存。

◆ 结点不同内在域的数目保存在nr_zones中。

◆ node_mem_map是指向page实例数组的指针,用于描述结点的所有物理内存页。它包含了结点中所有内存域的页。

◆ 在系统启动期间,内在管理子系统初始化之前,内核使用了自举内存分配器。bdata指向自举内存分配器数据结构的实例。

◆ node_start_pfn 是该NUMA结点第一个页帧的逻辑编号。系统中所有结点的页帧是依次编号的,每个页帧的号码都是全局唯一的(不只是结点内唯一)。

node_start_pfn在UMA系统中总是0,因为其中只有一个结点,因此其第一个页帧(物理内存页)编号总是0。

◆ node_present_pages 指定了结点中页帧(物理内存页)的数目。

◆ node_spanned_pages 则给出了该结点以页帧(物理内存页)为单位计算的长度。

node_present_pages 与 node_spanned_pages两者的值不一定相同,因为结点中可能有一些空洞,并不对应真正的页帧(物理内存页)

◆ node_id是全局结点的ID。系统中的NUMA结点都是从0开始编号。

◆ pgdat_next 连接到下一个内存点,系统中所有结点都通过单链表连接起来,其末尾通过空指针标记。

◆ kswapd_wait 是交换守护进程的等待队列,在将页帧换出结点时会用到。

结点的内存域保存在node_zones[MAX_NR_ZONES]。该数组总是有3个项,即使结点没有那么多内存域,也是如此。如果不足3个,则其余的数组项用0填充。

●结点状态管理

如果系统中结点多于一个,内核会维护一个位图,用以提供各个结点状态信息。状态是用位掩码指定的,可使用下列值:
<nodemask.h>
enum node_states {
// 结点在某个时候可能变为联机
N_POSSIBLE,

// 结点是联机的
N_ONLINE,

// 结点是普通内存域
N_NORMAL_MEMORY,
// 结点是普通或高端内存域
#ifdef CONFIG_HIGHMEM
N_HIGH_MEMORY,
#else
N_HIGH_MEMORY = N_NORMAL_MEMORY,
#endif
// 结点有一个或多个CPU
N_CPU,
NR_NODE_STATES
};


状态N_POSSIBLE、N_ONLINE和N_CPU用于CPU和内存的执插拔,在这里不考虑这些特性。

内存管理有必要的标志是N_HIGH_MEMORY 和 N_NORMAL_MEMORY。如果结点有普通或高端内存则使用 N_HIGH_MEMORY,仅当结点没有高端内存才设置N_NORMAL_MEMORY。

两个辅助函数来设置或清除位域或特定结点中的一个比特位:

<nodemask.h>
void node_set_state( int node , enum node_states state);
void node_clear_state( int node , enum node_states state );

此外宏for_each_node_state(__node,__state)用来迭代处于特定状态的所有结点,

而for_each_online_node(node) 则迭代所有活动结点。

如果内核编译为只支持单个结点(即使用平坦内在模型),则没有结点位图,上术操作该位图的函数则变为空操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: