物理内存管理

启动前期物理内存

在Linux启动时,物理内存整个系统是怎么被建立起来的。

比如一块ARM开发板配有1GB DDR,Linux内核也只是一个通用程序,
不能提前指导硬件内存布局,这部分由Bootloader / Firmware通过设备树将
硬件内存信息传递给内核。

当系统正常运行后,物理页主要由Buddy System管理。
这里就有一个问题,像初始化Buddy系统,必须指导物理内存布局;像用Buddy系统初始化物理内存,Buddy还没初始化。
于是又在早期引入memblock,负责Linux启动早期的物理地址区间管理器,同时提供简单的早期内存分配能力。
随后以页为单位管理物理内存。最后进入Buddy System。

内核是以页为基本单位对物理内存进行管理的,每页大小为 4K。一页大小的内存块在内核中用 struct page 结构体来进行管理,struct page 中封装了每页内存块的状态信息,比如:组织结构,使用信息,统计信息,以及与其他结构的关联映射信息等。

而为了快速索引到具体的物理内存页,内核为每个物理页 struct page 结构体定义了一个索引编号:PFN(Page Frame Number)。PFN 与 struct page 是一一对应的关系。

现在DTB里面携带了内存信息,Bootloader怎样把DTB交给ARM64 Linux?
不同的架构都有自己的head.S,看arm结构的

1
arch/arm64/kernel/head.S

代码注释里面有对应的解释

1
x0 = physical address to the FDT blob

x0存放的就是Bootloader阶段传过来的设备树存放的地址。

整个传递初始化流程如下

memblock早期管理内存系统

内核初始化完成后,物理内存的分配和回收都是由buddy系统和slab分类器管理,
在初始化阶段物理内存的分配和释放都是由memblock管理的。

1
2
3
4
5
6
7
struct memblock {
bool bottom_up; /* is bottom up direction? */
phys_addr_t current_limit;
struct memblock_type memory; // 系统RAM集合
struct memblock_type reserved; // 已占用区域集合
};
extern struct memblock memblock;

从定义就看出来分为两大部分。有了对象接下来就是一些操作函数了。
并且定义了一个全局变量。

1
2
3
4
5
6
7
struct memblock_type {
unsigned long cnt;
unsigned long max;
phys_addr_t total_size;
struct memblock_region *regions;
char *name;
};

一个memblock_type类型包含多个regions,对应物理内存区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct memblock_region {
/**
base = 0x40000000;
size = 0x40000000;
end = base + size;
[0x40000000, 0x80000000)
*/
phys_addr_t base;
phys_addr_t size;
enum memblock_flags flags; // region属性
#ifdef CONFIG_NUMA
int nid;
#endif
};

用于描述一段连续的物理内存分布,通过base+size的形式描述。也就是memblock管理的最小系统。

根据上面的数据结构,memblock通过memblock_type管理物理内存,每个memblock_type内存又很多memblock_region,
记录一段物理内存的信息,包括起始地址,大小和属性等,接下来看一些操作。首先是对象初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS];
struct memblock memblock __initdata_memblock = {
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_MEMORY_REGIONS,
.memory.name = "memory",

.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS,
.reserved.name = "reserved",

.bottom_up = false,
.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};

默认regions数量为128,即最大内存块个数。

在进行管理的时候就是对物理内存进行添加和移除。
memblock_add_range将进行一块物理内存的添加注册。
memblock_reserve将一块内存保留。

上面说了一大堆,都是在描述物理内存的分布和信息,真正执行物理内存申请的是memblock_alloc。
先看怎么使用的

1
2
3
4
5
6
void *ptr;

ptr = memblock_alloc(16 * 1024, 4096);
if (!ptr) {
/* 分配失败 */
}

上面的意思就是分配一段至少16 KiB的连续物理内存,并且起始地址必须按照4 KiB对齐。

1
2
3
4
5
6
7
8
static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
{
return memblock_alloc_try_nid(size, // 申请的字节数
align, // 起始地址要求
MEMBLOCK_LOW_LIMIT, // 搜索范围最低的物理地址
MEMBLOCK_ALLOC_ACCESSIBLE,
NUMA_NO_NODE);
}

会继续调用memblock_alloc_try_nid,memblock_find_in_range_node等包装函数。

与分配对应的就是memblock_free。

struct page

前面描述的主要是物理内存的分布,后面主要进入怎么从物理内存进入单页。
之前说过,内核以页为单位进行物理内存管理,将物理内存划分为一块一块的物理页,
每个页的大小为4KB。也就是我们的管理方式从region下降到page frame。
为了快速定位具体的物理页,每个物理页都有一个PFN(Page Frame Number),类似数组一样。

假设页大小是 4 KiB,那么每个物理页框中有4096个字节,使用页内偏移指向页内具体的字节。

两者的转换关系如下

