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

dma_map_sg后sg总长度不足请求值?代码排查求助

问题:DMA映射16G大内存后总长度不足的排查

我通过vm_mmap和get_user_pages_fast分配16G系统内存,传入dma_map_sg后,所有SG条目的总长度小于16G;首次加载内核模块时dma_len等于16G,后续加载则不足。禁用IOMMU后该现象消失。

测试代码

#include <linux/memblock.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/mman.h>
#include <linux/dma-mapping.h>

#define __SIZE_TO_PAGE_NUM(a)   ((a) >> PAGE_SHIFT)
#define XFER_PAGE_NUM __SIZE_TO_PAGE_NUM(xfer->size)
#define MAX_PIN_SIZE SZ_1G
#define MAX_PIN_PAGE_NUM        __SIZE_TO_PAGE_NUM(MAX_PIN_SIZE)

static struct sg_table sgt;
static struct page** pages = NULL;
static int probe_status = 0;
static u64 addr = 0;
static u64 size = SZ_16G;
static u32 page_num_pinned = 0; // 新增全局变量保存实际pin的页数

void dma_test_remove(struct pci_dev *pdev);
static struct pci_device_id dma_test_dev_id[] = {
        {
                PCI_DEVICE(0x1e3e, 0x2),
        },
        {}
};

static int dma_test_probe(struct pci_dev *pdev,
                             const struct pci_device_id *pent)
{
        u64 buf;
        u32 page_num;
        u32 index;
        int n;
        int ret;
        int i;
        struct scatterlist *sg;
        u64 dma_len = 0;
        int count1 = 0;
        int count0 = 0;
        /* init dma*/
        ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(48));
        if (ret) {
                printk("dma_set_mask failed\n");
                return ret;
        }
        ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(48));
        if (ret) {
                printk("dma_set_coherent_mask failed\n");
                return ret;
        }

        // alloc va
        addr = vm_mmap(NULL, 0, size,
                                PROT_READ | PROT_WRITE,
                                MAP_ANONYMOUS | MAP_SHARED | MAP_POPULATE, 0);
        if (IS_ERR(ERR_PTR(addr))) {
                printk("vm_mmap alloc va failed\n");
                return -EFAULT;
        }
        printk("alloc va success\n");

        page_num = size >> PAGE_SHIFT;
        pages = vmalloc(page_num * sizeof(struct page *)); // 修正结构体类型错误:struct pages* -> struct page*
        if (!pages) {
                vm_munmap(addr, size);
                printk("vmalloc alloc memory failed\n");
                return -ENOMEM;
        }

        page_num_pinned = 0; // 重置全局变量
        // pin memory
        buf = addr;
        while (page_num) {
                n = min_t(typeof(n), page_num, MAX_PIN_PAGE_NUM); // 恢复单次pin页限制
                n = get_user_pages_fast(buf, n, FOLL_WRITE, (pages + page_num_pinned));
                if (n < 0) {
                        printk("pin pages failed %d\n", n);
                        goto unpin_dma_pages;
                }
                page_num_pinned += n;
                page_num  -= n;
                buf += (unsigned long)n << PAGE_SHIFT;
        }
        printk("pin pages success\n");

        /*create sg table*/
        ret = sg_alloc_table_from_pages(&sgt, pages, size >> PAGE_SHIFT,
                                        0, size, GFP_KERNEL);
        if(ret) {
                printk("alloc sg_table failed %d\n", ret);
                goto unpin_dma_pages;
        }
        printk("alloc sg table success\n");

        dma_len = 0;
        count0 = 0;
        count1 = 0;
        for_each_sg(sgt.sgl, sg, sgt.nents, i){ // 使用sgt.nents替代sg_nents(sgt.sgl)
                dma_len += sg_dma_len(sg);
                count0++;
                if (sg_dma_len(sg) == 0)
                        count1++;
        }

        printk("count0 %d count1 %d\n", count0, count1);
        if (dma_len < size) {
                printk("dma len error in sg_alloc_table dma_len %llx size %llx\n", dma_len, size);
        }
        // dma map
        ret = dma_map_sg(&pdev->dev, sgt.sgl, sgt.nents, DMA_BIDIRECTIONAL); // 使用sgt.nents
        if (unlikely(!ret)) {
                printk("dma map sg failed %d\n", ret);
                ret = -ENOMEM;
                goto free_sg;
        }
        probe_status = 1;
        dma_len = 0;
        i = 0;
        count0 = 0;
        count1 = 0;
        for_each_sg(sgt.sgl, sg, sgt.nents, i){ // 使用sgt.nents
                dma_len += sg_dma_len(sg);
                count0++;
                if (sg_dma_len(sg) == 0)
                {
                        count1++;
                }
        }
        printk("count0 %d count1 %d dma_len %llx request len %llx\n", count0, count1, dma_len, size);
        if (dma_len < size) {
                printk("dma len error in dma_map_sg dma_len %llx size %llx\n", dma_len, size);
        }
        printk("dma map success\n");
        return 0;

