启动时内存管理

早期系统初始化不能简单地使用“正常”的内存管理,因为它尚未设置好。但是仍然需要为各种数据结构分配内存,例如为物理页面分配器。

一个名为 memblock 的专用分配器执行启动时内存管理。特定于架构的初始化必须在 setup_arch() 中设置它,并在 mem_init() 函数中将其拆除。

一旦早期内存管理可用,它会提供各种用于内存分配的函数和宏。分配请求可以定向到第一个(并且可能是唯一的)节点,或者定向到 NUMA 系统中的特定节点。有些 API 变体在分配失败时会 panic,有些则不会。

Memblock 还提供了各种控制其自身行为的 API。

Memblock 概述

Memblock 是一种在早期启动期间管理内存区域的方法,此时通常的内核内存分配器尚未启动并运行。

Memblock 将系统内存视为连续区域的集合。这些集合有几种类型

  • memory - 描述内核可用的物理内存;这可能与系统中安装的实际物理内存不同,例如当内存受到 mem= 命令行参数限制时

  • reserved - 描述已分配的区域

  • physmem - 描述启动期间可用的实际物理内存,无论可能的限制和内存热插拔如何; physmem 类型仅在某些架构上可用。

每个区域由 struct memblock_region 表示,它定义了区域范围、其属性和 NUMA 系统上的 NUMA 节点 ID。每种内存类型都由 struct memblock_type 描述,其中包含一个内存区域数组以及分配器元数据。“memory”和“reserved”类型很好地包装在 struct memblock 中。此结构在构建时静态初始化。区域数组最初的大小为 “memory” 的 INIT_MEMBLOCK_MEMORY_REGIONS 和 “reserved” 的 INIT_MEMBLOCK_RESERVED_REGIONS 。“physmem” 的区域数组最初的大小为 INIT_PHYSMEM_REGIONS。 memblock_allow_resize() 允许在添加新区域期间自动调整区域数组的大小。应谨慎使用此功能,以使为区域数组分配的内存不会与应保留的区域(例如 initrd)重叠。

早期架构设置应使用 memblock_add()memblock_add_node() 函数告诉 memblock 物理内存布局。第一个函数不会将区域分配给 NUMA 节点,它适用于 UMA 系统。然而,也可以在 NUMA 系统上使用它,并在设置过程中稍后使用 memblock_set_node() 将该区域分配给 NUMA 节点。memblock_add_node() 直接执行这种分配。

一旦 memblock 设置好,就可以使用以下 API 变体之一分配内存

  • memblock_phys_alloc*() - 这些函数返回已分配内存的物理地址

  • memblock_alloc*() - 这些函数返回已分配内存的虚拟地址。

请注意,这两个 API 变体都使用有关允许的内存范围和回退方法的隐含假设。有关更详细的说明,请查阅 memblock_alloc_internal()memblock_alloc_range_nid() 函数的文档。

随着系统启动的进行,特定于架构的 mem_init() 函数会将所有内存释放给伙伴页面分配器。

除非架构启用 CONFIG_ARCH_KEEP_MEMBLOCK,否则在系统初始化完成后,memblock 数据结构(“physmem” 除外)将被丢弃。

函数和结构

以下是 memblock 数据结构、函数和宏的描述。其中一些实际上是内部的,但由于它们已记录在案,因此省略它们是很愚蠢的。此外,阅读内部函数的描述可以帮助理解底层真正发生了什么。

enum memblock_flags

内存区域属性的定义

常量

MEMBLOCK_NONE

没有特殊请求

MEMBLOCK_HOTPLUG

在早期启动期间,固件提供的内存映射中指示为热插拔系统 RAM 的内存区域(例如,稍后可能被热插拔的内存范围)。在内核命令行上设置 “movable_node” 后,尝试保持此内存区域可热插拔。不适用于早期启动后添加(“热插拔”)的 memblock。

MEMBLOCK_MIRROR

镜像区域

