启动时内存管理

早期系统初始化无法使用“普通”内存管理,因为它尚未设置。但仍需要为各种数据结构分配内存,例如物理页分配器。

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

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

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 未初始化的内存区域(仅适用于保留区域)。

MEMBLOCK_RSRV_KERN

为内核使用而保留的内存区域,可以通过 memblock_reserve_kern() 或通过 memblock 分配 API 显式保留。所有 memblock 分配都会设置此标志。

MEMBLOCK_KHO_SCRATCH

kexec 在移交模式下可以传递给下一个内核的内存区域。在早期启动期间,我们尚不了解所有内存保留,因此我们从前一个内核中获取我们知道可以使用的临时内存。这是在此阶段唯一可以进行分配的内存。

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)

遍历不包含在 type 中的 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

指向 int 的指针,用于范围的 nid,可以是 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 中的 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

指向 int 的指针,用于范围的 nid,可以是 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 中的 memblock 区域,且不包含在 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)

遍历所有保留的 memblock 区域

参数

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

指向 ulong 的指针,用于范围的起始 pfn,可以是 NULL

p_end

指向 ulong 的指针,用于范围的结束 pfn,可以是 NULL

p_nid

指向 int 的指针,用于范围的 nid,可以是 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)

从给定点开始遍历特定区域的空闲 memblock 区域

参数

i

用作循环变量的 u64

zone

所有内存块所在的区域

p_start

指向 phys_addr_t 的指针,用于范围的起始地址,可以是 NULL

p_end

指向 phys_addr_t 的指针,用于范围的结束地址,可以是 NULL

描述

遍历特定区域中空闲的(memory && !reserved)memblock 区域,从当前位置继续。Memblock 初始化后即可使用。

for_each_free_mem_range

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

遍历空闲的 memblock 区域

参数

i

用作循环变量的 u64

nid

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

flags

根据内存属性从块中选择

p_start

指向 phys_addr_t 的指针,用于范围的起始地址,可以是 NULL

p_end

指向 phys_addr_t 的指针,用于范围的结束地址,可以是 NULL

p_nid

指向 int 的指针,用于范围的 nid,可以是 NULL

描述

遍历 memblock 中空闲的(memory && !reserved)区域。Memblock 初始化后即可使用。

for_each_free_mem_range_reverse

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

反向遍历空闲的 memblock 区域

参数

i

用作循环变量的 u64

nid

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

flags

根据内存属性从块中选择

p_start

指向 phys_addr_t 的指针,用于范围的起始地址,可以是 NULL

p_end

指向 phys_addr_t 的指针,用于范围的结束地址,可以是 NULL

p_nid

指向 int 的指针,用于范围的 nid,可以是 NULL

描述

以相反顺序遍历 memblock 中空闲的(memory && !reserved)区域。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)

遍历内存区域

参数

region

循环变量

for_each_reserved_mem_region

for_each_reserved_mem_region (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

根据内存属性从块中选择

描述

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

返回值

成功时返回找到的地址,失败时返回 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

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

描述

在指定的范围中,查找大小为 size 并按 align 对齐的空闲区域。

返回值

成功时返回找到的地址,失败时返回 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

隔离区域结束的输出参数

描述

遍历 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 pages 不会被初始化。

参数

phys_addr_t base

区域的基物理地址

phys_addr_t size

区域的大小

描述

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

返回值

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

int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size)

将内存区域标记为 MEMBLOCK_KHO_SCRATCH。

参数

phys_addr_t base

区域的基物理地址

phys_addr_t size

区域的大小

描述

只有标记有 MEMBLOCK_KHO_SCRATCH 的内存区域才会在 kexec 移交的早期启动期间被考虑用于分配。

返回值

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

int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size)

清除指定区域的 MEMBLOCK_KHO_SCRATCH 标志。

参数

phys_addr_t base

区域的基物理地址

phys_addr_t size

区域的大小

返回值

成功时返回 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

指向 int 的指针,用于范围的 nid,可以是 NULL

描述

*idx 中找到第一个匹配 nid 的区域,填充输出参数,并更新 *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() 的通用下一个函数

参数

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

指向 int 的指针,用于范围的 nid,可以是 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的memblock类型

int nid

要设置的节点ID

描述

将memblock type 区域在 [base, base + size) 范围内的节点ID设置为 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

指向 ulong 的指针,用于范围的起始 pfn,可以是 NULL

unsigned long *out_epfn

指向 ulong 的指针,用于范围的结束 pfn,可以是 NULL

描述

此函数旨在作为for_each_mem_range类型迭代器的 zone/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_alloc_or_panic(phys_addr_t size, phys_addr_t align, const char *func)

尝试分配内存并在失败时panic

参数

phys_addr_t size

要分配的内存块的大小(字节)

phys_addr_t align

区域和块大小的对齐

const char *func

调用函数名

描述

此函数尝试使用memblock_alloc分配内存,如果失败,则会使用格式化的消息调用panic。此函数不应直接使用,请使用宏memblock_alloc_or_panic。

void memblock_free_late(phys_addr_t base, phys_addr_t size)

直接将页面释放到buddy分配器

参数

phys_addr_t base

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

phys_addr_t size

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

描述

这仅在memblock分配器已被拆除但系统仍在初始化时有用。页面直接释放到buddy分配器。

unsigned long memblock_estimated_nr_free_pages(void)

从memblock角度返回估计的空闲页面数

参数

void

无参数

描述

在启动期间,子系统可能需要在buddy提供精确数字之前,获得整个系统中空闲页面的粗略估计。尤其是在启用CONFIG_DEFERRED_STRUCT_PAGE_INIT的情况下,从buddy获得的数字在启动期间可能非常不精确。

返回值

从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) 是否为内存块的子集。

返回值

如果为false则返回0,如果为true则返回非零值

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) 是否与保留内存块相交。

返回值

如果相交则为True,否则为False。

void memblock_free_all(void)

将空闲页面释放到buddy分配器

参数

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。

int reserve_mem_release_by_name(const char *name)

释放具有给定名称的保留内存区域

参数

const char *name

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

描述

强制释放保留内存区域中的页面,以便这些内存可以用作空闲内存。释放后,保留区域的大小变为0。

返回值

如果释放则为1,如果未找到则为0。