free_sg:
        sg_free_table(&sgt);

unpin_dma_pages:
        for (index = 0; index < page_num_pinned; index++) {
                put_page(pages[index]);
        }

        vfree(pages);
        vm_munmap(addr, size);

        return ret;
}

void dma_test_remove(struct pci_dev *pdev)
{
        int index;
        if (probe_status)
                dma_unmap_sg(&pdev->dev, sgt.sgl, sgt.nents, DMA_BIDIRECTIONAL); // 使用sgt.nents

        sg_free_table(&sgt);

        // 使用实际pin成功的页数释放
        for (index = 0; index < page_num_pinned; index++) {
                put_page(pages[index]);
        }

        if (addr)
                vm_munmap(addr, size);

        if (pages)
                vfree(pages);

        printk("remove dma-test module\n");
}
static struct pci_driver dma_test_pci_driver = {
        .name = "dma-test",
        .id_table = dma_test_dev_id,
        .probe = dma_test_probe,
        .remove = dma_test_remove,
};


static int __init dma_map_test_init(void)
{
        return pci_register_driver(&dma_test_pci_driver);
}

static void __exit dma_map_test_exit(void)
{
    pci_unregister_driver(&dma_test_pci_driver);
}

module_init(dma_map_test_init);
module_exit(dma_map_test_exit);

MODULE_DEVICE_TABLE(pci, dma_test_dev_id);
MODULE_AUTHOR("zhanged");
MODULE_DESCRIPTION("zhanged Test dma_map_sg for large size");
MODULE_LICENSE("GPL and additional rights");

服务器环境

  • Ubuntu 24.04 LTS
  • 内核版本:6.8.0-48-generic(更换内核后问题仍存在)
  • CPU:Intel Core i7-10700
  • 开启intel_iommu=on,禁用IOMMU后问题消失

问题分析与修复建议

代码中的错误点

  1. get_user_pages_fast无限制调用
    原代码注释了单次pin页的限制,一次性尝试pin全部16G内存。当系统内存碎片化或IOMMU资源不足时,后续加载模块会因无法分配足够DMA映射资源导致部分SG条目失效。恢复min_t限制,分批次pin内存可缓解此问题。

  2. 内存释放逻辑错误
    原remove函数中遍历总页数释放内存,但实际只有page_num_pinned页被成功pin住,遍历总页数会访问未初始化的数组元素,引发内存错误。需改用实际pin成功的页数进行释放。

  3. SG条目数获取错误
    使用sg_nents(sgt.sgl)获取SG条目数不准确,sg_table结构体的nents字段才是维护的有效条目数,应替换为sgt.nents。

  4. 结构体类型错误
    原代码中vmalloc(page_num * sizeof(struct pages *))存在类型错误,应为struct page *。

IOMMU相关原因

开启IOMMU时,DMA映射需要分配大量IOMMU页表条目。首次加载模块时系统尚有足够空闲资源,后续加载时资源耗尽,导致部分内存无法完成DMA映射,表现为SG条目长度为0。禁用IOMMU后,DMA直接使用物理地址,无需额外页表资源,问题消失。

修复后的代码说明

上述代码已修正了所有提到的错误点:恢复分批次pin页逻辑、修正内存释放逻辑、使用正确的SG条目数、修复结构体类型错误。

内容的提问来源于stack exchange,提问作者andyZhang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 08:28:12