kdump分配内存过程分析


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 设计特点

  1. 首次适应算法:简单但高效,在预留内存中快速找到第一个可用空洞
  2. 强隔离性:控制页必须不与任何现有段重叠,避免内存污染
  3. 架构兼容性
    • 主要服务于需要身份映射页表的架构(如某些无法关闭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 常见问题

  1. 分配失败:若预留内存不足或碎片化严重,可能导致返回NULL,此时需增大crashkernel=参数
  2. 加密内存访问:未正确调用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 关键设计点

  1. 内存分配策略:优先低地址,失败后尝试高地址(除非显式指定high)。
  2. 对齐要求CRASH_ALIGN确保内存对齐(避免内存碎片和性能问题)。
  3. 错误回滚:若低端内存分配失败,释放已保留的高端内存。
  4. 资源管理:通过iomem_resource全局资源树管理预留区域。

2.10 调试与验证

  • 查看预留结果
    dmesg | grep -i "crashkernel"   # 查看内核日志中的预留信息
    cat /proc/iomem | grep "Crash"  # 查看预留内存的地址范围
    
  • 参数示例
    • crashkernel=512M:预留512MB内存,自动分配基址。
    • crashkernel=1G@0x100000000:强制在4GB(0x100000000)处预留1GB内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值