引言最近因为工作需求涉及到一些AI端侧推理加速的需求于是新学习了一个新的知识点。一、linux-reserve-memory 方案linux-reserve-memory 是 Linux 内核中用于预留内存机制。预留内存Reserved Memory 是一种机制用于预留特定物理内存区域使其不被内核的常规内存管理分配使用。这种内存通常用于专用硬件如GPU、DMA控制器或需要连续物理内存的关键任务。在某些场景下系统需要预先保留一部分内存以供特定的设备、驱动程序或者内核功能使用避免这部分内存被常规的内存分配器如 kmalloc、vmalloc 等分配出去linux-reserve-memory 机制就是为了满足这种需求而设计的。1、 使用场景设备特定需求一些硬件设备如摄像头、硬件加速器可能需要固定的、连续的物理内存区域来进行 DMA直接内存访问操作。例如图形处理单元GPU、高速网络接口卡等它们在工作时需要直接访问内存如果内存不连续或者被其他程序占用可能会导致性能下降或者出现错误。内核功能需求某些内核功能可能需要预先分配一块内存来存储关键数据或者执行特定的操作。例如内核日志缓冲区、内核调试信息存储区等。避免内存碎片为需要大块连续内存的设备如DMA预留区域。安全/实时性需求确保关键数据不被换出或覆盖如实时系统或安全模块。启动阶段保留在系统初始化时通过内核参数或设备树静态保留内存。2、配置linux-reserve-memory在 Linux 内核中预留内存通常通过设备树Device Tree或者内核命令行参数来配置。2.1、设备树方式设备树是一种描述硬件设备信息的数据结构在设备树中可以定义预留内存节点在设备树.dts 或 .dtsi 文件中定义 reserved-memory 节点。/reserved-memory { #address-cells 1; #size-cells 1; ranges; my-reserved-memory: my-memory10000000 { reg 0x10000000 0x1000000; /* 起始地址为 0x10000000大小为 16MB */ no-map; }; };启动后查看 /proc/iomem保留区域会标记为 reservedno-map内核不会将此区域映射到虚拟内存需驱动手动处理。reusable内存可被系统临时使用但需要时能释放如CMA。以上定义了一个名为 my-reserved-memory 的预留内存节点起始地址为 0x10000000大小为 0x1000000即 16MB。no-map 属性表示这部分内存不会被内核映射到虚拟地址空间除非有特定的驱动程序进行映射。除了上面这种方式还可以结合CMA连续内存分配器CMA, Contiguous Memory AllocatorCMA是一种动态保留机制内存平时可被普通进程使用但需要时能回收供设备使用。reserved-memory { linux,cma { compatible shared-dma-pool; reusable; size 0x4000000; // 64MB alignment 0x2000; linux,cma-default; // 设为默认CMA区域 }; };使用CMA驱动通过 dma_alloc_coherent() 或 dmam_alloc_coherent() 分配内存。2.2、内核命令行参数方式内核启动参数Boot Parameters除了设备树还可以通过内核命令行参数来预留内存语法memmap size start_address 。例如在 /boot/grub/grub.cfg 中添加以下参数mem1G memmap16M$1G这个参数表示系统总内存为 1GB从 1GB 地址处开始预留 16MB 的内存。3、内核代码中的使用预留内存步骤在 Linux 内核代码中可以通过以下步骤来使用预留内存保留内存需要驱动程序访问3.1、查找预留内存区域使用 of_reserved_mem_device_init 函数来初始化设备的预留内存区域。例如#includelinux/of_reserved_mem.hstaticintmy_driver_probe(structplatform_device*pdev){structreserved_mem*rmem;intret;retof_reserved_mem_device_init(pdev-dev);if(ret)returnret;rmemof_reserved_mem_lookup(pdev-dev);if(!rmem){dev_err(pdev-dev,No reserved memory found\n);return-ENODEV;}// 使用 rmem 进行后续操作// ...return0;}3.2、物理地址直接映射使用 ioremap() 或 memremap() 将物理地址映射到内核虚拟地址。如果需要将预留的物理内存映射到虚拟地址空间可以使用 ioremap 函数。#includelinux/io.hvoid*virt_addr;phys_addr_tphys_addrrmem-base;size_tsizermem-size;virt_addrioremap(phys_addr,size);if(!virt_addr){dev_err(pdev-dev,Failed to map reserved memory\n);return-ENOMEM;}3.3、DMA API通过 dma_alloc_coherent() 从CMA区域分配内存。4、验证保留内存查看 cat /proc/iomem 文件保留区域会显示为 reserved 或指定名称。10000000-101fffff : my-reserved-memory内核日志dmesg 会输出内存保留信息。CMA状态查看 cat /proc/meminfo 中的 CmaTotal 和 CmaFree。5、注意事项内存管理预留内存不会被内核的常规内存分配器管理因此需要驱动程序自己负责这部分内存的分配和释放。兼容性不同的硬件平台和内核版本可能对预留内存的配置和使用方式有所不同需要根据具体情况进行调整。错误处理在使用预留内存时需要进行充分的错误处理避免因内存分配失败或者映射失败导致系统崩溃。地址冲突确保保留区域不与现有内存或硬件地址重叠。大小对齐物理地址需按页对齐通常 4KB某些硬件可能需要更大对齐如2MB。性能影响过度保留内存可能减少系统可用内存影响性能。动态预留CMA适合需要灵活分配的场景而静态保留适合硬件强制要求固定地址的情况。7、普通进程访问保留内存的方法在Linux系统中普通应用程序用户空间进程不能直接访问保留内存因为保留内存通常由内核或专用硬件管理且用户空间与内核空间存在隔离。但通过以下方法可以间接或受限地访问保留内存7.1、内核空间访问推荐场景驱动/内核模块有以下几种方式7.1.1、映射物理内存到内核虚拟地址如果预留内存通过设备树标记为 no-map即内核不自动映射需通过 ioremap 或 memremap 手动映射。// 内核模块示例#includelinux/io.h#includelinux/module.hstaticvoid__iomem*reserved_mem;staticint__initmy_module_init(void){phys_addr_tphys_addr0x80000000;// 预留内存的物理地址size_tsize0x10000000;// 预留内存大小256MB// 映射物理内存到内核虚拟地址reserved_memioremap(phys_addr,size);if(!reserved_mem){pr_err(Failed to ioremap reserved memory\n);return-ENOMEM;}// 写入/读取示例writel(0x12345678,reserved_mem);// 写入32位数据pr_info(Read value: 0x%x\n,readl(reserved_mem));return0;}staticvoid__exitmy_module_exit(void){iounmap(reserved_mem);}module_init(my_module_init);module_exit(my_module_exit);7.1.2、直接物理地址访问DMA场景如果内存用于 DMA 操作如硬件加速卡可直接将物理地址传递给设备// 在设备驱动中设置 DMA 地址 dma_addr_t dma_handle phys_to_dma(dev, 0x80000000); dma_engine_start_transfer(dev, dma_handle, buffer_size);高度依赖需要加速卡驱动程序配合7.1.3、使用 CMAContiguous Memory Allocator如果预留内存标记为 shared-dma-pool动态分配连续内存// 从 CMA 区域分配内存structpage*cma_pages;cma_pagesdma_alloc_from_contiguous(dev,size,order);// 转换为虚拟地址void*virt_addrpage_to_virt(cma_pages);若保留内存是 CMA 区域可通过 DMA 接口分配内存并共享到用户空间步骤内核驱动分配 CMA 内存dma_addr_tdma_handle;void*cpu_addrdma_alloc_coherent(dev,size,dma_handle,GFP_KERNEL);用户空间映射通过 mmap 或 ioctl 将 DMA 缓冲区暴露给应用。7.2、 用户空间访问需谨慎控制权限有两种7.2.1、使用 /dev/mem 直接访问物理内存Linux 提供了 /dev/mem 设备文件允许有权限的进程直接读写物理内存但是有风险。首先启用 /dev/mem内核配置确保 CONFIG_STRICT_DEVMEM 未启用或通过 CAP_SYS_RAWIO 权限绕过限制。内核需启用 CONFIG_DEVMEM 和 CONFIG_STRICT_DEVMEM默认启用但可能限制访问。需要 root 权限或 CAP_SYS_RAWIO 能力。权限控制限制 /dev/mem 的访问权限chmod 600 /dev/mem。直接访问物理内存可能引发缓存一致性问题需使用 O_SYNC 标志。再映射物理地址int fd open(/dev/mem, O_RDWR | O_SYNC); void *addr mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, phys_addr);# 查看物理内存布局确认保留地址 sudo cat /proc/iomem # 使用 dd 读取物理内存示例地址 0x10000000 sudo dd if/dev/mem bs1 count4 skip$((0x10000000)) 2/dev/null | hexdump -C一个简单的例子#includesys/mman.h#includefcntl.h#defineRESERVED_PHYS_ADDR0x80000000#defineRESERVED_SIZE0x10000000intmain(){intfdopen(/dev/mem,O_RDWR|O_SYNC);if(fd-1){perror(open /dev/mem failed);return-1;}// 映射物理内存到用户空间void*memmmap(NULL,RESERVED_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,RESERVED_PHYS_ADDR);if(memMAP_FAILED){perror(mmap failed);close(fd);return-1;}// 读写操作*((volatileuint32_t*)mem)0xdeadbeef;printf(Read value: 0x%x\n,*((volatileuint32_t*)mem));munmap(mem,RESERVED_SIZE);close(fd);return0;}也存在一些风险隐患系统稳定性误操作可能导致内核崩溃。安全漏洞恶意程序可能通过 /dev/mem 窃取敏感数据。内核配置限制CONFIG_STRICT_DEVMEM 默认阻止访问关键内存区域。7.2.2、通过 UIOUserspace I/O框架Linux UIOUserspace I/O框架 是一种允许在用户空间Userspace中实现设备驱动程序的机制。它通过将部分设备驱动逻辑如内存映射和中断处理从内核空间Kernelspace转移到用户空间简化了硬件访问流程同时减少了对内核模块开发的依赖。UIO 特别适合需要快速原型开发、实时性要求高或需要隔离用户空间与内核空间的场景更安全的用户空间访问方案需内核驱动支持UIO 框架将设备驱动分为两部分内核模块仅负责硬件的基本初始化、中断处理和物理内存映射。用户空间程序实现设备控制逻辑如读写寄存器、数据处理等。这种分离使得开发者无需编写复杂的内核代码即可操作硬件典型场景7.2.2.1、UIO 的主要功能内存映射将设备的物理内存映射到用户空间虚拟地址用户程序可直接访问硬件寄存器或内存区域。内核通过 uio_map 接口暴露内存地址用户程序通过 mmap 映射。中断处理内核侧捕获硬件中断并通知用户空间。用户侧通过 read 或 poll 监听中断事件如读取 /dev/uioX 设备文件。设备发现通过 sysfs 文件系统/sys/class/uio/uioX提供设备信息如内存区域、中断号等7.2.2.2、UIO 的使用步骤内核配置启用 UIO 支持CONFIG_UIOy # 基础 UIO 框架 CONFIG_UIO_PDRV_GENIRQy # 通用 UIO 平台驱动适用于设备树配置设备树配置通过设备树定义 UIO 设备以 FPGA 为例 / { reserved-memory { #address-cells 1; #size-cells 1; fpga_region: fpga80000000 { compatible shared-dma-pool; reg 0x80000000 0x1000000; // 16MB 保留内存 no-map; }; }; uio_fpga { compatible generic-uio; memory-region fpga_region; // 绑定到预留内存 interrupts 0 123 4; // 中断号示例 }; };加载 UIO 驱动加载通用 UIO 驱动或自定义内核模块# 加载通用 UIO 平台驱动 sudo modprobe uio_pdrv_genirq用户空间通过UIO访问7.2.3、通过内核驱动程序映射这是最安全和标准的方法需编写内核模块将保留内存暴露给用户空间7.2.3.1、编写内核驱动1.在驱动中申请保留内存如通过设备树预留的内存区域。2.创建字符设备并实现 mmap 操作将保留内存映射到用户空间。// 设备树中定义的保留内存区域staticstructresource*reserved_mem;staticvoid__iomem*mem_base;staticintmydev_mmap(structfile*filp,structvm_area_struct*vma){// 将物理内存映射到用户空间returnremap_pfn_range(vma,vma-vm_start,PFN_DOWN(reserved_mem-start),vma-vm_end-vma-vm_start,vma-vm_page_prot);}staticstructfile_operationsfops{.mmapmydev_mmap,};// 初始化模块时获取保留内存资源staticint__initmydev_init(void){reserved_memrequest_mem_region(0x10000000,0x200000,my_reserved_mem);mem_baseioremap(reserved_mem-start,resource_size(reserved_mem));// 注册字符设备...return0;}7.2.3.2、用户空间访问1.应用程序通过 open() 打开设备文件。2.使用 mmap() 系统调用将设备内存映射到进程的虚拟地址空间。#include fcntl.h #include sys/mman.h int main() { int fd open(/dev/mydevice, O_RDWR); void *addr mmap(NULL, 0x200000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // 通过 addr 访问保留内存 munmap(addr, 0x200000); close(fd); return 0; }小结生产环境使用内核驱动程序通过 mmap 安全地暴露保留内存。临时调试谨慎使用 /dev/mem需关闭 CONFIG_STRICT_DEVMEM。硬件开发结合 UIO 框架实现用户空间驱动。