《Linux内核深度解析》选载之物理内存组织

Ftrace训练营火热报名中:Ftrace训练营:站在设计者的角度来理解ftrace(限50人)。训练营第一期报名已圆满成功,好评如潮。第二期课程正在进行中,第三期报名正在火爆进行中(咨询小月微信:linuxer2016)。


ARM安全架构训练营2期火热报名中:阅码场训练营:ARM安全架构之Trustzone/TEE实战--【介绍视频】报名咨询客服(小月微信:linuxer2016)。


ARM架构与调优调试训练营火热报名中:阅码场训练营:ARM架构与调试调优报名咨询客服(小月微信:linuxer2016)。



阅码场用户程磊对《Linux内核深度解析》推荐如下:

1.语言浅显易懂,内容深入浅出。

2.逻辑清晰,条理分明,逐步深入,层层递进。

3.基于较新的4.12内核版本,很多经典内核书籍虽然写的都非常好,但是都是基于2.6内核,很多在2.6之后引入的新技术并没有讲到,而本书对这些新技术都有非常详细的讲解。




作者简介:

余华兵,2005年毕业于华中科技大学计算机学院,取得硕士学位。毕业后的十余年一直在网络通信行业从事软件设计和开发工作,研究方向包括IPv4协议栈、IPv6协议栈和Linux内核。


目录

3.5 物理内存组织 

3.5.1 体系结构 
3.5.2 内存模型 
3.5.3 三级结构 


3.5 物理内存组织


3.5.1 体系结构


目前多处理器系统有两种体系结构。


1)非一致内存访问(Non-Uniform Memory AccessNUMA):指内存被划分成多个内存节点的多处理器系统,访问一个内存节点花费的时间取决于处理器和内存节点的距离。每个处理器有一个本地内存节点,处理器访问本地内存节点的速度比访问其他内存节点的速度快。NUMA 是中高端服务器的主流体系结构。

2)对称多处理器(Symmetric Multi-ProcessorSMP):即一致内存访问(Uniform Memory AccessUMA),所有处理器访问内存花费的时间是相同的。每个处理器的地位是平等的,仅在内核初始化的时候不平等:“0 号处理器作为引导处理器负责初始化内核,其他处理器等待内核初始化完成。

在实际应用中可以采用混合体系结构,在 NUMA 节点内部使用 SMP 体系结构。


3.5.2 内存模型


内存模型是从处理器的角度看到的物理内存分布情况,内核管理不同内存模型的方式存在差异。内存管理子系统支持 3 种内存模型。

1)平坦内存(Flat Memory):内存的物理地址空间是连续的,没有空洞。


(2)不连续内存(Discontiguous Memory):内存的物理地址空间存在空洞,这种模型可以高效地处理空洞。


3)稀疏内存(Sparse Memory):内存的物理地址空间存在空洞。如果要支持内存热插拔,只能选择稀疏内存模型。


什么情况会出现内存的物理地址空间存在空洞?系统包含多块物理内存,两块内存的物理地址空间之间存在空洞。一块内存的物理地址空间也可能存在空洞,可以查看处理器的参考手册获取分配给内存的物理地址空间。


如果内存的物理地址空间是连续的,不连续内存模型会产生额外的开销,降低性能,所以平坦内存模型是更好的选择。


如果内存的物理地址空间存在空洞,应该选择哪种内存模型?


平坦内存模型会为空洞分配 page 结构体,浪费内存;而不连续内存模型对空洞做了优化处理,不会为空洞分配 page 结构体。和平坦内存模型相比,不连续内存模型是更好的选择。

稀疏内存模型是实验性的,尽量不要选择稀疏内存模型,除非内存的物理地址空间很稀疏,或者要支持内存热插拔。其他情况应该选择不连续内存模型。


3.5.3 三级结构


内存管理子系统使用节点(node)、区域(zone)和页(page)三级结构描述物理内存。


1.内存节点


内存节点分两种情况。


1NUMA 系统的内存节点,根据处理器和内存的距离划分。


2)在具有不连续内存的 UMA 系统中,表示比区域的级别更高的内存区域,根据物理地址是否连续划分,每块物理地址连续的内存是一个内存节点。

如图 3.16 所示,内存节点使用一个 pglist_data 结构体描述内存布局。内核定义了宏NODE_DATA(nid),它用来获取节点的 pglist_data 实例。对于平坦内存模型,只有一个pglist_data 实例:contig_page_data

图3.16 内存节点的pglist_data实例


成员 node_id 是节点标识符。


成员 node_zones 是内存区域数组,成员 nr_zones 是内存节点包含的内存区域的数量。


成员 node_start_pfn 是起始物理页号,成员 node_present_pages 是实际存在的物理页的总数,成员 node_spanned_pages 是包括空洞的物理页总数。

成员 node_mem_map 指向页描述符数组,每个物理页对应一个页描述符。注意:成员node_mem_map 可能不是指向数组的第一个元素,因为页描述符数组的大小必须对齐到 2的(MAX_ORDER − 1)次方,(MAX_ORDER − 1)是页分配器可分配的最大阶数。

pglist_data 结构体的主要成员如下:


include/linux/mmzone.h typedef struct pglist_data {  struct zone node_zones[MAX_NR_ZONES]; /* 内存区域数组 */  struct zonelist node_zonelists[MAX_ZONELISTS]; /* 备用区域列表 */  int nr_zones; /* 该节点包含的内存区域数量 */ #ifdef CONFIG_FLAT_NODE_MEM_MAP /* 除了稀疏内存模型以外 */  struct page *node_mem_map; /* 页描述符数组 */ #ifdef CONFIG_PAGE_EXTENSION  struct page_ext *node_page_ext; /* 页的扩展属性 */ #endif #endif  unsigned long node_start_pfn; /* 该节点的起始物理页号 */  unsigned long node_present_pages; /* 物理页总数 */  unsigned long node_spanned_pages; /* 物理页范围的总长度,包括空洞 */  int node_id; /* 节点标识符 */ } pg_data_t;

2.内存区域


内存节点被划分为内存区域,内核定义的区域类型如下:


include/linux/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, #ifdef CONFIG_ZONE_DEVICE  ZONE_DEVICE, #endif __MAX_NR_ZONES };

DMA 区域(ZONE_DMADMA 是“Direct Memory Access”的缩写,意思是直接内存访问。如果有些设备不能直接访问所有内存,需要使用 DMA 区域。例如旧的工业标准体系结构(Industry Standard ArchitectureISA)总线只能直接访问 16MB 以下的内存。

DMA32 区域(ZONE_DMA3264 位系统,如果既要支持只能直接访问 16MB 以下内存的设备,又要支持只能直接访问 4GB 以下内存的 32 位设备,那么必须使用 DMA32 区域。

普通区域(ZONE_NORMAL:直接映射到内核虚拟地址空间的内存区域,直译为“普通区域”,意译为“直接映射区域”或“线性映射区域”。内核虚拟地址和物理地址是线性映射的关系,即虚拟地址 =(物理地址 + 常量)。是否需要使用页表映射?不同处理器的实现不同,例如 ARM 处理器需要使用页表映射,而 MIPS 处理器不需要使用页表映射。

高端内存区域(ZONE_HIGHMEM:这是 32 位时代的产物,内核和用户地址空间1 : 3 划分,内核地址空间只有 1GB,不能把 1GB 以上的内存直接映射到内核地址空间,把不能直接映射的内存划分到高端内存区域。通常把 DMA 区域、DMA32 区域和普通区域统称为低端内存区域。64 位系统的内核虚拟地址空间非常大,不再需要高端内存区域。

可移动区域(ZONE_MOVABLE:它是一个伪内存区域,用来防止内存碎片,后面讲反碎片技术的时候具体描述。


设备区域(ZONE_DEVICE:为支持持久内存(persistent memory)热插拔增加的内存区域。


每个内存区域用一个 zone 结构体描述,其主要成员如下:


include/linux/mmzone.h struct zone {  unsigned long watermark[NR_WMARK]; /* 页分配器使用的水线 */  long lowmem_reserve[MAX_NR_ZONES]; /* 页分配器使用,当前区域保留多少页不能借给高的区域类型 */  struct pglist_data *zone_pgdat; /* 指向内存节点的pglist_data实例 */  struct per_cpu_pageset __percpu *pageset; /* 每处理器页集合 */  unsigned long zone_start_pfn; /* 当前区域的起始物理页号 */  unsigned long managed_pages; /* 伙伴分配器管理的物理页的数量 */  unsigned long spanned_pages; /* 当前区域跨越的总页数,包括空洞 */  unsigned long present_pages; /* 当前区域存在的物理页的数量,不包括空洞 */  const char *name; /* 区域名称 */  struct free_area free_area[MAX_ORDER]; /* 不同长度的空闲区域 */ }

3.物理页


每个物理页对应一个 page 结构体,称为页描述符,内存节点的 pglist_data 实例的成员node_mem_map 指向该内存节点包含的所有物理页的页描述符组成的数组。

结构体 page 的成员 flags 的布局如下:


| [SECTION] | [NODE] | ZONE | [LAST_CPUPID] | ... | FLAGS |

其中,SECTION 是稀疏内存模型中的段编号,NODE 是节点编号,ZONE 是区域类型,FLAGS 是标志位。

内联函数 page_to_nid 用来得到物理页所属的内存节点的编号,page_zonenum 用来得到物理页所属的内存区域的类型。


include/linux/mm.h static inline int page_to_nid(const struct page *page) {  return (page->flags >> NODES_PGSHIFT) & NODES_MASK; } static inline enum zone_type page_zonenum(const struct page *page) {  return (page->flags >> ZONES_PGSHIFT) & ZONES_MASK; }

头文件“include/linux/mm_types.h”定义了 page 结构体。因为物理页的数量很大,所以在 page 结构体中增加 1 个成员,可能导致所有 page 实例占用的内存大幅增加。为了减少内存消耗,内核努力使 page 结构体尽可能小,对于不会同时生效的成员,使用联合体,这种做法带来的负面影响是 page 结构体的可读性差。

感兴趣者可进群一起交流学习




连载已发布文章:

《Linux内核深度解析》选载之内存映射

《Linux内核深度解析》选载之内存地址空间


精华文章:【精华】Linux阅码场原创精华文章汇总



阅码场付费会员专业交流群

会员招募:各专业群会员费为88元/季度,权益包含群内提问,线下活动8折,全年不定期群技术分享(普通用户直播免费,分享后每次点播价为19元/次),有意加入请私信客服小月(小月微信号:linuxer2016)


专业群介绍:

彭伟林-阅码场内核性能与稳定性
本群定位内核性能与稳定性技术交流,覆盖云/网/车/机/芯领域资深内核专家,由阅码场资深讲师彭伟林主持。


甄建勇-性能优化与体系结构

本群定位Perf、cache和CPU架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师甄建勇主持。


李春良-Xenomai与实时优化

本群定位Xenomai与实时优化技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师李春良和彭伟林共同主持。


周贺贺-Tee和ARM架构

本群定位Tee和ARM架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师周贺贺主持。


谢欢-Linux tracers

本群定位Linux tracers技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师谢欢主持。