MEMBLOCK_NOMAP

不要添加到内核直接映射,并在内存映射中视为保留;有关更多详细信息,请参阅 memblock_mark_nomap() 的描述

MEMBLOCK_DRIVER_MANAGED

始终通过驱动程序检测和添加,并且永远不会在固件提供的内存映射中指示为系统 RAM 的内存区域。这对应于内核资源树中的 IORESOURCE_SYSRAM_DRIVER_MANAGED。

MEMBLOCK_RSRV_NOINIT

不初始化 struct pages 的内存区域(仅适用于保留区域)。

struct memblock_region

表示一个内存区域

定义:

struct memblock_region {
    phys_addr_t base;
    phys_addr_t size;
    enum memblock_flags flags;
#ifdef CONFIG_NUMA;
    int nid;
#endif;
};

成员

base

该区域的基地址

size

该区域的大小

flags

内存区域属性

nid

NUMA 节点 ID

struct memblock_type

某种类型的内存区域集合

定义:

struct memblock_type {
    unsigned long cnt;
    unsigned long max;
    phys_addr_t total_size;
    struct memblock_region *regions;
    char *name;
};

成员

cnt

区域数

max

已分配数组的大小

total_size

所有区域的大小

regions

区域数组

name

内存类型的符号名称

struct memblock

memblock 分配器元数据

定义:

struct memblock {
    bool bottom_up;
    phys_addr_t current_limit;
    struct memblock_type memory;
    struct memblock_type reserved;
};

成员

bottom_up

是自下而上的方向吗?

current_limit

当前分配限制的物理地址

memory

可用的内存区域

reserved

保留的内存区域

for_each_physmem_range

for_each_physmem_range (i, type, p_start, p_end)

遍历类型中不包含的 physmem 区域。

参数

i

用作循环变量的 u64

type

指向 memblock_type 的指针,它从迭代中排除,可以为 NULL

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

__for_each_mem_range

__for_each_mem_range (i, type_a, type_b, nid, flags, p_start, p_end, p_nid)

从 type_a 遍历 memblock 区域,而不包含在 type_b 中。如果 type_b 为 NULL,则仅遍历 type_a。

参数

i

用作循环变量的 u64

type_a

指向要遍历的 memblock_type 的指针

type_b

指向从迭代中排除的 memblock_type 的指针

nid

节点选择器,NUMA_NO_NODE 表示所有节点

flags

根据内存属性从块中选择

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

p_nid

指向范围 nid 的 int 的指针,可以为 NULL

__for_each_mem_range_rev

__for_each_mem_range_rev (i, type_a, type_b, nid, flags, p_start, p_end, p_nid)

反向遍历从 type_a 类型开始且不包含在 type_b 类型中的内存块区域。如果 type_b 为 NULL,则只遍历 type_a。

参数

i

用作循环变量的 u64

type_a

指向要遍历的 memblock_type 的指针

type_b

指向从迭代中排除的 memblock_type 的指针

nid

节点选择器,NUMA_NO_NODE 表示所有节点

flags

根据内存属性从块中选择

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

p_nid

指向范围 nid 的 int 的指针,可以为 NULL

for_each_mem_range

for_each_mem_range (i, p_start, p_end)

遍历内存区域。

参数

i

用作循环变量的 u64

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

for_each_mem_range_rev

for_each_mem_range_rev (i, p_start, p_end)

反向遍历从 type_a 类型开始且不包含在 type_b 类型中的内存块区域。如果 type_b 为 NULL,则只遍历 type_a。

参数

i

用作循环变量的 u64

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

for_each_reserved_mem_range

for_each_reserved_mem_range (i, p_start, p_end)

遍历所有预留的内存块区域

参数

i

用作循环变量的 u64

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

描述

遍历 memblock 的预留区域。在 memblock 初始化后立即可用。

for_each_mem_pfn_range

for_each_mem_pfn_range (i, nid, p_start, p_end, p_nid)

早期内存 PFN 范围迭代器

