如何在Linux中分配大的连续内存区域

9

是的,最终我将用它来进行DMA,但让我们暂时不考虑一致性。我有64位BAR寄存器,因此据我所知,所有RAM(例如高于4G)都可用于DMA。

我正在寻找约64MB的连续RAM。是的,这很多。

Ubuntu 16和18有CONFIG_CMA=y,但在内核编译时没有设置 CONFIG_DMA_CMA

请注意,如果两者都被设置了(在内核构建时),我可以简单地调用dma_alloc_coherent,但由于后勤原因,重新编译内核是不可取的。

机器始终会拥有至少32GB的RAM,不运行任何RAM密集型任务,并且内核模块将在引导后不久加载,在RAM变得显着分段之前,据我所知,没有其他东西使用CMA。

我已经设置了内核参数CMA = 1G。(并尝试过256M和512M)

# dmesg | grep cma
[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-4.4.170 root=UUID=2b25933c-e10c-4833-b5b2-92e9d3a33fec ro cma=1G
[    0.000000] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-4.4.170 root=UUID=2b25933c-e10c-4833-b5b2-92e9d3a33fec ro cma=1G
[    0.000000] Memory: 65612056K/67073924K available (8604K kernel code, 1332K rwdata, 3972K rodata, 1484K init, 1316K bss, 1461868K reserved, 0K cma-reserved)

我尝试过使用alloc_pages(GFP_KERNEL | __GFP_HIGHMEM, order),但并未成功。

最后的问题是:如何从CMA中获取大的连续块?我在网上找到的所有信息都建议使用dma_alloc_coherent,但我知道这仅适用于CONFIG_CMA=yCONFIG_DMA_CMA=yes

模块源代码为tim.c

#include <linux/module.h>       /* Needed by all modules */
#include <linux/kernel.h>       /* Needed for KERN_INFO */
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/gfp.h>
unsigned long big;
const int order = 15;
static int __init tim_init(void)
{
    printk(KERN_INFO "Hello Tim!\n");
    big = __get_free_pages(GFP_KERNEL | __GFP_HIGHMEM, order);
    printk(KERN_NOTICE "big = %lx\n", big);
    if (!big)
        return -EIO; // AT&T

    return 0; // success
}

static void __exit tim_exit(void)
{
    free_pages(big, order);
    printk(KERN_INFO "Tim says, Goodbye world\n");
}

module_init(tim_init);
module_exit(tim_exit);
MODULE_LICENSE("GPL");

插入模块会产生以下效果...

# insmod tim.ko
insmod: ERROR: could not insert module tim.ko: Input/output error
# dmesg | tail -n 33

[  176.137053] Hello Tim!
[  176.137056] ------------[ cut here ]------------
[  176.137062] WARNING: CPU: 4 PID: 2829 at mm/page_alloc.c:3198 __alloc_pages_nodemask+0xd14/0xe00()
[  176.137063] Modules linked in: tim(OE+) xt_CHECKSUM iptable_mangle ipt_MASQUERADE nf_nat_masquerade_ipv4 iptable_nat nf_nat_ipv4 nf_nat nf_conntrack_ipv4 nf_defrag_ipv4 xt_conntrack nf_conntrack ipt_REJECT nf_reject_ipv4 xt_tcpudp bridge stp llc ebtable_filter ebtables ip6table_filter ip6_tables iptable_filter ip_tables x_tables configfs vxlan ip6_udp_tunnel udp_tunnel uio pf_ring(OE) x86_pkg_temp_thermal intel_powerclamp coretemp kvm_intel kvm mei_me mei irqbypass sb_edac ioatdma edac_core shpchp serio_raw input_leds lpc_ich dca acpi_pad 8250_fintek mac_hid ib_iser rdma_cm iw_cm ib_cm ib_sa ib_mad ib_core ib_addr iscsi_tcp libiscsi_tcp libiscsi scsi_transport_iscsi autofs4 btrfs raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor raid6_pq libcrc32c raid0 multipath linear
[  176.137094]  hid_generic usbhid crct10dif_pclmul crc32_pclmul ghash_clmulni_intel e1000e aesni_intel raid1 aes_x86_64 isci lrw libsas ahci gf128mul ptp glue_helper ablk_helper cryptd psmouse hid libahci scsi_transport_sas pps_core wmi fjes
[  176.137105] CPU: 4 PID: 2829 Comm: insmod Tainted: G           OE   4.4.170 #1
[  176.137106] Hardware name: Supermicro X9SRL-F/X9SRL-F, BIOS 3.3 11/13/2018
[  176.137108]  0000000000000286 8ba89d23429d5749 ffff88100f5cba90 ffffffff8140a061
[  176.137110]  0000000000000000 ffffffff81cd89dd ffff88100f5cbac8 ffffffff810852d2
[  176.137112]  ffffffff821da620 0000000000000000 000000000000000f 000000000000000f
[  176.137113] Call Trace:
[  176.137118]  [<ffffffff8140a061>] dump_stack+0x63/0x82
[  176.137121]  [<ffffffff810852d2>] warn_slowpath_common+0x82/0xc0
[  176.137123]  [<ffffffff8108541a>] warn_slowpath_null+0x1a/0x20
[  176.137125]  [<ffffffff811a2504>] __alloc_pages_nodemask+0xd14/0xe00
[  176.137128]  [<ffffffff810ddaef>] ? msg_print_text+0xdf/0x1a0
[  176.137132]  [<ffffffff8117bc3e>] ? irq_work_queue+0x8e/0xa0
[  176.137133]  [<ffffffff810de04f>] ? console_unlock+0x20f/0x550
[  176.137137]  [<ffffffff811edbdc>] alloc_pages_current+0x8c/0x110
[  176.137139]  [<ffffffffc0024000>] ? 0xffffffffc0024000
[  176.137141]  [<ffffffff8119ca2e>] __get_free_pages+0xe/0x40
[  176.137143]  [<ffffffffc0024020>] tim_init+0x20/0x1000 [tim]
[  176.137146]  [<ffffffff81002125>] do_one_initcall+0xb5/0x200
[  176.137149]  [<ffffffff811f90c5>] ? kmem_cache_alloc_trace+0x185/0x1f0
[  176.137151]  [<ffffffff81196eb5>] do_init_module+0x5f/0x1cf
[  176.137154]  [<ffffffff81111b05>] load_module+0x22e5/0x2960
[  176.137156]  [<ffffffff8110e080>] ? __symbol_put+0x60/0x60
[  176.137159]  [<ffffffff81221710>] ? kernel_read+0x50/0x80
[  176.137161]  [<ffffffff811123c4>] SYSC_finit_module+0xb4/0xe0
[  176.137163]  [<ffffffff8111240e>] SyS_finit_module+0xe/0x10
[  176.137167]  [<ffffffff8186179b>] entry_SYSCALL_64_fastpath+0x22/0xcb
[  176.137169] ---[ end trace 6aa0b905b8418c7b ]---
[  176.137170] big = 0

有趣的是,再试一次会产生...

# insmod tim.ko
insmod: ERROR: could not insert module tim.ko: Input/output error
...and dmesg just shows:

[  302.068396] Hello Tim!
[  302.068398] big = 0

为什么第二(和随后的)尝试没有堆栈转储?

我正在尝试避免在qemu/KVM中运行内核,并使用gdb跟踪alloc_pages调用并寻找线索。 - tallen
你尝试过使用__get_dma_pages或者__get_free_pages吗?(https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch07s03.html) 我几年前为了一个项目构建了一个内核模块,使用__get_free_pages来分配大块的内存。 - Miguel Garcia
刚刚尝试了__get_free_pages(GFP_KERNEL | __GFP_HIGHMEM, 15),任何超过10的顺序都会失败。我已经大幅编辑了原帖并添加了源代码和dmesg以增加清晰度。我有一种潜在的猜测,认为我可能错过了一些愚蠢的东西。 - tallen
1个回答

11

简而言之,__GFP_DIRECT_RECLAIM (也由__GFP_RECLAIM提供)是必需的,因为最终将调用dma_alloc_contiguous并通过调用gfpflags_allow_blocking来检查阻塞是否可行。我使用了通常提供GFP_KERNEL__GFP_RECLAIM | __GFP_IO | __GFP_FS。但在所有这些之前,必须使用DMA_BIT_MASK(64)不是DMA_BIT_MASK(32)调用dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))

