内核解读之内存管理内存模型

Posted 奇妙之二进制

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核解读之内存管理内存模型相关的知识,希望对你有一定的参考价值。

文章目录

1、基本术语

在介绍内存模型之前需要了解一些基本的知识。

1、什么是page frame?

在linux操作系统中,物理内存被分成一页页的page frame来管理,具体page frame size是多少是和硬件以及linux系统配置相关的,4k是最经典的设定,也就是一个page frame表示4K的物理内存。我们针对每一个物理的page frame建立一个struct page的数据结构来跟踪每一个物理页面的使用情况:是用于内核的正文段?还是用于进程的页表?是用于各种file cache还是处于free状态……

2、什么是PFN?

对于一个计算机系统,其整个物理地址空间应该是从0开始,到实际系统能支持的最大物理空间为止的一段地址空间。在ARM系统中,假设物理地址是32个bit,那么其物理地址空间就是4G,在ARM64系统中,如果支持的物理地址bit数目是48个,那么其物理地址空间就是256T。当然,实际上这么大的物理地址空间并不是都用于内存,有些也属于I/O空间(当然,有些cpu arch有自己独立的io address space)。因此,内存所占据的物理地址空间应该是一个有限的区间,不可能覆盖整个物理地址空间。

PFN是page frame number的缩写,所谓page frame,是针对物理内存而言的,把物理内存分成一个个的page size的区域,并且给每一个page 编号,这个号码就是PFN。假设物理内存从0地址开始,那么PFN等于0的那个页帧就是0地址(物理地址)开始的那个page。假设物理内存从x地址开始,那么第一个页帧号码就是(x>>PAGE_SHIFT)。

PAGE_SHIFT表示以2的幂为单位页的大小(例如对于4K大小的页,PAGE_SHIFT等于12)。

Linux内核支持的两种内存模型:

CONFIG_FLATMEM(平坦内存模型)
CONFIG_SPARSEMEM_VMEMMAP(稀疏的内存模型)

2、FLATMEM(平坦内存模型)

如果物理内存空间是一个连续的,没有空洞的地址空间,那么这种计算机系统的内存模型就是Flat memory,如图所示,我们将内存分成一页一页的Page frame,描述page frame的结构体page组成一个数组mem_map,通过将地址转换成页帧号(即该地址对应的page在page数组中的索引)可以取得该地址对应的page,就可以访问任何一页物理内存。

在flat memory的情况下,PFN(page frame number)和mem_map数组index的关系是线性的(有一个固定偏移,如果内存的起始物理地址等于0,那么PFN就是数组index)。

UMA+flat memory的情况:

NUMA+flat memory的情况:

需要强调的是struct page所占用的内存位于直接映射(directly mapped)区间,因此操作系统不需要再为其建立page table。

一个node的内存空间可能是不连续的,即由多段内存组成,单个node的page frames可能是这样的:

平铺式内存组织page的缺点就是,当存在内存空洞时,仍要给page分配内存空间,造成浪费。进一步看,每个page结构体需要占用64字节空间,假设内核配置的页大小为4k,则在没有空洞情况下该结构就需要占用约1.56%(64/4096)的总内存。若内存地址之间的空洞很大,则其浪费的内存将会非常惊人。

同时对于numa系统,cpu访问不同节点内存的速度不同,将page结构体分配到某一个特定节点,则不与该节点绑定的cpu访问这些数据的效率显然也会有问题。

因此作为内核对上述问题的解决方案,稀疏内存模型开始闪亮登场。

3、SPARSEMEM稀疏内存模型

不连续的物理内存肯定是由一块块连续的物理内存组成的,一块连续的物理内存由struct mem_section表示。

如果没有启动64K页大小,一个section size的大小一般是128MB,启用了64K页,一个section的大小是512MB。按照128MB来算,一个section有1024 * 32个页。

arch/arm64/include/asm/sparsemem.h:

/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2012 ARM Ltd.
 */
#ifndef __ASM_SPARSEMEM_H
#define __ASM_SPARSEMEM_H

#define MAX_PHYSMEM_BITS	CONFIG_ARM64_PA_BITS

/*
 * Section size must be at least 512MB for 64K base
 * page size config. Otherwise it will be less than
 * (MAX_ORDER - 1) and the build process will fail.
 */
#ifdef CONFIG_ARM64_64K_PAGES
#define SECTION_SIZE_BITS 29

#else

/*
 * Section size must be at least 128MB for 4K base
 * page size config. Otherwise PMD based huge page
 * entries could not be created for vmemmap mappings.
 * 16K follows 4K for simplicity.
 */