参数

i

用作循环变量的整数

nid

节点选择器,MAX_NUMNODES 表示所有节点

p_start

指向范围起始 PFN 的 ulong 指针,可以为 NULL

p_end

指向范围结束 PFN 的 ulong 指针,可以为 NULL

p_nid

指向范围 nid 的 int 的指针,可以为 NULL

描述

遍历已配置的内存范围。

for_each_free_mem_pfn_range_in_zone_from

for_each_free_mem_pfn_range_in_zone_from (i, zone, p_start, p_end)

从给定点开始,遍历特定区域的空闲内存块区域

参数

i

用作循环变量的 u64

区域

所有内存块所在的区域

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

描述

遍历特定区域中 memblock 的空闲(内存 && !预留)区域,从当前位置继续。在 memblock 初始化后立即可用。

for_each_free_mem_range

for_each_free_mem_range (i, nid, flags, p_start, p_end, p_nid)

遍历空闲的内存块区域

参数

i

用作循环变量的 u64

nid

节点选择器,NUMA_NO_NODE 表示所有节点

flags

根据内存属性从块中选择

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

p_nid

指向范围 nid 的 int 的指针,可以为 NULL

描述

遍历 memblock 的空闲(内存 && !预留)区域。在 memblock 初始化后立即可用。

for_each_free_mem_range_reverse

for_each_free_mem_range_reverse (i, nid, flags, p_start, p_end, p_nid)

反向遍历空闲的内存块区域

参数

i

用作循环变量的 u64

nid

节点选择器,NUMA_NO_NODE 表示所有节点

flags

根据内存属性从块中选择

p_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

p_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

p_nid

指向范围 nid 的 int 的指针,可以为 NULL

描述

以相反的顺序遍历 memblock 的空闲(内存 && !预留)区域。在 memblock 初始化后立即可用。

void memblock_set_current_limit(phys_addr_t limit)

设置当前分配限制,以便在启动期间将分配限制为当前可访问的范围

参数

phys_addr_t limit

新的限制值(物理地址)

unsigned long memblock_region_memory_base_pfn(const struct memblock_region *reg)

获取内存区域的最低 PFN

参数

const struct memblock_region *reg

memblock_region 结构

返回值

与内存区域相交的最低 PFN

unsigned long memblock_region_memory_end_pfn(const struct memblock_region *reg)

获取内存区域的结束 PFN

参数

const struct memblock_region *reg

memblock_region 结构

返回值

预留区域的 end_pfn

unsigned long memblock_region_reserved_base_pfn(const struct memblock_region *reg)

获取预留区域的最低 PFN

参数

const struct memblock_region *reg

memblock_region 结构

返回值

与预留区域相交的最低 PFN

unsigned long memblock_region_reserved_end_pfn(const struct memblock_region *reg)

获取预留区域的结束 PFN

参数

const struct memblock_region *reg

memblock_region 结构

返回值

预留区域的 end_pfn

for_each_mem_region

for_each_mem_region (region)

遍历内存区域

参数

区域

循环变量

for_each_reserved_mem_region

for_each_reserved_mem_region (region)

遍历预留内存区域

参数

区域

循环变量

phys_addr_t __init_memblock __memblock_find_range_bottom_up(phys_addr_t start, phys_addr_t end, phys_addr_t size, phys_addr_t align, int nid, enum memblock_flags flags)

自下而上查找空闲区域实用程序

参数

phys_addr_t start

候选范围的起始地址

phys_addr_t end

候选范围的结束地址,可以为 MEMBLOCK_ALLOC_ANYWHEREMEMBLOCK_ALLOC_ACCESSIBLE

phys_addr_t size

要查找的空闲区域大小

phys_addr_t align

要查找的空闲区域的对齐方式

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

enum memblock_flags flags

根据内存属性从块中选择

描述

memblock_find_in_range_node() 调用的实用程序,自下而上查找空闲区域。

返回值

