如何在 Linux 中分配大的连续内存区域
How to allocate large contiguous, memory regions in Linux
是的,我最终会将其用于 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=y
和 CONFIG_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
为什么没有堆栈转储第二次(及后续)尝试?
简短的版本是 __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 区域
重建内核
- 使用
uname -r
确定您当前的内核版本
- 发出
apt install linux-source-$(uname -r)
获取内核源码
- 复制
/boot/config-$(uname -r)
到/usr/src/linux-source-$(uname -r)/.config
- 编辑
.config
- 定位
CONFIG_DMA_CMA
未设置
- 改为
CONFIG_DMA_CMA=y
- 构建内核
make -j[2 × # of cores]
make -j[2 × # of cores] modules
make install
- 您已重建内核
配置 CMA 以保留 RAM
- 编辑
/etc/defualt/grub
- 找到
GRUB_CMDLINE_LINUX=""
- 改为
GRUB_CMDLINE_LINUX="cma=33G"
- 使用所需的 CMA 保留 RAM 代替
33G
- 问题
update-grub
- 重启
- 发布 dmesg | grep cma
- 寻找
Memory: 30788784K/67073924K available (14339K kernel code, 2370K rwdata, 4592K rodata, 2696K init, 5044K bss, 1682132K reserved, 34603008K cma-reserved
- 注:本例预留33G
- 您已将 CMA 配置为阻止来自正常分配子系统的 RAM
更改您的内核模块(驱动程序)源代码
- 通知内核网卡可以寻址64b
- 在你的探测函数中找到一行
dma_alloc_coherent(…
- 之前的几行你可能会发现
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))
- 将此更改为
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))
- 您已通知内核,有问题的卡不限于低内存
dma_alloc_coherent(&pdev->dev, dbsize, paddr, GFP_KERNEL)
dbsize
最多可指定32G
- 重新编译您的内核模块(驱动程序)并进行测试
是的,我最终会将其用于 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=y
和 CONFIG_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
为什么没有堆栈转储第二次(及后续)尝试?
简短的版本是 __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 区域重建内核
- 使用
uname -r
确定您当前的内核版本 - 发出
apt install linux-source-$(uname -r)
获取内核源码 - 复制
/boot/config-$(uname -r)
到/usr/src/linux-source-$(uname -r)/.config
- 编辑
.config
- 定位
CONFIG_DMA_CMA
未设置 - 改为
CONFIG_DMA_CMA=y
- 定位
- 构建内核
make -j[2 × # of cores]
make -j[2 × # of cores] modules
make install
- 您已重建内核
- 使用
配置 CMA 以保留 RAM
- 编辑
/etc/defualt/grub
- 找到
GRUB_CMDLINE_LINUX=""
- 改为
GRUB_CMDLINE_LINUX="cma=33G"
- 使用所需的 CMA 保留 RAM 代替
33G
- 找到
- 问题
update-grub
- 重启
- 发布 dmesg | grep cma
- 寻找
Memory: 30788784K/67073924K available (14339K kernel code, 2370K rwdata, 4592K rodata, 2696K init, 5044K bss, 1682132K reserved, 34603008K cma-reserved
- 注:本例预留33G
- 寻找
- 您已将 CMA 配置为阻止来自正常分配子系统的 RAM
- 编辑
更改您的内核模块(驱动程序)源代码
- 通知内核网卡可以寻址64b
- 在你的探测函数中找到一行
dma_alloc_coherent(…
- 之前的几行你可能会发现
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))
- 将此更改为
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))
- 您已通知内核,有问题的卡不限于低内存
dma_alloc_coherent(&pdev->dev, dbsize, paddr, GFP_KERNEL)
dbsize
最多可指定32G- 重新编译您的内核模块(驱动程序)并进行测试