1. kimage_alloc_crash_control_pages 为预留的内存分配页
/ kernel / kexec_core.c
1.1 函数功能
这是kexec/kdump机制中分配控制页的核心函数,专用于在预留的 crashkernel 内存区域中分配控制页。
控制页(Control Pages)是内核崩溃时加载第二个内核(crash kernel)所需的临时内存,用于管理内存复制、页表映射等关键操作。
1.2 代码路径
kernel/kexec_core.c
(与内核版本相关,可能略有差异)
1.3 参数说明
struct kimage *image
:描述 kexec 镜像的元数据结构unsigned int order
:内存分配阶数(分配大小为2^order * PAGE_SIZE
)
1.4 核心逻辑分解
1. 内存对齐与空洞搜索
size = (1 << order) << PAGE_SHIFT;
hole_start = (image->control_page + (size - 1)) & ~(size - 1);
hole_end = hole_start + size - 1;
- 对齐处理:计算起始地址
hole_start
,确保按size
对齐(例如size=4K时,地址必须是4K的倍数) - 空洞定义:从
image->control_page
开始寻找连续可用内存区域(hole)
2. 循环遍历预留内存区域
while (hole_end <= crashk_res.end) {
// ...
if (hole_end > KEXEC_CRASH_CONTROL_MEMORY_LIMIT) break;
// ...
}
- 范围限制:在
crashk_res
定义的预留内存区域内搜索(由crashkernel=
参数预留) - 上限检查:通过
KEXEC_CRASH_CONTROL_MEMORY_LIMIT
限制控制页的最大地址
3. 段重叠检查
for (i = 0; i < image->nr_segments; i++) {
mstart = image->segment[i].mem;
mend = mstart + image->segment[i].memsz - 1;
if ((hole_end >= mstart) && (hole_start <= mend)) {
// 调整空洞起始位置到当前段末尾
hole_start = (mend + (size - 1)) & ~(size - 1);
hole_end = hole_start + size - 1;
break;
}
}
- 关键逻辑:检查当前空洞是否与已有内存段(
image->segment[]
)重叠 - 避让策略:若发生重叠,将空洞起始位置移动到当前段的末尾后重新对齐
4. 成功分配条件
if (i == image->nr_segments) {
pages = pfn_to_page(hole_start >> PAGE_SHIFT);
image->control_page = hole_end;
break;
}
- 找到可用空洞:当遍历完所有段后未发现重叠,将空洞起始地址转换为
struct page*
- 更新控制页指针:
image->control_page
指向空洞末尾,为下次分配做准备
5. 安全增强处理
if (pages)
arch_kexec_post_alloc_pages(page_address(pages), 1 << order, 0);
- 内存加密场景:若启用SME(Secure Memory Encryption),需显式解密控制页(x86架构特性)
1.5 设计特点
- 首次适应算法:简单但高效,在预留内存中快速找到第一个可用空洞
- 强隔离性:控制页必须不与任何现有段重叠,避免内存污染
- 架构兼容性:
- 主要服务于需要身份映射页表的架构(如某些无法关闭MMU的ARM变种)
- 通用逻辑与架构特定处理分离(通过
arch_kexec_post_alloc_pages
)
1.6 调用场景
当通过kexec_load()
系统调用加载crash kernel时,此函数被触发。典型路径:
kexec_load()
-> do_kexec_load()
-> kimage_alloc_init()
-> kimage_alloc_control_pages()
-> kimage_alloc_crash_control_pages()
1.7 验证与调试
- 查看控制页分配:通过内核日志
dmesg
搜索kimage_alloc_crash_control_pages
- 调试宏:启用
CONFIG_KEXEC_DEBUG
可获取详细分配日志
1.8 常见问题
- 分配失败:若预留内存不足或碎片化严重,可能导致返回
NULL
,此时需增大crashkernel=
参数 - 加密内存访问:未正确调用
arch_kexec_post_alloc_pages
会导致控制页无法访问(SME场景)
通过此函数可看出,内核在崩溃转储过程中对控制页的管理遵循严格隔离和最小化分配原则,确保关键操作的内存安全。
1.9 kimage_alloc_control_pages
switch (image->type) {
case KEXEC_TYPE_DEFAULT: // 常规热重启
pages = kimage_alloc_normal_control_pages(image, order);
break;
case KEXEC_TYPE_CRASH: // 崩溃转储场景
pages = kimage_alloc_crash_control_pages(image, order);
break;
}
特性 常规分配 崩溃分配
内存来源 普通内存池 预留crash内存区域
分配时机 动态分配 预分配+运行时锁定
可靠性要求 允许失败 必须保证可用性
典型调用路径 kexec_load系统调用 kdump服务初始化
2. reserve_crashkernel
2.1. 函数入口与变量声明
static void __init reserve_crashkernel(void) {
unsigned long long crash_size, crash_base, total_mem;
bool high = false;
int ret;
total_mem = memblock_phys_mem_size(); // 获取系统物理内存总量
- 作用:初始化变量,获取系统物理内存总量(
total_mem
)。 - 关键函数:
memblock_phys_mem_size()
(统计所有可用物理内存)。
2.2. 解析启动参数
/* 尝试解析 crashkernel=XM 格式 */
ret = parse_crashkernel(boot_command_line, total_mem, &crash_size, &crash_base);
if (ret != 0 || crash_size <= 0) {
/* 若失败,尝试解析 crashkernel=X,high 格式 */
ret = parse_crashkernel_high(boot_command_line, total_mem, &crash_size, &crash_base);
if (ret != 0 || crash_size <= 0)
return; // 完全未定义参数则直接退出
high = true; // 标记为需要高端内存分配
}
- 作用:从内核启动参数
cmdline
中解析crashkernel=
配置。 - 两种格式:
crashkernel=XM
:直接指定大小(如512M
)。crashkernel=X,high
:强制在4G以上高端内存分配。
- 关键函数:
parse_crashkernel()
:解析普通参数。parse_crashkernel_high()
:解析带high
标志的参数。
2.3. Xen虚拟化环境检查
if (xen_pv_domain()) {
pr_info("Ignoring crashkernel for a Xen PV domain\n");
return; // Xen PV虚拟机环境下跳过预留
}
- 作用:在Xen半虚拟化(PV)环境中,因内存管理机制不同,直接跳过预留。
2.4. 自动分配内存基址(未指定crash_base时)
if (!crash_base) {
/* 优先在低地址范围(0~CRASH_ADDR_LOW_MAX)分配 */
if (!high)
crash_base = memblock_find_in_range(CRASH_ALIGN, CRASH_ADDR_LOW_MAX,
crash_size, CRASH_ALIGN);
/* 若低地址分配失败,尝试高地址范围(CRASH_ADDR_LOW_MAX~CRASH_ADDR_HIGH_MAX) */
if (!crash_base)
crash_base = memblock_find_in_range(CRASH_ALIGN, CRASH_ADDR_HIGH_MAX,
crash_size, CRASH_ALIGN);
/* 完全失败则报错退出 */
if (!crash_base) {
pr_info("crashkernel reservation failed - No suitable area found.\n");
return;
}
}
- 作用:若未指定
crash_base
,自动寻找可用的内存区域。 - 关键参数:
CRASH_ALIGN
:内存对齐要求(通常为2MB或16MB)。CRASH_ADDR_LOW_MAX
:低地址最大值(例如x86中为4GB)。CRASH_ADDR_HIGH_MAX
:高地址最大值(例如x86_64中为物理内存上限)。
- 关键函数:
memblock_find_in_range()
:在指定范围内查找符合对齐和大小的空闲内存块。
2.5. 用户指定基址时的检查
else {
/* 检查用户指定的crash_base是否可用 */
unsigned long long start = memblock_find_in_range(crash_base,
crash_base + crash_size, crash_size, 1 << 20);
if (start != crash_base) {
pr_info("crashkernel reservation failed - memory is in use.\n");
return;
}
}
- 作用:若用户显式指定了
crash_base
,检查该区域是否已被占用。 - 关键逻辑:尝试在
[crash_base, crash_base + crash_size)
范围内分配内存,若返回的起始地址不等于crash_base
,说明该区域已被占用。
2.6. 保留内存并处理低端内存
ret = memblock_reserve(crash_base, crash_size); // 正式保留内存
if (ret) {
pr_err("Error reserving crashkernel memblock.\n");
return;
}
/* 若crash_base >= 4GB,尝试额外保留低端内存(用于DMA/SWIOTLB) */
if (crash_base >= (1ULL << 32) && reserve_crashkernel_low()) {
memblock_free(crash_base, crash_size); // 失败则释放已保留内存
return;
}
- 作用:通过
memblock_reserve()
将内存标记为保留区域。 - 低端内存处理:当crashkernel位于4GB以上时,需额外保留256MB低端内存(通过
reserve_crashkernel_low()
实现),用于DMA缓冲区或SWIOTLB。
2.7. 日志输出与资源注册
pr_info("Reserving %ldMB at %ldMB for crashkernel (System RAM: %ldMB)\n",
(unsigned long)(crash_size >> 20),
(unsigned long)(crash_base >> 20),
(unsigned long)(total_mem >> 20));
crashk_res.start = crash_base;
crashk_res.end = crash_base + crash_size - 1;
insert_resource(&iomem_resource, &crashk_res); // 注册到全局资源树
}
- 作用:打印预留信息,并将预留区域注册到内核资源树(
iomem_resource
)。 - 关键结构体:
crashk_res
(类型为struct resource
),表示预留内存的物理地址范围。 - 关键函数:
insert_resource()
:将资源插入全局资源树,供其他模块(如/proc/iomem)查询。
2.8 完整调用流程图
解析crashkernel参数 → 检查Xen环境 → 自动分配/校验crash_base → memblock_reserve()
↓ ↑
parse_crashkernel() 处理低端内存(reserve_crashkernel_low())
↓ ↑
parse_crashkernel_high() 注册资源到iomem_resource
2.9 关键设计点
- 内存分配策略:优先低地址,失败后尝试高地址(除非显式指定
high
)。 - 对齐要求:
CRASH_ALIGN
确保内存对齐(避免内存碎片和性能问题)。 - 错误回滚:若低端内存分配失败,释放已保留的高端内存。
- 资源管理:通过
iomem_resource
全局资源树管理预留区域。
2.10 调试与验证
- 查看预留结果:
dmesg | grep -i "crashkernel" # 查看内核日志中的预留信息 cat /proc/iomem | grep "Crash" # 查看预留内存的地址范围
- 参数示例:
crashkernel=512M
:预留512MB内存,自动分配基址。crashkernel=1G@0x100000000
:强制在4GB(0x100000000)处预留1GB内存。