成功时返回找到的地址,失败时返回 0。

phys_addr_t __init_memblock __memblock_find_range_top_down(phys_addr_t start, phys_addr_t end, phys_addr_t size, phys_addr_t align, int nid, enum memblock_flags flags)

自上而下查找空闲区域实用程序

参数

phys_addr_t start

候选范围的起始地址

phys_addr_t end

候选范围的结束地址,可以为 MEMBLOCK_ALLOC_ANYWHEREMEMBLOCK_ALLOC_ACCESSIBLE

phys_addr_t size

要查找的空闲区域大小

phys_addr_t align

要查找的空闲区域的对齐方式

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

enum memblock_flags flags

根据内存属性从块中选择

描述

该工具函数从 memblock_find_in_range_node() 调用,自顶向下查找空闲区域。

返回值

成功时返回找到的地址,失败时返回 0。

phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size, phys_addr_t align, phys_addr_t start, phys_addr_t end, int nid, enum memblock_flags flags)

在给定范围和节点中查找空闲区域

参数

phys_addr_t size

要查找的空闲区域大小

phys_addr_t align

要查找的空闲区域的对齐方式

phys_addr_t start

候选范围的起始地址

phys_addr_t end

候选范围的结束地址,可以为 MEMBLOCK_ALLOC_ANYWHEREMEMBLOCK_ALLOC_ACCESSIBLE

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

enum memblock_flags flags

根据内存属性从块中选择

描述

在指定的范围和节点中,查找与 align 对齐的 size 大小的空闲区域。

返回值

成功时返回找到的地址,失败时返回 0。

phys_addr_t __init_memblock memblock_find_in_range(phys_addr_t start, phys_addr_t end, phys_addr_t size, phys_addr_t align)

在给定范围内查找空闲区域

参数

phys_addr_t start

候选范围的起始地址

phys_addr_t end

候选范围的结束地址,可以为 MEMBLOCK_ALLOC_ANYWHEREMEMBLOCK_ALLOC_ACCESSIBLE

phys_addr_t size

要查找的空闲区域大小

phys_addr_t align

要查找的空闲区域的对齐方式

描述

在指定的范围内,查找与 align 对齐的 size 大小的空闲区域。

返回值

成功时返回找到的地址,失败时返回 0。

void memblock_discard(void)

如果已分配,则丢弃内存和保留的数组

参数

void

无参数

int __init_memblock memblock_double_array(struct memblock_type *type, phys_addr_t new_area_start, phys_addr_t new_area_size)

将 memblock 区域数组的大小加倍

参数

struct memblock_type *type

正在加倍的区域数组的 memblock 类型

phys_addr_t new_area_start

避免与重叠的内存范围的起始地址

phys_addr_t new_area_size

避免与重叠的内存范围的大小

描述

type 区域数组的大小加倍。如果 memblock 用于为新的保留区域数组分配内存,并且之前存在一个已分配的内存范围 [new_area_start, new_area_start + new_area_size] 等待保留,请确保新数组使用的内存不重叠。

返回值

成功返回 0,失败返回 -1。

void __init_memblock memblock_merge_regions(struct memblock_type *type, unsigned long start_rgn, unsigned long end_rgn)

合并相邻的兼容区域

参数

struct memblock_type *type

要扫描的 memblock 类型

unsigned long start_rgn

从 (start_rgn - 1) 开始扫描

unsigned long end_rgn

在 (end_rgn - 1) 结束扫描。扫描 type 并合并 [start_rgn - 1, end_rgn) 中相邻的兼容区域

void __init_memblock memblock_insert_region(struct memblock_type *type, int idx, phys_addr_t base, phys_addr_t size, int nid, enum memblock_flags flags)

插入新的 memblock 区域

参数

struct memblock_type *type

要插入的 memblock 类型

int idx

插入点的索引

phys_addr_t base

新区域的基地址

phys_addr_t size

新区域的大小

int nid

新区域的节点 ID