#define SECTION_SIZE_BITS 27
#endif /* CONFIG_ARM64_64K_PAGES */

#endif

下图画了128MB的section示意图:

这样如果某个section的地址都位于空洞中,我们就不需要为其分配page结构体,而且不同section对应的page结构体内存也不必是连续的。此时它们之间的组织关系可变为下图所示的形式:

看起来前面的两个问题都被完美地解决了:
(1)由于不再为空洞建立page结构体,内存浪费处于可控状态。
(2)每个section的page结构体内存可独立分配,这样就可以方便地将其放到最优的numa节点中,内存访问速度的问题也得到解决

极致稀疏内存模式page的mem_section和page的映射关系:

整个连续的物理地址空间是按照一个section一个section来切断的,每一个section内部,其memory是连续的(即符合flat memory的特点),因此,mem_map的page数组依附于section结构(struct mem_section)而不是node结构了(struct pglist_data)。当然,无论哪一种memory model,都需要处理PFN和page之间的对应关系,只不过sparse memory多了一个root和section的概念,
让转换变成了PFN<—>root + section + pfn + in_page_offset<—>page。

sparse内存模型从pfn到page分成了root、section、pfn、页内偏移4级映射。

如果不是极致稀疏内存模式,root = section,下图的蓝色和红色合并。

这个图画的非常精妙(不过有一点小纰漏),这个图画的是极致稀疏内存模式:

顶层是root,一个root包含SECTIONS_PER_ROOT个sections。

如果不是极致sparse内存模式,所有的mem_section由一个二维数组管理,大小是NR_SECTION_ROOTS * SECTIONS_PER_ROOT,一个root只放一个mem_section,即SECTIONS_PER_ROOT=1,即所有mem_section放在了一个连续的空间。

如果是极致sparse内存模式,用一个page的大小来存放一个root的所有mem_section,也就是一个root有SECTIONS_PER_ROOT个section。

#define SECTIONS_PER_ROOT       (PAGE_SIZE / sizeof (struct mem_section))

一个root的sections是连续存放的,即放在一个page内,但root并不需要连续存放,mem_section是个指针数组。

#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif

我们来算下SECTIONS_PER_ROOT是多大。mem_section包含了两个指针,大小是16字节:

struct mem_section 
	/*
	 * This is, logically, a pointer to an array of struct
	 * pages.  However, it is stored with some other magic.
	 * (see sparse.c::sparse_init_one_section())
	 *
	 * Additionally during early boot we encode node id of
	 * the location of the section here to guide allocation.
	 * (see sparse.c::memory_present())
	 *
	 * Making it a UL at least makes someone do a cast
	 * before using it wrong.
	 */
	unsigned long section_mem_map;

	struct mem_section_usage *usage;
#ifdef CONFIG_PAGE_EXTENSION
	/*
	 * If SPARSEMEM, pgdat doesn't have page_ext pointer. We use
	 * section. (see page_ext.h about this.)
	 */
	struct page_ext *page_ext;
	unsigned long pad;
#endif
	/*
	 * WARNING: mem_section must be a power-of-2 in size for the
	 * calculation and use of SECTION_ROOT_MASK to make sense.
	 */
;

一个page按照4k(2的12次方)计算,SECTIONS_PER_ROOT需要 12 - 4 = 8 位来表示,所以上面画的图是对的。另外,上图有个小纰漏,section的位数应该是27,因为画的是4K的页大小,如果是29,页大小是64K。

SECTIONS_SHIFT表示为了描述MAX_PHYSMEM_BITS位的物理内存,需要SECTIONS_SHIFT位来描述SECTIONS。

换言之,如果一个物理地址的高SECTIONS_SHIFT位用来

#ifdef CONFIG_SPARSEMEM
#include <asm/sparsemem.h>
#define SECTIONS_SHIFT	(MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)
#else
#define SECTIONS_SHIFT	0
#endif

NR_MEM_SECTIONS表示为了描述MAX_PHYSMEM_BITS位的物理内存所需的section个数。

#define NR_MEM_SECTIONS		(1UL << SECTIONS_SHIFT)

以上是关于内核解读之内存管理内存模型的主要内容,如果未能解决你的问题,请参考以下文章

内核解读之内存管理内存模型

内核解读之内存管理内存管理三级架构之内存结点node

内核解读之内存管理内存管理三级架构之内存结点node

内核解读之内存管理CPU体系架构UMA和NUMA

内核解读之内存管理CPU体系架构UMA和NUMA

内核解读之内存管理内存管理三级架构之page