pfn_to_page、phys_to_pfn能否在中断上下文中调用

在Linux内核中,pfn_to_pagephys_to_pfn 函数通常可以在中断上下文中安全调用。以下是详细分析:


1. phys_to_pfn

  • 功能:将物理地址(phys_addr_t)转换为页帧号(Page Frame Number, PFN)。
  • 实现
    1
    2
    // include/linux/pfn.h
    #define phys_to_pfn(phys) ((unsigned long)((phys) >> PAGE_SHIFT))
  • 安全性
    • 这是一个简单的位操作(右移 PAGE_SHIFT 位),不涉及任何锁、内存分配或复杂计算。
    • 完全可在中断上下文中使用

2. pfn_to_page

  • 功能:将PFN转换为对应的 struct page 指针。
  • 实现:依赖内核的内存模型CONFIG_SPARSEMEMCONFIG_FLATMEM 等),但主流实现是安全的:
    • FLATMEM(简单系统):
      1
      2
      // arch/arm/include/asm/memory.h
      #define pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
    • SPARSEMEM_VMEMMAP(通用服务器/PC):
      1
      2
      // include/asm-generic/memory_model.h
      #define pfn_to_page(pfn) (vmemmap + (pfn))
    • SPARSEMEM(稀疏内存):
      1
      #define __pfn_to_page(pfn)  ... // 通过 mem_section 表查找(无锁)
  • 安全性
    • 所有实现均为纯算术操作或数组索引
    • 不涉及锁、动态内存分配或可能阻塞的操作。
    • 可在中断上下文中安全使用

关键原因:无锁且无阻塞

  • 这两个函数本质是地址转换宏/内联函数,不涉及:
    • 锁(如 spin_lock
    • 动态内存分配(如 kmalloc
    • 文件I/O或慢速路径操作
  • 符合中断上下文要求:不可睡眠、不可阻塞

注意事项

  1. 内存有效性
    • 确保传入的物理地址/PFN是有效的(属于系统内存)。非法地址可能导致崩溃(如解引用 NULL)。
  2. 内存模型差异
    • 不同架构(x86、ARM)或配置(如 CONFIG_SPARSEMEM_VMEMMAP)的实现不同,但均保证中断安全。
  3. 调试代码
    • 某些调试配置(如 CONFIG_DEBUG_VIRTUAL)可能添加额外检查,但通常仍中断安全。

结论

  • phys_to_pfnpfn_to_page 可在中断上下文中调用
  • 它们是无副作用的纯计算操作,无锁、无阻塞,满足中断上下文约束。但仍需确保参数有效性,避免非法地址。

PageHWPoison能否在中断上下文使用

在 Linux 内核中,**PageHWPoison 宏可以在中断上下文中安全使用**,但需要满足特定条件。以下是详细分析:


1. PageHWPoison 的本质

1
2
// include/linux/page-flags.h
#define PageHWPoison(p) PageHWPoison(p)

实际实现为对 struct page->flags 的位测试(PG_hwpoison 位):

1
TESTPAGEFLAG(HWPoison, hwpoison, PF_ANY)

展开后为简单的位掩码操作

1
2
3
static inline int PageHWPoison(const struct page *page) {
return test_bit(PG_hwpoison, &page->flags);
}

2. 中断上下文安全性分析

安全的原因

  1. 无锁操作

    • 仅通过 test_bit() 检查标志位(原子读操作)
    • 不涉及任何锁机制(如 spin_lock
  2. 无阻塞行为

    • 纯算术操作(位掩码 + 内存读取)
    • 不调用可能休眠的函数(如 kmalloc() 或文件系统操作)
  3. 无副作用

    • 仅读取 page->flags不修改页面状态

⚠️ 关键前提条件

1
struct page *page = /* 必须确保该指针有效且稳定 */;
  1. 指针有效性

    • 传入的 struct page* 必须属于有效的物理页
    • 非法指针会导致非法内存访问(如触发 Oops)
  2. 内存稳定性

    • 页面不能被同时释放(需确保引用计数安全)
    • 通常要求调用前已通过 get_page_unless_zero() 或其他机制持有引用

3. 中断上下文中的使用场景

✅ 安全用例:

1
2
3
4
5
6
7
8
// 在中断处理函数中(如 MCE 处理)
irqreturn_t mce_handler(...) {
struct page *page = pfn_to_page(pfn); // 安全(见之前分析)

if (PageHWPoison(page)) { // 安全:仅位测试
// 标记错误但不立即处理...
}
}

⚠️ 危险场景

1
2
3
4
if (PageHWPoison(page)) {
put_page(page); // 可能触发 __put_page() -> 释放操作
free_pages(...); // 绝对禁止在中断中调用!
}
  • 页释放操作(如 put_page())在中断上下文中:
    • 可能触发内存管理子系统复杂操作
    • 可能休眠或获取锁(违反中断上下文规则)

4. 实际内核代码参考

  • 内存错误处理(如 MCE)在中断上下文中使用 PageHWPoison
    1
    2
    3
    4
    5
    6
    7
    // arch/x86/kernel/cpu/mce/core.c
    void mce_process_work(...) {
    ...
    if (PageHWPoison(p))
    set_mce_nospec(p->pfn, p->flags & MF_PAGE_SOFT_OFFLINE);
    ...
    }
  • 设计原则
    • 仅执行诊断/标记(如设置 MCE 状态)
    • 推迟复杂处理(如页面回收)到进程上下文

5. 结论与最佳实践

操作 中断上下文是否安全 说明
PageHWPoison(page) 安全 纯位测试无锁无阻塞
set_page_hwpoison() 不安全 涉及标志修改和锁操作
后续页面回收操作 不安全 可能休眠或调用复杂路径

最佳实践

  1. 中断中仅作检查

    1
    2
    3
    4
    if (PageHWPoison(page)) {
    atomic_inc(&hwpoison_counter); // 安全:原子操作
    schedule_work(&recovery_work); // 推迟处理到进程上下文
    }
  2. 确保页面指针稳定

    • 通过 get_page_unless_zero() 获取引用
    • 或确认页面来自不可释放区域(如内核保留页)
  3. 避免修改页面状态

    • 所有设置/清除 PG_hwpoison 的操作(如 SetPageHWPoison()必须在进程上下文执行