enum memblock_flags flags

新区域的标志

描述

将新的 memblock 区域 [base, base + size) 插入到 typeidx 位置。type 必须已经有额外的空间来容纳新区域。

int __init_memblock memblock_add_range(struct memblock_type *type, phys_addr_t base, phys_addr_t size, int nid, enum memblock_flags flags)

添加新的 memblock 区域

参数

struct memblock_type *type

要将新区域添加到的 memblock 类型

phys_addr_t base

新区域的基地址

phys_addr_t size

新区域的大小

int nid

新区域的 nid

enum memblock_flags flags

新区域的标志

描述

将新的 memblock 区域 [base, base + size) 添加到 type 中。新区域允许与现有区域重叠 - 重叠不会影响已存在的区域。在添加后,保证 type 是最小的(所有相邻的兼容区域都已合并)。

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size, int nid, enum memblock_flags flags)

在 NUMA 节点内添加新的 memblock 区域

参数

phys_addr_t base

新区域的基地址

phys_addr_t size

新区域的大小

int nid

新区域的 nid

enum memblock_flags flags

新区域的标志

描述

将新的 memblock 区域 [base, base + size) 添加到 “memory” 类型。有关模式的更多详细信息,请参阅 memblock_add_range() 的描述

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)

添加新的 memblock 区域

参数

phys_addr_t base

新区域的基地址

phys_addr_t size

新区域的大小

描述

将新的 memblock 区域 [base, base + size) 添加到 “memory” 类型。有关模式的更多详细信息,请参阅 memblock_add_range() 的描述

返回值

成功返回 0,失败返回 -errno。

bool __init_memblock memblock_validate_numa_coverage(unsigned long threshold_bytes)

检查未分配节点 ID 的内存量是否小于阈值

参数

unsigned long threshold_bytes

可以具有未分配节点 ID 的最大内存大小(以字节为单位)。

描述

有缺陷的固件可能会报告不属于任何节点的内存。检查此类内存量是否低于 threshold_bytes

返回值

成功返回 true,失败返回 false。

int __init_memblock memblock_isolate_range(struct memblock_type *type, phys_addr_t base, phys_addr_t size, int *start_rgn, int *end_rgn)

将给定范围隔离到不相交的 memblock 中

参数

struct memblock_type *type

要隔离范围的 memblock 类型

phys_addr_t base

要隔离的范围的基地址

phys_addr_t size

要隔离的范围的大小

int *start_rgn

隔离区域的起始位置的输出参数

int *end_rgn

隔离区域末尾的 out 参数

描述

遍历 type 并确保区域不会越过 [base, base + size) 定义的边界。越界的区域会在边界处分割,这可能会创建最多两个额外的区域。范围内的第一个区域的索引在 *start_rgn 中返回,范围后的第一个区域的索引在 *end_rgn 中返回。

返回值

成功返回 0,失败返回 -errno。

void __init_memblock memblock_free(void *ptr, size_t size)

释放启动内存分配

参数

void *ptr

启动内存分配的起始地址

size_t size

启动内存块的大小(以字节为单位)

描述

释放先前由 memblock_alloc_xx() API 分配的启动内存块。释放的内存不会释放给伙伴分配器。

int __init_memblock memblock_phys_free(phys_addr_t base, phys_addr_t size)

释放启动内存块

参数

phys_addr_t base

启动内存块的物理起始地址

phys_addr_t size

启动内存块的大小(以字节为单位)

描述

释放先前由 memblock_phys_alloc_xx() API 分配的启动内存块。释放的内存不会释放给伙伴分配器。

int __init_memblock memblock_setclr_flag(struct memblock_type *type, phys_addr_t base, phys_addr_t size, int set, int flag)

设置或清除内存区域的标志

参数

struct memblock_type *type

要设置/清除标志的 memblock 类型

phys_addr_t base

该区域的基地址

phys_addr_t size

该区域的大小

int set

设置或清除标志

int flag

要更新的标志