具体转换过程受物理内存模型影响,物理内存模型表示内核中组织管理这些物理内存页 struct page 的方式。

  1. FLATMEM平坦内存模型
    特点:物理内存连续。
    既然物理内存连续,那么物理地址也连续,使用一个数组就能维护。
    示意如下:

内核中使用struct page *mem_map进行这些物理内存的组织,数据的序号就是物理页对应的PFN。

1
2
3
4
#if defined(CONFIG_FLATMEM)
#define __pfn_to_page(pfn) (mem_map + ((pfn)-ARCH_PFN_OFFSET))
#define __page_to_pfn(page) ((unsigned long)((page)-mem_map) + ARCH_PFN_OFFSET)
#endif

ARCH_PFN_OFFSET 是 PFN 的起始偏移量

  1. 普通SPARSEMEM模型
    物理内存往往是不连续的,不连续的物理内存会形成内存空洞,如果为内存空洞也分配
    struct page就会咋成很大的浪费。采用另一种管理方式,就是把连续的部分抽离出来
    表示为一个section进行管理,示意图如下:

  1. SPARSEMEM_VMEMMAP模型
    用一块连续的虚拟地址空间vmemmap管理struct page

上面都是从软件角度看的,我们看一下真实的物理内存情况。

上面的图表示的一致性内存访问 UMA(Uniform Memory Access),所有的 CPU 访问内存都要过总线,而且距离都是一样的,由于所有 CPU 对内存的访问距离都是一样的,所以在 UMA 架构下所有 CPU 访问内存的速度都是一样的。这种访问模式称为 SMP(Symmetric multiprocessing),即对称多处理器。

相对的另一种模式非一致性内存访问 NUMA 架构

内核通过struct pglist_data数据结构描述NUMA节点,并使用一个全局数组管理所有的Node节点。
数组大小为MAX_NUMNODES 。

1
extern struct pglist_data *node_data[];

整个数据结构的核心如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES]; // Zone分布

struct zonelist
node_zonelists[MAX_ZONELISTS];

int nr_zones;

#ifdef CONFIG_FLATMEM
struct page *node_mem_map;
#endif

unsigned long node_start_pfn;
unsigned long node_present_pages;
unsigned long node_spanned_pages;

int node_id; // NUMA节点id

wait_queue_head_t kswapd_wait;
struct task_struct *kswapd; // 回收线程
int kswapd_order;
enum zone_type kswapd_highest_zoneidx;

struct lruvec __lruvec;

/* Node级统计、内存规整等其他字段 */
} pg_data_t;

pg_data_t是Linux用于描述一个内存Node的核心数据结构。

一个 Node 中的物理内存,被划分为多个Zone

每个Zone都维护自己的Buddy空闲链表。

其分类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA, //为DMA寻址范围很小的设备保留低地址内存
#endif

#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32, //为只能访问32位物理地址的DMA设备提供内存
#endif

ZONE_NORMAL, // CPU可以直接正常访问的普通内存

#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM, // CPU不能长期直接映射,需要临时映射的高端内存
#endif

ZONE_MOVABLE, // 主要存放可迁移页面,方便内存规整和热插拔

#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE, // 描述某些设备内存,不属于普通系统RAM的典型用法
#endif

__MAX_NR_ZONES
};

说了这么多不过是把物理内存分为不同的粒度进行管理
Node,Zone和Page的关系如下

  • Node:决定内存离CPU的距离
  • Zone:决定内存的限制
  • Page:具体的物理页

在代码上的关系

一个Zone管理一段PFN范围,源码

1
2
3
4
5
6
7
8
9
10
11
struct zone {
...

unsigned long zone_start_pfn; // Zone管理的起始PFN

atomic_long_t managed_pages; // 真正交给页分配器管理的页面数量
unsigned long spanned_pages; // Zone覆盖的PFN跨度
unsigned long present_pages; // Zone范围内实际存在的物理页数量

...
};

空闲页的管理受结构体影响free_area

1
2
3
4
5
struct zone {
...
struct free_area free_area[MAX_ORDER];
...
};

数组中每一个元素管理统一大小页的空闲块,是Buddy System的核心数据结构。
数据下标记为order,则每个元素管理的空闲块的大小为2^order个页面。
每个空闲快用链表链接。

Buddy System的核心职责是:
按照不同大小,组织每个Zone中的连续空闲物理页块。
它不是把所有空闲页都看成相同的单页,而是组织成2^order页。

1
2
3
4
5
6
7
8
struct zone
└── free_area[MAX_ORDER]
├── free_area[0] → 1 页连续块: [页] [页] ...
├── free_area[1] → 2 页连续块: [页][页] ...
├── free_area[2] → 4 页连续块: [页][页][页][页] ...
├── free_area[3] → 8 页连续块: [页][页][页][页][页][页][页][页] ...
└── ...
free_area[order] → 每块有 2^order 个连续物理页

物理内存管理
https://cj0510.github.io/2026/09/14/Linux Kernel/Memory Management/物理内存/
作者
CJ1018
发布于
2026年9月14日
许可协议
CC BY-NC-SA 4.0