err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (err) {
    printk(KERN_INFO "[%s:probe] dma_set_mask returned: %d\n", DRIVER_NAME, err);
    return -EIO;
}
vaddr = dma_alloc_coherent(&pdev->dev, dbsize, paddr, GFP_KERNEL);
if (!vaddr) {
    printk(KERN_ALERT "[%s:probe] failed to allocate coherent buffer\n", DRIVER_NAME);
    return -EIO;
}

iowrite32(paddr, ctx->bar0_base_addr + 0x140); // tell card where to DMA from

使用CMA在Ubuntu 16.04和18.04中分配过大的DMA区域:

  1. 重建内核

    1. 使用uname -r确定当前内核版本
    2. 发出apt install linux-source-$(uname -r)获取内核源代码
    3. /boot/config-$(uname -r)复制到/usr/src/linux-source-$(uname -r)/.config
    4. 编辑.config
      1. 查找CONFIG_DMA_CMA未设置
      2. 更改为CONFIG_DMA_CMA=y
    5. 构建内核
      1. make -j[2 × # of cores]
      2. make -j[2 × # of cores] modules
      3. make install
    6. 已重新构建内核
  2. 配置CMA以保留RAM

    1. 编辑/etc/default/grub
      1. 查找GRUB_CMDLINE_LINUX=""
      2. 更改为GRUB_CMDLINE_LINUX="cma=33G"
      3. 用所需的CMA保留RAM替换33G
    2. 发出update-grub
    3. 重新启动
    4. 发出dmesg | grep cma
      1. 查找Memory: 30788784K/67073924K available (14339K kernel code, 2370K rwdata, 4592K rodata, 2696K init, 5044K bss, 1682132K reserved, 34603008K cma-reserved
      2. 注意:此示例保留了33G
    5. 已将CMA配置为阻止RAM被正常分配子系统使用
  3. 更改内核模块(驱动程序)源代码

    1. 通知内核卡可以寻址64b
    2. 在您的探测函数中定位像dma_alloc_coherent(…这样的一行
    3. 在那之前的几行,您可能会找到dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))
    4. 将其更改为dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))
    5. 您已告知内核,所涉及的卡不受低内存限制
    6. dma_alloc_coherent(&pdev->dev, dbsize, paddr, GFP_KERNEL)
    7. dbsize可以指定高达32G
    8. 重新编译您的内核模块(驱动程序)并进行测试

3
我可以确认这个按照广告宣传的方式运作。非常感谢您抽出时间回答自己的问题! - hugos
似乎 CMA 只能在 ARM 上使用,在 RHEL7/CentOS7 中不支持 x86-64。这是真的吗?Ubuntu 支持 x86 的 CMA 吗? - GL2014
2
我可以确认这适用于Ubuntu 22.04、Kernel 5.15.64、X86。我的情况略有不同,因为DMA必须是32位地址。我在grub中使用了以下命令行:"cma=500M@0-4G"。dmesg报告:cma: 在0x0000000034400000处保留了500 MiB。 - Liyong Zhou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接