23 | 物理内存管理(上):会议室管理员如何分配会议室?
下载APP
关闭
渠道合作
推荐作者
23 | 物理内存管理(上):会议室管理员如何分配会议室?
2019-05-20 刘超 来自北京
《趣谈Linux操作系统》
课程介绍
讲述:刘超
时长19:09大小17.55M
前一节,我们讲了如何从项目经理的角度看内存,看到的是虚拟地址空间,这些虚拟的地址,总是要映射到物理的页面。这一节,我们来看,物理的页面是如何管理的。
物理内存的组织方式
前面咱们讲虚拟内存,涉及物理内存的映射的时候,我们总是把内存想象成它是由连续的一页一页的块组成的。我们可以从 0 开始对物理页编号,这样每个物理页都会有个页号。
由于物理地址是连续的,页也是连续的,每个页大小也是一样的。因而对于任何一个地址,只要直接除一下每页的大小,很容易直接算出在哪一页。每个页有一个结构 struct page 表示,这个结构也是放在一个数组里面,这样根据页号,很容易通过下标找到相应的 struct page 结构。
如果是这样,整个物理内存的布局就非常简单、易管理,这就是最经典的平坦内存模型(Flat Memory Model)。
我们讲 x86 的工作模式的时候,讲过 CPU 是通过总线去访问内存的,这就是最经典的内存使用方式。
在这种模式下,CPU 也会有多个,在总线的一侧。所有的内存条组成一大片内存,在总线的另一侧,所有的 CPU 访问内存都要过总线,而且距离都是一样的,这种模式称为 SMP(Symmetric multiprocessing),即对称多处理器。当然,它也有一个显著的缺点,就是总线会成为瓶颈,因为数据都要走它。
为了提高性能和可扩展性,后来有了一种更高级的模式,NUMA(Non-uniform memory access),非一致内存访问。在这种模式下,内存不是一整块。每个 CPU 都有自己的本地内存,CPU 访问本地内存不用过总线,因而速度要快很多,每个 CPU 和内存在一起,称为一个 NUMA 节点。但是,在本地内存不足的情况下,每个 CPU 都可以去另外的 NUMA 节点申请内存,这个时候访问延时就会比较长。
这样,内存被分成了多个节点,每个节点再被分成一个一个的页面。由于页需要全局唯一定位,页还是需要有全局唯一的页号的。但是由于物理内存不是连起来的了,页号也就不再连续了。于是内存模型就变成了非连续内存模型,管理起来就复杂一些。
这里需要指出的是,NUMA 往往是非连续内存模型。而非连续内存模型不一定就是 NUMA,有时候一大片内存的情况下,也会有物理内存地址不连续的情况。
后来内存技术牛了,可以支持热插拔了。这个时候,不连续成为常态,于是就有了稀疏内存模型。
节点
我们主要解析当前的主流场景,NUMA 方式。我们首先要能够表示 NUMA 节点的概念,于是有了下面这个结构 typedef struct pglist_data pg_data_t,它里面有以下的成员变量:
每一个节点都有自己的 ID:node_id;
node_mem_map 就是这个节点的 struct page 数组,用于描述这个节点里面的所有的页;
node_start_pfn 是这个节点的起始页号;
node_spanned_pages 是这个节点中包含不连续的物理内存地址的页面数;
node_present_pages 是真正可用的物理页面的数目。
例如,64M 物理内存隔着一个 4M 的空洞,然后是另外的 64M 物理内存。这样换算成页面数目就是,16K 个页面隔着 1K 个页面,然后是另外 16K 个页面。这种情况下,node_spanned_pages 就是 33K 个页面,node_present_pages 就是 32K 个页面。
每一个节点分成一个个区域 zone,放在数组 node_zones 里面。这个数组的大小为 MAX_NR_ZONES。我们来看区域的定义。
ZONE_DMA 是指可用于作 DMA(Direct Memory Access,直接内存存取)的内存。DMA 是这样一种机制:要把外设的数据读入内存或把内存的数据传送到外设,原来都要通过 CPU 控制完成,但是这会占用 CPU,影响 CPU 处理其他事情,所以有了 DMA 模式。CPU 只需向 DMA 控制器下达指令,让 DMA 控制器来处理数据的传送,数据传送完毕再把信息反馈给 CPU,这样就可以解放 CPU。
对于 64 位系统,有两个 DMA 区域。除了上面说的 ZONE_DMA,还有 ZONE_DMA32。在这里你大概理解 DMA 的原理就可以,不必纠结,我们后面会讲 DMA 的机制。
ZONE_NORMAL 是直接映射区,就是上一节讲的,从物理内存到虚拟内存的内核区域,通过加上一个常量直接映射。
ZONE_HIGHMEM 是高端内存区,就是上一节讲的,对于 32 位系统来说超过 896M 的地方,对于 64 位没必要有的一段区域。
ZONE_MOVABLE 是可移动区域,通过将物理内存划分为可移动分配区域和不可移动分配区域来避免内存碎片。
这里你需要注意一下,我们刚才对于区域的划分,都是针对物理内存的。
nr_zones 表示当前节点的区域的数量。node_zonelists 是备用节点和它的内存区域的情况。前面讲 NUMA 的时候,我们讲了 CPU 访问内存,本节点速度最快,但是如果本节点内存不够怎么办,还是需要去其他节点进行分配。毕竟,就算在备用节点里面选择,慢了点也比没有强。
既然整个内存被分成了多个节点,那 pglist_data 应该放在一个数组里面。每个节点一项,就像下面代码里面一样:
区域
到这里,我们把内存分成了节点,把节点分成了区域。接下来我们来看,一个区域里面是如何组织的。
表示区域的数据结构 zone 的定义如下:
在一个 zone 里面,zone_start_pfn 表示属于这个 zone 的第一个页。
如果我们仔细看代码的注释,可以看到,spanned_pages = zone_end_pfn - zone_start_pfn,也即 spanned_pages 指的是不管中间有没有物理内存空洞,反正就是最后的页号减去起始的页号。
present_pages = spanned_pages - absent_pages(pages in holes),也即 present_pages 是这个 zone 在物理内存中真实存在的所有 page 数目。
managed_pages = present_pages - reserved_pages,也即 managed_pages 是这个 zone 被伙伴系统管理的所有的 page 数目,伙伴系统的工作机制我们后面会讲。
per_cpu_pageset 用于区分冷热页。什么叫冷热页呢?咱们讲 x86 体系结构的时候讲过,为了让 CPU 快速访问段描述符,在 CPU 里面有段描述符缓存。CPU 访问这个缓存的速度比内存快得多。同样对于页面来讲,也是这样的。如果一个页被加载到 CPU 高速缓存里面,这就是一个热页(Hot Page),CPU 读起来速度会快很多,如果没有就是冷页(Cold Page)。由于每个 CPU 都有自己的高速缓存,因而 per_cpu_pageset 也是每个 CPU 一个。
页
了解了区域 zone,接下来我们就到了组成物理内存的基本单位,页的数据结构 struct page。这是一个特别复杂的结构,里面有很多的 union,union 结构是在 C 语言中被用于同一块内存根据情况保存不同类型数据的一种方式。这里之所以用了 union,是因为一个物理页面使用模式有多种。
第一种模式,要用就用一整页。这一整页的内存,或者直接和虚拟地址空间建立映射关系,我们把这种称为匿名页(Anonymous Page)。或者用于关联一个文件,然后再和虚拟地址空间建立映射关系,这样的文件,我们称为内存映射文件(Memory-mapped File)。
如果某一页是这种使用模式,则会使用 union 中的以下变量:
struct address_space *mapping 就是用于内存映射,如果是匿名页,最低位为 1;如果是映射文件,最低位为 0;
pgoff_t index 是在映射区的偏移量;
atomic_t _mapcount,每个进程都有自己的页表,这里指有多少个页表项指向了这个页;
struct list_head lru 表示这一页应该在一个链表上,例如这个页面被换出,就在换出页的链表中;
compound 相关的变量用于复合页(Compound Page),就是将物理上连续的两个或多个页看成一个独立的大页。
第二种模式,仅需分配小块内存。有时候,我们不需要一下子分配这么多的内存,例如分配一个 task_struct 结构,只需要分配小块的内存,去存储这个进程描述结构的对象。为了满足对这种小内存块的需要,Linux 系统采用了一种被称为 slab allocator 的技术,用于分配称为 slab 的一小块内存。它的基本原理是从内存管理模块申请一整块页,然后划分成多个小块的存储池,用复杂的队列来维护这些小块的状态(状态包括:被分配了 / 被放回池子 / 应该被回收)。
也正是因为 slab allocator 对于队列的维护过于复杂,后来就有了一种不使用队列的分配器 slub allocator,后面我们会解析这个分配器。但是你会发现,它里面还是用了很多 slab 的字眼,因为它保留了 slab 的用户接口,可以看成 slab allocator 的另一种实现。
还有一种小块内存的分配器称为 slob,非常简单,主要使用在小型的嵌入式系统。
如果某一页是用于分割成一小块一小块的内存进行分配的使用模式,则会使用 union 中的以下变量:
s_mem 是已经分配了正在使用的 slab 的第一个对象;
freelist 是池子中的空闲对象;
rcu_head 是需要释放的列表。
页的分配
好了,前面我们讲了物理内存的组织,从节点到区域到页到小块。接下来,我们来看物理内存的分配。
对于要分配比较大的内存,例如到分配页级别的,可以使用伙伴系统(Buddy System)。
Linux 中的内存管理的“页”大小为 4KB。把所有的空闲页分组为 11 个页块链表,每个块链表分别包含很多个大小的页块,有 1、2、4、8、16、32、64、128、256、512 和 1024 个连续页的页块。最大可以申请 1024 个连续页,对应 4MB 大小的连续内存。每个页块的第一个页的物理地址是该页块大小的整数倍。
第 i 个页块链表中,页块中页的数目为 2^i。
在 struct zone 里面有以下的定义:
MAX_ORDER 就是指数。
当向内核请求分配 (2^(i-1),2^i]数目的页块时,按照 2^i 页块请求处理。如果对应的页块链表中没有空闲页块,那我们就在更大的页块链表中去找。当分配的页块中有多余的页时,伙伴系统会根据多余的页块大小插入到对应的空闲页块链表中。
例如,要请求一个 128 个页的页块时,先检查 128 个页的页块链表是否有空闲块。如果没有,则查 256 个页的页块链表;如果有空闲块的话,则将 256 个页的页块分成两份,一份使用,一份插入 128 个页的页块链表中。如果还是没有,就查 512 个页的页块链表;如果有的话,就分裂为 128、128、256 三个页块,一个 128 的使用,剩余两个插入对应页块链表。
上面这个过程,我们可以在分配页的函数 alloc_pages 中看到。
alloc_pages 会调用 alloc_pages_current,这里面的注释比较容易看懂了,gfp 表示希望在哪个区域中分配这个内存:
GFP_USER 用于分配一个页映射到用户进程的虚拟地址空间,并且希望直接被内核或者硬件访问,主要用于一个用户进程希望通过内存映射的方式,访问某些硬件的缓存,例如显卡缓存;
GFP_KERNEL 用于内核中分配页,主要分配 ZONE_NORMAL 区域,也即直接映射区;
GFP_HIGHMEM,顾名思义就是主要分配高端区域的内存。
另一个参数 order,就是表示分配 2 的 order 次方个页。
接下来调用 __alloc_pages_nodemask。这是伙伴系统的核心方法。它会调用 get_page_from_freelist。这里面的逻辑也很容易理解,就是在一个循环中先看当前节点的 zone。如果找不到空闲页,则再看备用节点的 zone。
每一个 zone,都有伙伴系统维护的各种大小的队列,就像上面伙伴系统原理里讲的那样。这里调用 rmqueue 就很好理解了,就是找到合适大小的那个队列,把页面取下来。
接下来的调用链是 rmqueue->__rmqueue->__rmqueue_smallest。在这里,我们能清楚看到伙伴系统的逻辑。
从当前的 order,也即指数开始,在伙伴系统的 free_area 找 2^order 大小的页块。如果链表的第一个不为空,就找到了;如果为空,就到更大的 order 的页块链表里面去找。找到以后,除了将页块从链表中取下来,我们还要把多余部分放到其他页块链表里面。expand 就是干这个事情的。area–就是伙伴系统那个表里面的前一项,前一项里面的页块大小是当前项的页块大小除以 2,size 右移一位也就是除以 2,list_add 就是加到链表上,nr_free++ 就是计数加 1。
总结时刻
对于物理内存的管理的讲解,到这里要告一段落了。这一节我们主要讲了物理内存的组织形式,就像下面图中展示的一样。
如果有多个 CPU,那就有多个节点。每个节点用 struct pglist_data 表示,放在一个数组里面。
每个节点分为多个区域,每个区域用 struct zone 表示,也放在一个数组里面。
每个区域分为多个页。为了方便分配,空闲页放在 struct free_area 里面,使用伙伴系统进行管理和分配,每一页用 struct page 表示。
课堂练习
伙伴系统是一种非常精妙的实现方式,无论你使用什么语言,请自己实现一个这样的分配系统,说不定哪天你在做某个系统的时候,就用到了。
欢迎留言和我分享你的疑惑和见解,也欢迎你收藏本节内容,反复研读。你也可以把今天的内容分享给你的朋友,和他一起学习、进步。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 20
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
22 | 进程空间管理:项目组还可以自行布置会议室
下一篇
24 | 物理内存管理(下):会议室管理员如何分配会议室?
精选留言(38)
- why2019-05-23- 物理内存组织方式 - 每个物理页由 struct page 表示 - 物理页连续, page 放入一个数组中, 称为平坦内存模型 - 多个 CPU 通过总线访问内存, 称为 SMP 对称多处理器(采用平坦内存模型, 总线成为瓶颈) - 每个 CPU 都有本地内存, 访问内存不用总线, 称为 NUMA 非一致内存访问 - 本地内存称为 NUMA 节点, 本地内存不足可以向其他节点申请 - NUMA 采用非连续内存模型,页号不连续 - 另外若内存支持热插拔,则采用稀疏内存模型 - 节点 - 用 pglist_data 表示 NUMA 节点,多个节点信息保存在 node_data 数组中 - pglist_data 包括 id,page 数组,起始页号, 总页数, 可用页数 - 节点分为多个区域 zone, 包括 DMA; 直接映射区; 高端内存区; 可移动区(避免内存碎片) - 区域 zone - 用 zone 表示; 包含第一个页页号; 区域总页数; 区域实际页数; 被伙伴系统管理的页数; 用 per_cpu_pageset 区分冷热页(热页, 被 CPU 缓存的页) - 页 - 用 struct page 表示, 有多种使用模式, 因此 page 结构体多由 union 组成 - 使用一整个页: 1) 直接和虚拟地址映射(匿名页); 2) 与文件关联再与虚拟地址映射(内存映射文件) - page 记录: 标记用于内存映射; 指向该页的页表数; 换出页的链表; 复合页, 用于合成大页; - 分配小块内存: - Linux 采用 slab allocator 技术; 申请一整页, 分为多个小块存储池, 用队列维护其状态(较复杂) - slub allocator 更简单 - slob allocator 用于嵌入式 - page 记录: 第一个 slab 对象; 空闲列表; 待释放列表 - 页分配 - 分配较大内存(页级别), 使用伙伴系统 - Linux 把空闲页分组为 11 个页块链表, 链表管理大小不同的页块(页大小 2^i * 4KB) - 分配大页剩下的内存, 插入对应空闲链表 - alloc_pages->alloc_pages_current 用 gfp 指定在哪个 zone 分配展开51
- 有铭2019-05-20还是没理解那个“伙伴系统”为何会命名为伙伴系统,没感觉到有“伙伴”的感觉
作者回复: 一整个会议室,划分为两个1/2,再划分为四个1/4,两个1/4就是伙伴呀
共 4 条评论15 - 奔跑的码仔2019-09-03老师,对于32bit的系统,如果物理内存大小小于896M,那物理内存区域还有ZONE_HIGHMEM吗?您的第26节课讲到内核处理匿名页的缺页中断处理时,会调用alloc_zeroed_user_highpage_movable去高端内存区域分配物理页,对于32bit系统,物理内存小于896M的情况,这时候伙伴系统去哪里分配物理页呢?对于64bit系统,没有高端内存区域,那上述函数去哪里分配高端内存的物理页呢?
作者回复: 小于就没有了。如果小于没有ZONE_HIGHMEM的话,就会退而求其次,在直接映射区分配,如果分配不到,就说明没有内存了。64位就是直接映射区分配。
8 - czh2019-11-25关键词:物理内存的组织+分配机制。回忆文中的图4
- 江山未2019-08-01一边读一边在脑海构想结构关系图,半天不得要领。看到最后这张图才恍然大悟。艾玛,以后先看最后这张图,少分配一点脑细胞了
作者回复: 哈哈,以后先看最后的总结图。
4 - AlexS2020-12-13来来来,经过一段时间的学习,我有明确的答案了。 它为什么叫"伙伴系统","伙伴"体现在哪。 一个page block它的buddy是唯一的,它只能和它的唯一buddy组合成大一级的page block。 Two blocks are buddies if: – They have the same size, e.g. b, – They are adjacent to each other, – the physical address of the first page of the first block is a multiple of 2 x b x PAGE_SIZE. 这里考虑的是两个buddy合成的新block的首地址要是这个新block的size的整数倍。 A split block can only be merged with its unique buddy block: 为什么呢?因为每个page block的首页的起始物理地址一定要是page block size的整数倍。 比如一个page block是16个页,那么这个page block的起始物理地址一定是16*PAGE_SIZE的整数倍。 比如 order 为0的 page 0,1,2,3。0和1是buddy,1和2就不能是buddy。因为1和2不能merge成 order 为1的 page block,因为起始地址1不是block size 2的整数倍。 下面给出如何找到一个page block的唯一buddy To find the buddy block of a page block __find_buddy_pfn(page_pfn, order) (mm/internal.h) * 1) Any buddy B1 will have an order O twin B2 which satisfies * the following equation: * B2 = B1 ^ (1 << O) * For example, if the starting buddy (buddy2) is #8 its order * 1 buddy is #10: * B2 = 8 ^ (1 << 1) = 8 ^ 2 = 10 * 同样的若是B1是10,它的order 1 buddy B2就是8. * B2 = 10 ^ (1 << 1) = 10 ^ 2 = 8展开4
- oldman2019-07-12老师,我这里有一个疑惑,内存的最大连续页是1024个页,也就是4MB的大小,我们知道在一般的应用程序中,数组都是一连串的内存,那我如果要申请一个大于4MB的数组呢,操作系统会怎么样来分配呢,期待老师解惑。
作者回复: 这是物理内存呀。应用程序操作的是虚拟内存呀,虚拟内存大了去了。
共 3 条评论3 - Leon📷2019-05-22伙伴系统的意思就是劫富济贫
作者回复: 也不是,回收内存的时候,还合并呢
3 - 淤白2020-01-31通过本篇文章,主要学习到的知识点:在当前的主流非连续内存模型 NUMA 中,将内存分成了三层(节点 -> 区域 -> 页),方便了对内存的管理;Linux 中的一页大小是 4 KB,其将空闲的页分组成了 11 个链表,当有分配内存的需求时,会从链表(大于等于分配需求,持有快最小的链表)中找出一个空闲的页块进行分配,如果没有找到空闲页块,会去更大页块的链表中进行查找,找到后进行分割等后续处理。2
- Linuxer2019-05-21每个页块的第一个页的物理地址是该页块大小的整数倍。怎么理解
作者回复: 大小为16个页框的块,其起始地址是16*2^12的倍数.
共 2 条评论2 - 王亭亭2022-03-24free_area的管理很像包里的钱,包里一些1元、2元、4元、8...2的n次方元面额的钱。每次分配内存,就是付钱和找回零钱的过程。1
- 渣渣2021-04-20有个问题请教一下,一个节点的物理内存是怎么划分为DMA,DMA32,NORMAL,HIGHMEM,MOVABLE区域的,是和虚拟内存划分一样按照地址和大小划分的吗?还有每个区域又是怎么按照可移动性分组划分为unmovable,movable,reclaimable的,也是按照地址和大小划分的吗?这个物理内存的划分和虚拟内存的划分有什么联系吗?1
- Geek_37ea992020-08-30想问一下,如果是numa结构,kernel的代码段在哪个cpu节点的内存上?还是每个cpu节点上都有一份copy?共 1 条评论1
- 注意力$2020-06-01老师,好多数据安装时都建议关闭numa,主要是觉得numa哪里不好呢
作者回复: 跨节点速度慢
1 - djfhchdh2019-12-11"ZONE_NORMAL 是直接映射区,就是上一节讲的,从物理内存到虚拟内存的内核区域,通过加上一个常量直接映射。" 这里讲的有问题,ZONE_NORMAL就是一个普通的可映射的物理内存区域,而不是直接映射区1
- williamcai2019-09-22老师,所有节点的可用内存的容量等于伙伴系统可用空间之和吗1
- 奔跑的码仔2019-08-28您好,之前看过一篇文章讲解Linux内存物理模型,里面说到了三种物理模型:平坦型、非连续型、稀疏性,看完本节后,发现基于zone的buddy系统和上面讲解的几种模型的关系,理不清了。不知道,三种物理模型是不是也是基于zone的伙伴系统呢?
作者回复: 这三种模型的区别是page不是连续的,对于zone没有影响
共 2 条评论1 - Linuxer2019-05-21有些应用会要求关闭numa那么这里numanode就为1吗?这时候是所有CPU共用一个pglist?
作者回复: 关闭就smp了
1 - │.Sk2022-10-27 来自北京老师您好, “ZONE_NORMAL 是直接映射区,就是上一节讲的,从物理内存到虚拟内存的内核区域,通过加上一个常量直接映射” 请问这种说法是否有误,ZONE_NORMAL 应该不仅仅包含虚拟地址空间中的内核部分;应该也包含用户态可分配的物理内存页吧?展开
- 一省吾身2022-10-26 来自北京本节收获: 小系统通常是UMA. 大系统才会考虑NUMA. 针对不通场景UMA和NUMA各有优劣. zone--->page(向下slab,向上page块,都是为了合理而方便地分配和使用内存) ps:伙伴系统将大量的页组织成不通大小的页块,根据需要快速分配拆分合并,维护相对比较方便.展开