描述

此函数隔离区域 [base, base + size),并设置/清除标志

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_mark_hotplug(phys_addr_t base, phys_addr_t size)

使用标志 MEMBLOCK_HOTPLUG 标记热插拔内存。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_clear_hotplug(phys_addr_t base, phys_addr_t size)

清除指定区域的 MEMBLOCK_HOTPLUG 标志。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_mark_mirror(phys_addr_t base, phys_addr_t size)

使用标志 MEMBLOCK_MIRROR 标记镜像内存。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)

使用标志 MEMBLOCK_NOMAP 标记内存区域。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

描述

使用 MEMBLOCK_NOMAP 标记的内存区域将不会添加到物理内存的直接映射中。这些区域仍将被内存映射覆盖。内存映射中表示 NOMAP 内存帧的 struct page 将是 PageReserved()

注意

如果标记为 MEMBLOCK_NOMAP 的内存是从 memblock 分配的,则调用者必须通知 kmemleak 忽略该内存

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_clear_nomap(phys_addr_t base, phys_addr_t size)

清除指定区域的 MEMBLOCK_NOMAP 标志。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

返回值

成功返回 0,失败返回 -errno。

int __init_memblock memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size)

使用标志 MEMBLOCK_RSRV_NOINIT 标记保留的内存区域,这将导致此区域的 struct page 不被初始化。

参数

phys_addr_t base

区域的物理起始地址

phys_addr_t size

区域的大小

描述

对于使用 MEMBLOCK_RSRV_NOINIT 标记的保留内存区域,struct page 将不会被初始化。

返回值

成功返回 0,失败返回 -errno。

void __next_mem_range(u64 *idx, int nid, enum memblock_flags flags, struct memblock_type *type_a, struct memblock_type *type_b, phys_addr_t *out_start, phys_addr_t *out_end, int *out_nid)

for_each_free_mem_range() 等的下一个函数。

参数

u64 *idx

指向 u64 循环变量的指针

int nid

节点选择器,NUMA_NO_NODE 表示所有节点

enum memblock_flags flags

根据内存属性从块中选择

struct memblock_type *type_a

指向从中获取范围的 memblock_type 的指针

struct memblock_type *type_b

指向排除内存被获取的 memblock_type 的指针

phys_addr_t *out_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

phys_addr_t *out_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

int *out_nid

指向范围 nid 的 int 的指针,可以为 NULL

描述

*idx 中找到与 nid 匹配的第一个区域,填充 out 参数,并更新 *idx 以进行下一次迭代。 *idx 的低 32 位包含 type_a 的索引,高 32 位索引 type_b 中每个区域之前的区域。例如,如果 type_b 区域如下所示,

0:[0-16), 1:[32-48), 2:[128-130)

高 32 位索引以下区域。

0:[0-0), 1:[16-32), 2:[48-128), 3:[130-MAX)

由于两个区域数组都已排序,因此该函数以锁步方式推进两个索引并返回每个交集。

void __init_memblock __next_mem_range_rev(u64 *idx, int nid, enum memblock_flags flags, struct memblock_type *type_a, struct memblock_type *type_b, phys_addr_t *out_start, phys_addr_t *out_end, int *out_nid)

for_each_*_range_rev() 的通用 next 函数

参数

u64 *idx

指向 u64 循环变量的指针

int nid

节点选择器,NUMA_NO_NODE 表示所有节点

enum memblock_flags flags

根据内存属性从块中选择

struct memblock_type *type_a

指向从中获取范围的 memblock_type 的指针

struct memblock_type *type_b

指向排除内存被获取的 memblock_type 的指针

phys_addr_t *out_start

指向范围起始地址的 phys_addr_t 的指针,可以为 NULL

phys_addr_t *out_end

指向范围结束地址的 phys_addr_t 的指针,可以为 NULL

int *out_nid

指向范围 nid 的 int 的指针,可以为 NULL

描述

