You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux内核DMA固定用户空间缓冲区触发内核Oops问题求助

搞定DMA直接映射的"bad page state"内核Oops

老兄,你这是踩了直接DMA映射用户缓冲区的经典陷阱——内核页交换和硬件DMA访问的冲突!咱们一步步拆解问题,给你解决思路:

问题根因

你直接用应用层分配的缓冲区做DMA,本质上是把用户态虚拟地址对应的物理页交给硬件去读写。但问题在于:这些用户页默认是可被内核交换到磁盘的!当updatedb这类工具触发系统页扫描,或者内核的kswapd守护进程要回收内存时,它会尝试把这些正在被DMA访问的页换出去——这时候内核发现页的状态完全矛盾(明明应该是空闲/可交换,却正在被硬件占用),直接就抛出"bad page state"的Oops了。

核心解决方案:锁定用户页,禁止内核交换

要解决这个问题,关键是在DMA操作期间,把用户对应的物理页锁定在内存中,不让内核碰它。具体要用到内核的pin_user_pages()(或老版本的get_user_pages())函数,操作流程如下:

  • 锁定页:在设置DMA之前,调用pin_user_pages()把用户缓冲区对应的物理页全部锁定,标记为不可交换。记得加上FOLL_WRITE(如果DMA要写数据)和FOLL_LONGTERM(如果DMA操作持续时间较长)标志。
  • 设置DMA:用锁定后得到的struct page指针,获取物理地址来配置硬件DMA。
  • 解锁页:DMA操作完成后,立刻调用put_user_pages()解锁,让内核可以正常管理这些页。

给你一段示例代码参考:

struct page **dma_pages;
size_t buf_len = 1024 * 1024 * 1024; // 1GB缓冲区
int num_pages = (buf_len + PAGE_SIZE - 1) / PAGE_SIZE;

// 分配存储page指针的数组
dma_pages = kmalloc_array(num_pages, sizeof(struct page *), GFP_KERNEL);
if (!dma_pages) {
    pr_err("Failed to allocate page array\n");
    return -ENOMEM;
}

// 锁定用户页,禁止交换
int ret = pin_user_pages((unsigned long)user_buffer, num_pages,
                         FOLL_WRITE | FOLL_LONGTERM, dma_pages, NULL);
if (ret < 0) {
    pr_err("Failed to pin user pages: %d\n", ret);
    kfree(dma_pages);
    return ret;
}

// 这里用dma_pages中的物理页信息,设置DMA地址(比如用page_to_phys())
// ... 配置硬件DMA寄存器 ...

// 等待DMA操作完成(比如用中断或轮询)
// ...

// DMA完成后,解锁页
put_user_pages(dma_pages, num_pages);
kfree(dma_pages);

额外注意事项

  • 异常路径处理:如果DMA操作中途失败(比如硬件出错),一定要记得解锁页,不然这些页会一直被锁定,导致内存泄漏。
  • 避免长期锁定:不要把页锁定太长时间,否则会耗尽系统可用内存,反而引发其他内存回收问题。
  • 应用层配合(可选):也可以让应用层用mlock()或mmap(MAP_LOCKED)来锁定自己的缓冲区,但这种方式依赖应用层正确实现,不如内核层锁定可靠。

再啰嗦两句为什么Oops会出现

内核的页状态管理是严格的:一个页要么是可交换的用户页,要么是被锁定的正在使用的页。当DMA在访问某个页,但内核没收到"这个页不能动"的通知,就会认为这是状态错误——毕竟正常情况下,被硬件占用的页应该被标记为锁定状态。所以内核直接抛出BUG,终止相关进程来避免更严重的内存 corruption。

内容的提问来源于stack exchange,提问作者T.O.M.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:20:06