大家好,今天来为大家解答Linux内存管理:NUMA技术(非统一内存访问架构)详解这个问题的一些问题点,包括也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~
如果物理内存是分布式的并且由多个单元组成(例如每个核心都有自己的本地内存),那么CPU在访问靠近它的本地内存时会更快,而在访问其他CPU的内存或全局内存时会更快它比较慢。这种架构称为非统一内存访问(NUMA)。
以上是硬件层面的NUMA,Linux作为软件层面抽象了NUMA的概念。即使硬件是具有连续内存的UMA,Linux也可以将其划分为多个节点。同样,即使硬件是具有不连续物理内存的NUMA,Linux也可以将其视为UMA。
因此,在Linux系统中,您可以基于UMA平台测试NUMA上的应用程序特性。从另一个角度来看,UMA是一种特殊的NUMA,只有一个节点,因此两者都可以用NUMA模型来表示。
在传统的SMP(对称多处理器)中,所有处理器共享系统总线。因此,当处理器数量增加时,系统总线的竞争和冲突加剧,系统总线将成为瓶颈。因此,目前SMP系统的CPU数量一般只有几十个,可扩展性受到很大限制。 NUMA技术有效地结合了SMP系统的易编程性和MPP(大规模并行)系统的易扩展性,更好地解决了SMP系统的可扩展性问题,已成为当今高性能服务器的主流架构之一。
在NUMA系统中,当Linux内核收到内存分配请求时,它会首先从发出请求的CPU的本地或相邻内存节点中搜索空闲内存。这种方法称为本地分配。本地分配允许后续的内存访问对于底层物理资源来说是本地的。
每个节点由一个或多个zone组成(我们可能经常迷失在虚拟内存和物理内存的各种描述中,但是以后看到zone就知道它指的是物理内存),每个zone又由若干个zone组成。由页框组成(一般页框指的是物理页)。
基于NUMA架构的高性能服务器有HP的Superdome、SGI的Altix 3000、IBM的x440、NEC的TX7、AMD的Opteron等。
概念
NUMA有多个节点(Node)。每个节点可以有多个CPU(每个CPU可以有多个核心或线程)。节点内使用共享内存控制器,因此该节点的所有内存都供该节点的所有CPU 使用。是相同的,但对于其他节点中的所有CPU 来说是不同的。节点可以分为三种类型:本地节点(Local Node)、邻居节点(Neighbor Node)和远程节点(Remote Node)。
本地节点:对于一个节点中的所有CPU来说,这个节点称为本地节点;
邻居节点:与本地节点相邻的节点称为邻居节点;
远程节点:既不是本地节点也不是邻居节点的节点称为远程节点。
邻居节点和远程节点称为非本地节点(Off Node)。
不同类型节点的CPU访问内存的速度是不同的:本地节点、邻居节点、远程节点。本地节点的访问速度最快,远程节点的访问速度最慢。即访问速度与节点的距离有关。距离越远,访问速度越慢。这个距离称为节点距离。
常用的NUMA系统中:硬件设计已经保证了系统中所有缓存的一致性(Cache Coherent,ccNUMA);不同类型节点之间的缓存同步时间不同,这会导致不公平的资源竞争。对于一些特殊的应用,可以考虑使用FIFO Spinlock来保证公平性。
二.NUMA存储管理
NUMA系统由通过高速互连网络连接的多个节点组成。图1 显示了SGI Altix 3000 ccNUMA 系统中的两个节点。
NUMA系统的节点通常由一组CPU(例如SGI Altix 3000是2个Itanium2 CPU)和本地内存组成。某些节点可能还具有I/O 子系统。由于每个节点都有自己的本地内存,因此整个系统的内存在物理上是分布式的。每个节点访问本地内存和访问其他节点的远程内存的延迟是不同的。为了减少不一致性,减少随机内存访问对系统的影响,硬件设计应尽量减少远程内存访问的延迟(如通过Cache一致性设计等),操作系统也必须能够感知硬件的拓扑并优化系统的内存访问。
IA64 Linux 目前支持的NUMA 架构服务器的物理拓扑描述是通过ACPI(高级配置和电源接口)来实现的。 ACPI 是由Compaq、Intel、Microsoft、Phoenix 和Toshiba 联合开发的BIOS 规范。它定义了非常广泛的配置和电源管理。目前该规范的版本已经发展到2.0,并且3.0o版本正在开发中。具体信息可以从http://www.acpi.info网站获取。 ACPI规范也被广泛应用于IA-32架构的Xeon服务器系统中。
Linux的NUMA系统的物理内存分布信息是从系统固件的ACPI表中获取的。最重要的是SRAT(系统资源关联表)和SLIT(系统位置信息表)表。 SRAT包含两个结构:
Processor Local APIC/SAPIC Affinity Structure:记录某个CPU的信息; Memory Affinity Structure:记录内存信息; SLIT表记录了每个节点之间的距离,在系统中通过数组node_distance[]记录。
Linux采用Node、Zone、Page三级结构来描述物理内存,如图2所示。
图2 Linux中Node、Zone、Page的关系
相关视频推荐
90分钟了解Linux内存架构、numa的优点、slab的实现、vmalloc的原理
Linux内存管理问题——如何梳理自己的思路,开发面试双丰收
学习地址:C/C++Linux链安装/处理Xuanfear
需要C/C++ Linux服务器架构师学习资料加qun812855908(资料包括C/C++、Linux、golang技术、内核、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、流媒体、CDN、P2P、K8S、Docker 、TCP/IP、协程、DPDK、ffmpeg、各大厂商面试题等)
2.1 结点
Linux 使用struct pg_data_t 结构来描述系统的内存。系统中的每个节点都挂载在一个pgdat_list列表中。对于UMA架构来说,只有一个静态的pg_data_t结构体contig_page_data。扩展NUMA系统非常容易。 NUMA系统中的节点可以对应Linux存储描述中的节点。详细说明请参见linux/mmzone.h。
typedef struct pglist_data { zone_t node_zones[MAX_NR_ZONES]; zonelist_t node_zonelists[GFP_ZONEMASK+1]; int nr_zones;结构页*node_mem_map;无符号长*valid_addr_bitmap;结构bootmem_data *bdata;无符号长node_start_paddr;无符号长node_start_mapnr;无符号长节点大小; int 节点ID ; struct pglist_data *node_next;} pg_data_t;该结构体中的主要字段解释如下。
领域
阐明
节点区域
该节点的zone类型一般包括ZONE_HIGHMEM、ZONE_NORMAL和ZONE_DMA。
节点区域列表
分配内存时对区域进行排序。这是page_alloc.c 中free_area_init_core() 到build_zonelists() 设置区域的顺序
区域编号
该节点的区域数量可以是1 到3 个,但并非所有节点都需要有3 个区域。
节点内存映射
它是struct page数组的第一页,代表节点中的每个物理页框。根据节点在系统中的顺序,它可以位于全局mem_map 数组中的某个位置
有效地址位图
用于描述节点内存空洞的位图
节点起始地址
节点的起始物理地址
节点开始映射号
给定全局mem_map 中的页偏移量,free_area_init_core() 计算该节点在mem_map 和lmem_map 之间的页框数
节点大小
该区域的页框总数
节点id
节点的ID,整个系统的节点ID从0开始
系统中所有节点都维护在pgdat_list列表中,列表初始化在init_bootmem_core函数中完成。
影响区域列表模式
Node模式下组织的zonelist为:
即每个节点按照与节点的Node Distance距离进行排序,以达到更好的内存分配。
区域列表[2]
配置NUMA 后,每个节点将关联2 个zonelist:
1)zonelist[0]存储以Node模式或Zone模式组织的zonelist,包括所有节点的zone;
2)zonelist[1]中只存储本节点的zone,为legacy模式;
zonelist[1] 用于仅从节点自己的区域分配内存(请参阅__GFP_THISNODE 标志)。
Page Frame
虽然内存访问的最小单位是字节或字,但MMU以页为单位搜索页表,页已成为Linux中内存管理的重要单位。包括换出、回收(relcaim)、映射等操作都是基于页粒度的。
因此,描述页框的struct page自然就成为了内核中使用非常频繁、非常重要的结构体。我们看一下它是如何组成的(为了解释起见,这不是最新的内核代码):
结构页{无符号长标志; atomic_t 计数;原子_t_mapcount;结构list_head lru;结构地址空间*映射;无符号长索引; }flags代表页框的状态或属性,包括PG_active、PG_dirty、与内存回收相关的PG_writeback、PG_reserved、PG_locked、PG_highmem等。其实flags扮演着多种角色,它还有其他用途,将下面进行介绍。 count 代表引用计数。当计数值为0时,可以释放页框;如果不为0,则表示该页正在被进程或内核使用,计数值可以通过调用page_count()获得。 _mapcount表示该页框被映射的次数,即有多少个页表项包含该页框的PFN。 lru 是“最近最少使用”的缩写。根据页框的活跃程度(使用频率),可回收的页框要么挂在active_list双向链表上,要么挂在inactive_list双向链表上,作为页面回收选择的依据。lru 包含指向链表中前一个和后一个节点的指针(参考这篇文章)。如果一个页面属于某个文件(即在页面缓存中),则映射指向该文件inode对应的address_space(虽然这个结构体称为address_space,但它并不是进程地址空间中的地址空间),索引代表地址空间。文件内页面的偏移量(以页面大小为单位)。有了文件的inode和索引,只有当该页面的内容需要与外部磁盘/flash上的相应部分同步时,才能找到具体的文件位置。如果页面是匿名的,则映射指向代表交换缓存的swapper_space,索引是swapper_space中的偏移量。
事实上,最新的Linux版本的struct page实现现在大量使用了union,即同一个元素在不同的场景下有不同的含义。这是因为每个页框都需要一个struct page 来描述它。一个页框占用4KB,一个struct page占用32字节。所有struct page消耗的内存占整个系统内存的32/4096,不到1%虽小,但对于4GB物理内存的系统来说,仅这一项的最大消耗就可以达到30MB以上。
如果可以在struct page中节省4个字节,则可以节省超过4 MB的内存空间。因此,这个结构的设计一定是非常精密的。您不能仅仅因为还需要一种场景而向结构页面添加一个。元素应尽可能地重复使用。
需要注意的是,struct page描述和管理的是这4KB物理内存,它并不关注这块内存中的数据变化。
2.2 Zone
每个节点的内存被划分为多个块,称为zone,代表内存中的一个区域。区域由struct_zone_t 结构描述。区域的主要类型包括ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。 ZONE_DMA位于内存空间的低端,被一些较旧的ISA设备使用。
ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的高端部分,很多内核操作只能在ZONE_NORMAL中执行。
由于硬件限制,内核无法对所有页框使用相同的处理方法,因此它将具有相同属性的页框分组到一个区域中。区域的划分与硬件有关,并且对于不同的处理器架构可能有所不同。
例如,在i386中,一些使用DMA的设备只能访问0~16MB的物理空间,因此0~16MB被划分为ZONE_DMA。 ZONE_HIGHMEM适用于需要访问的物理地址空间大于虚拟地址空间且无法建立直接映射的场景。除了这两个特殊区域外,物理内存的其余部分都是ZONE_NORMAL。
例如,在X86中,zone的物理地址如下:
类型
地址范围
区域_DMA
第一个16MB 内存
区域_正常
16MB - 896MB
ZONE_HIGHMEM
896 MB 或更多
Zone由struct zone_t描述,它跟踪页框使用情况、空闲区域和锁等信息。具体描述如下:
typedef struct zone_struct { spinlock_t 锁;无符号长free_pages;无符号长pages_min、pages_low、pages_high; int 需要平衡; free_area_t free_area[MAX_ORDER]; wait_queue_head_t * wait_table;无符号长等待表大小;无符号长wait_table_shift; struct pglist_data *zone_pgdat;结构页*zone_mem_map;无符号长zone_start_paddr;无符号长zone_start_mapnr;字符*名称;无符号长尺寸;} zone_t;在某些其他处理器架构中,可能不需要ZONE_DMA,并且ZONE_HIGHMEM 可能不可用。例如,在64位x64中,由于内核虚拟地址空间足够大,因此不再需要ZONE_HIGH映射。然而,为了区分使用32位地址的DMA应用和使用64位地址的DMA应用,在64位系统中设置了ZONE_DMA32和ZONE_DMA。
因此,同一个ZONE_DMA对于32位系统和64位系统具有不同的含义。 ZONE_DMA32 仅对64 位系统有意义。对于32位系统来说,它相当于ZONE_DMA,没有单独的含义。
此外,还有防止内存碎片的ZONE_MOVABLE和支持设备热插拔的ZONE_DEVICE。可以使用“cat /proc/zoneinfo |grep Node”命令查看系统中包含的区域类型。
[rongtao@toa ~]$ cat /proc/zoneinfo |grep NodeNode 0, zone DMANode 0, zone DMA32[rongtao@toa ~]$ 该结构体中的主要字段解释如下。
当系统中可用内存较少时,kswapd将被唤醒并执行页面交换。如果内存压力很大,进程会同步释放内存。如前所述,每个区域都有三个阈值,称为pages_low、pages_min和pages_high,用于跟踪该区域的内存压力。 page_min中的页框数是由内存初始化free_area_init_core函数根据zone中页框的比例计算出来的。最小值为20 页,最大值一般为255 页。当达到pages_min时,分配器将以同步方式执行kswapd的工作;当空闲页面数达到pages_low时,kswapd被buddy分配器唤醒,开始释放页面;当达到pages_high时,kswapd将被唤醒,kswapd不会考虑如何平衡区域,直到有pages_high空闲页面。一般情况下,pages_high的默认值为pages_min的3倍。
Linux存储管理的这种分层结构可以有效地将ACPI的SRAT和SLIT信息映射到Node和Zone,从而克服传统Linux中扁平结构无法体现NUMA架构的缺点。当任务请求分配内存时,Linux采用本地节点分配策略。它首先在自己的节点中搜索空闲页面;如果没有空闲页,则在相邻节点中查找空闲页;如果不存在,则在远程节点中搜索空闲页面。查找目标中的空闲页面,从而优化操作系统级别的内存访问性能。
虽然Zone是用来管理物理内存的,但是Zone之间并没有物理上的划分。它只是Linux为了方便管理而做出的逻辑划分。 Zone在Linux中用struct zone表示(为了便于说明,下面调整了结构体中元素的顺序):
结构体区域{ spinlock_t 锁;无符号长跨度页面;无符号长present_pages;无符号长nr_reserved_highatomic; atomic_long_t 管理页面; struct free_area free_area[MAX_ORDER];无符号长_watermark[NR_WMARK];长lowmem_reserve[MAX_NR_ZONES]; atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];无符号长zone_start_pfn ; struct pglist_data *zone_pgdat;结构页*zone_mem_map; }lock 是一个自旋锁,用于防止对结构体区域的并行访问。它只能保护struct结构区,而不能保护整个区的所有页面。 spanned_pages 是该区域中包含的页框总数。在某些架构中(例如Sparc),区域中可能存在没有物理页面的“空洞”,spanned_pages减去这些空洞中不存在的页面就是present_pages。 nr_reserved_highatomic是为某些场景保留的内存,management_pages是buddy内存分配系统管理的页框数量。事实上,就是present_pages减去保留页。
free_area由一个free list空闲链表组成,表示该区域中有多少个空闲页框可供分配。 _watermark有min(最小值)、low、high三种类型,可以作为启动内存回收的标准。 zone_start_pfn是区域的起始物理页号,zone_start_pfn+spanned_pages是区域的结束物理页号。 zone_pgdat 指向该区域所属的节点。 zone_mem_map指向struct page组成的mem_map数组。由于内核对该区域的访问非常频繁,为了更好地利用硬件缓存来提高访问速度,结构体区域中有一些填充位来帮助结构体元素的缓存行对齐。这与struct page对内存的小心翼翼的使用形成了鲜明的对比,因为zone的类型非常有限,一个系统中的zone并不多。结构struct zone越大也没关系。
Node Distance
上一节的例子以2个节点为例。如果有2个节点,则需要考虑不同节点之间的距离来排列节点。例如,以4个节点2个ZONE为例,每个节点的布局(例如级联4个XLP832物理CPU)的值如下:
上图中,Node0和Node2的节点距离为25,Node1和Node3的节点距离为25,其他节点距离为15。
在
三、NUMA调度器
NUMA系统中,由于本地内存的内存访问延迟低于远程内存的内存访问延迟,因此将进程分配到本地内存附近的处理器可以极大地优化应用程序的性能。由于Linux 2.4内核中的调度器只设计了一个运行队列,其可扩展性较差,在SMP平台上的性能也并不理想。当有大量正在运行的任务时,多个CPU会增加对系统资源的竞争并限制负载吞吐量。在2.5 内核的开发过程中,Ingo Molnar 编写了一个名为O(1) 的多队列调度程序。从2.5.2开始,O(1)调度器已集成到2.5内核版本中。 O(1) 是一个多队列调度程序。每个处理器都有自己的运行队列。但是,由于O(1) 调度器无法更好地感知NUMA 系统中的节点层结构,因此无法保证调度后进程仍然运行在同一个节点上。为此,Eirch Focht 开发了一种节点友好的NUMA 调度器,该调度器基于Ingo Molnar 的O(1) 调度器。 Eirch 将调度程序向后移动到2.4。
3.1 初始负载平衡
每个任务创建时都会分配一个HOME节点(所谓HOME节点就是任务获得初始内存分配的节点)。是任务创建时整个系统负载最轻的节点。因为目前Linux不支持任务内存从一个节点迁移到另一个节点,所以在任务生命周期内HOME节点保持不变。任务初始的负载均衡工作(即选择任务的HOME结构
点)缺省情况下是由exec()系统调用完成的,也可以由fork()系统调用完成。在任务结构中的node_policy域决定了最初的负载平衡选择方式。 Node_policy 平衡方式 注释 0(缺省值) do_execve() 任务由fork()创建,但不在同一个结点上运行exec() do_fork() 如果子进程有新的mm结构,选择新的HOME结点 do_fork() 选择新的HOME结点
用户评论
我一直在学习 Linux 内存管理,这次终于清楚了 NUMA 的运作机制了! 以前一直感觉系统有时会卡顿,看来和内存分配有关啊! 文章写的很详细,图解也很直观,受益匪浅!
有10位网友表示赞同!
NUMA 是个挺老的技术了,我早就在大学做嵌入式的时候接触过,那时候主要关注性能优化。 没想到现在放到云计算场景下依然重要!这篇文章很好的总结了 NUMA 的原理和应用场景,很值得一读。
有8位网友表示赞同!
这篇博文的深度真的太棒了!从 非一致内存访问架构 直到具体的内核机制都详细介绍了,让我对 NUMA 有更深的理解。 以前只是知道它是多核系统的优化技术,现在终于明白它如何运作了!
有6位网友表示赞同!
感觉这篇文章偏向理论,对于初学者可能有点难度。 我还是比较想了解 NUMA 在实际应用上的具体例子,比如在云服务平台或者大数据处理中是如何发挥作用的?
有18位网友表示赞同!
我最近在研究高性能计算集群,NUMA 技术是必不可少的。 文章分析的非常透彻,尤其对内存分配策略和 NUMA 节点的管理机制讲解得非常好。 不过,关于不同 Linux 版本对 NUMA 支持情况的变化,希望能多加补充说明。
有13位网友表示赞同!
原来如此!我一直以为所有架构都支持非一致内存访问,现在才知道这是个特点。 读完这篇文章我终于明白如何理解 NUMA 的优势和局限性了,非常有帮助!
有10位网友表示赞同!
对于硬件平台的了解太少,这篇博文对我来说有些抽象? 希望以后能分享一些具体的案例分析和配置技巧,这样更容易理解和应用。
有15位网友表示赞同!
NUMA 确实是个很重要的技术,可以大幅提升系统性能。 这篇文章虽然写的不错,但我个人觉得缺乏实战操作指南, 希望作者能够后续提供更多关于 NUMA 实际调优和应用的案例分享。
有6位网友表示赞同!
我对 Linux 内存管理一直不太了解,这篇博文让我入门了! 文章语言通俗易懂,图示也很清晰,即使我是初学者也能很容易理解 NON-UNITY ACCESS 架构的概念。 希望作者以后能更多地讲解一些更具体的例子和实践方法。
有8位网友表示赞同!
这篇文章太棒了!把 NUMA 的原理解释得清清楚楚,而且结合了实际应用案例,让我茅塞顿开! 我现在终于明白为什么大型服务器都支持这个技术了!
有11位网友表示赞同!
我一直觉得 Linux 系统很强大,这篇博文更让我认识到 Linux 内存管理的精细程度。 NUMA 技术真是太厉害了,能够充分利用内存资源,提升系统性能!
有12位网友表示赞同!
读完这篇文章之后我才明白,NUMA 不是万能的,它也有自身的局限性,比如:应用程序对 NUMA 的支持等等。 这更让我意识到,在实际应用中需要根据具体情况选择合适的架构和技术方案。
有10位网友表示赞同!
我觉得这篇博文写的太深入,对于初学者来说可能有点难懂。希望作者以后可以出一篇浅显易懂的NUMA入门教程,介绍其基本原理和工作机制。
有11位网友表示赞同!
我很喜欢这篇文章的图表讲解,它让我更容易理解 NUMA 的概念。 但是,我觉得文章还缺少一些具体的实践案例,例如是如何在实际的 Linux 系统中配置和使用 NUMA 技术。
有12位网友表示赞同!
NUMA 是个非常重要的技术,对于开发高性能应用程序来说至关重要。 这篇文章很好的讲解了这个主题,但是我希望作者能够补充一些关于如何在实际应用中诊断和解决 NUMA 相关的性能问题的内容。
有7位网友表示赞同!
我对 Linux 内存管理一直很感兴趣,这篇博文让我对NUMA有了更深入的理解。 我特关注文章中提到的 NUMA 算法和策略,这些内容对我未来开发高性能应用非常有用。
有11位网友表示赞同!
这篇文章太长了!我快速浏览了一下,主要了解到 NON-UNITY ACCESS 架构的概念和基本的原理。希望作者以后能将博文分成几个小章节,更方便阅读和理解。
有15位网友表示赞同!
我对 NUMA 本身很感兴趣,但是这篇博文的写作风格有些枯燥乏味,缺乏生动的案例或具体的应用场景描述,让我不容易产生共鸣。
有14位网友表示赞同!