从 type_a 中查找下一个未在 type_b 中标记为不合适的范围。

__next_mem_range() 的反向。

int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size, struct memblock_type *type, int nid)

在 memblock 区域上设置节点 ID

参数

phys_addr_t base

要设置节点 ID 的区域的起始地址

phys_addr_t size

要设置节点 ID 的区域的大小

struct memblock_type *type

设置节点 ID 的内存块类型

int nid

要设置的节点 ID

描述

将 [base, base + size) 范围内的内存块 type 区域的 nid 设置为 nid。跨越区域边界的区域会根据需要进行拆分。

返回值

成功返回 0,失败返回 -errno。

void __init_memblock __next_mem_pfn_range_in_zone(u64 *idx, struct zone *zone, unsigned long *out_spfn, unsigned long *out_epfn)

for_each_*_range_in_zone() 的迭代器

参数

u64 *idx

指向 u64 循环变量的指针

struct zone *zone

所有内存块所在的区域

unsigned long *out_spfn

指向范围起始 PFN 的 ulong 指针,可以为 NULL

unsigned long *out_epfn

指向范围结束 PFN 的 ulong 指针,可以为 NULL

描述

此函数旨在作为 for_each_mem_range 类型迭代器的特定于区域/pfn 的包装器。具体来说,它们用于延迟内存初始化例程,因此我们在整个代码中重复了许多此逻辑。因此,与其将它放在多个位置,不如将它集中到一个新的迭代器中,该迭代器可以完成它们需要的一切,这似乎更有意义。

phys_addr_t memblock_alloc_range_nid(phys_addr_t size, phys_addr_t align, phys_addr_t start, phys_addr_t end, int nid, bool exact_nid)

分配启动内存块

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t start

要分配的内存区域的下限(物理地址)

phys_addr_t end

要分配的内存区域的上限(物理地址)

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

bool exact_nid

控制分配回退到其他节点

描述

如果 end == MEMBLOCK_ALLOC_ACCESSIBLE,则分配是从受 memblock.current_limit 限制的内存区域执行的。

如果指定的节点无法容纳请求的内存且 exact_nid 为 false,则分配将回退到系统中的任何节点。

对于具有内存镜像的系统,分配首先尝试从启用镜像的区域进行,然后从任何内存区域重试。

此外,函数使用 kmemleak_alloc_phys 来分配启动内存块,它永远不会报告为泄漏。

返回值

成功时分配的内存块的物理地址,失败时为 0

phys_addr_t memblock_phys_alloc_range(phys_addr_t size, phys_addr_t align, phys_addr_t start, phys_addr_t end)

在指定范围内分配内存块

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t start

要分配的内存区域的下限(物理地址)

phys_addr_t end

要分配的内存区域的上限(物理地址)

描述

startend 之间分配 size 个字节。

返回值

成功时分配的内存块的物理地址,失败时为 0

phys_addr_t memblock_phys_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid)

从指定的 NUMA 节点分配内存块

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

描述

从指定的 NUMA 节点分配内存块。如果该节点没有可用内存,则尝试从系统中的任何节点分配。

返回值

成功时分配的内存块的物理地址,失败时为 0

void *memblock_alloc_internal(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid, bool exact_nid)

分配启动内存块

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t min_addr

要分配的内存区域的下限(物理地址)

phys_addr_t max_addr

要分配的内存区域的上限(物理地址)

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

bool exact_nid

控制分配回退到其他节点

描述

使用 memblock_alloc_range_nid() 分配内存块,并将返回的物理地址转换为虚拟地址。

如果无法满足 min_addr 限制,则会放弃该限制,并且分配将回退到 min_addr 以下的内存。诸如节点和镜像内存之类的其他约束将在 memblock_alloc_range_nid() 中再次处理。

返回值

成功时分配的内存块的虚拟地址,失败时为 NULL。

void *memblock_alloc_exact_nid_raw(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid)

在精确节点上分配启动内存块,而不清零内存

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t min_addr

首选分配的内存区域的下限(物理地址)

phys_addr_t max_addr

首选分配的内存区域的上限(物理地址),或 MEMBLOCK_ALLOC_ACCESSIBLE,仅从受 memblock.current_limit 值限制的内存中分配

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

描述

公共函数,如果启用,则提供额外的调试信息(包括调用者信息)。不清除已分配的内存。

返回值

成功时分配的内存块的虚拟地址,失败时为 NULL。

void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid)

分配启动内存块,不清零内存,且不发生 panic

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t min_addr

首选分配的内存区域的下限(物理地址)

phys_addr_t max_addr

首选分配的内存区域的上限(物理地址),或 MEMBLOCK_ALLOC_ACCESSIBLE,仅从受 memblock.current_limit 值限制的内存中分配

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

描述

公共函数,如果启用,则提供额外的调试信息(包括调用者信息)。不清除已分配的内存,如果无法满足请求,则不会发生 panic。

返回值

成功时分配的内存块的虚拟地址,失败时为 NULL。

void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, phys_addr_t min_addr, phys_addr_t max_addr, int nid)

分配启动内存块

参数

phys_addr_t size

要分配的内存块的大小(以字节为单位)

phys_addr_t align

区域和块大小的对齐方式

phys_addr_t min_addr

首选分配的内存区域的下限(物理地址)

phys_addr_t max_addr

首选分配的内存区域的上限(物理地址),或 MEMBLOCK_ALLOC_ACCESSIBLE,仅从受 memblock.current_limit 值限制的内存中分配

int nid

要查找的空闲区域的 NID,对于任何节点都为 NUMA_NO_NODE

描述

公共函数,如果启用,则提供额外的调试信息(包括调用者信息)。此函数会清除已分配的内存。

返回值

成功时分配的内存块的虚拟地址,失败时为 NULL。

void memblock_free_late(phys_addr_t base, phys_addr_t size)

将页面直接释放到伙伴分配器

参数

phys_addr_t base

启动内存块的物理起始地址

phys_addr_t size

启动内存块的大小(以字节为单位)

描述

仅当内存块分配器已被拆除,但我们仍在初始化系统时,这才有意义。页面将直接释放到伙伴分配器。

unsigned long memblock_estimated_nr_free_pages(void)

从内存块的角度返回估计的可用页面数

参数

void

无参数

描述

在启动过程中,子系统可能需要在伙伴系统提供精确数值之前,对整个系统的空闲页数进行粗略的估计。特别是在使用 CONFIG_DEFERRED_STRUCT_PAGE_INIT 时,从伙伴系统获得的数值在启动过程中可能非常不精确。

返回值

从 memblock 角度估计的空闲页数。

bool __init_memblock memblock_is_region_memory(phys_addr_t base, phys_addr_t size)

检查一个区域是否是内存的子集

参数

phys_addr_t base

要检查的区域的起始地址

phys_addr_t size

要检查的区域的大小

描述

检查区域 [base, base + size) 是否是内存块的子集。

返回值

如果为假则为 0,如果为真则为非零值

bool __init_memblock memblock_is_region_reserved(phys_addr_t base, phys_addr_t size)

检查一个区域是否与保留内存相交

参数

phys_addr_t base

要检查的区域的起始地址

phys_addr_t size

要检查的区域的大小

描述

检查区域 [base, base + size) 是否与保留内存块相交。

返回值

如果相交则为真,否则为假。

void memblock_free_all(void)

将空闲页面释放给伙伴分配器

参数

void

无参数

int reserve_mem_find_by_name(const char *name, phys_addr_t *start, phys_addr_t *size)

查找具有给定名称的保留内存区域

参数

const char *name

附加到保留内存区域的名称

phys_addr_t *start

如果找到,则保存起始地址

phys_addr_t *size

如果找到,则保存地址的大小。

描述

只有在找到 name 时,才会更新 startsize

返回值

如果找到则为 1,否则为 0。