Linux驱动开发之分配连续内存

Posted 贺二公子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发之分配连续内存相关的知识,希望对你有一定的参考价值。

原文地址:https://zhuanlan.zhihu.com/p/391799765


文章目录

参考

  1. Dynamic DMA mapping
  2. dma_alloc_coherent memory with mmap
  3. CMA模块学习笔记
  4. A deep dive into CMA
  5. Linux内核最新的连续内存分配器(CMA)——避免预留大块内存
  6. CMA 详细分析
  7. CMA连续物理内存用户空间映射—(一)
  8. CMA连续物理内存用户空间映射—(二)
  9. linux cma内存管理
  10. Linux-3.14.12内存管理笔记【连续内存分配器(CMA)】
  11. LINUX CMA 详细分析
  12. Linux内存管理:CMA
  13. dts中memreserve和reserved-memory的区别
  14. 内存初始化代码分析(一):identity mapping和kernel image mapping
  15. 内存初始化代码分析(二):内存布局
  16. 内存初始化代码分析(三):创建系统内存地址映射
  17. meminfo与vmallocinfo实例
  18. Cortex-A8处理器memcpy的优化方案
  19. ARM64-memcpy.S 汇编源码分析
  20. /dev/mem可没那么简单
  21. DPDK中的memcpy性能优化及思考
  22. Linux kernel中的按页分配和释放内存方式

开机预留

开机预留的方法有下面两种,

  1. 通过uboot传入bootargs/cmdline,参考常用知识——linux内核中常见的内存分配方法,在Linux内核引导时,传入参数“mem=size”保留顶部的内存区间。比如系统有256MB内存,参数“mem=248M”会预留顶部的8MB内存,进入系统后可以调用ioremap(0xF800000,0x800000)来申请这段内存。
  2. 设备树设置memory为248M,预留8MB,这个可能会有问题,我们在arch/arm/boot/dts目录下面看到的dts文件关于memory的size的描述可能和实际的memory的size大小不一致,此问题主要是在bootloader中也会对memory信息进行获取,然后修改dts,并把dts load到内存制定的位置,然后把这个值传递给kernel,在setup_arch里面调用setup_machine_fdt时所传递的参数__atags_pointer就是boot loader传递过来存放dts的地址。
  3. 设备树设置memreserve或reserved-memory,参考dts中memreserve和reserved-memory的区别。memreserve分配的内存, 无法再被操作系统使用; 而reserved-memory内存有可能进入系统CMA, 是否做为CMA, 依赖以下几个条件:(1)compatible 必须为shared-dma-pool(2)没有定义no-map属性(3)定义了reusable属性。

dma_alloc_coherent

参考内核文档Documentation/DMA-API.txt和Documentation/DMA-API-HOWTO.txt。这个函数分配的内存从哪儿来,有书上说的是__alloc_pages,实现原理类似于__get_free_pages,受限于内核,一般最大分配4MB,但现在arm内核支持CMA机制,dma_alloc_coherent可从CMA分配内存,但也可以不走CMA,走alloc_pages,即有两个分配途径。

这样最大应该就是CMA区的最大值,dma_alloc_coherent分配的内存不带cache。dma_alloc_coherent分配超过4MB空间内存失败,需要确保系统有足够的DMA内存可用。CONSISTENT_DMA_SIZE的值是否大于5MB,这个值必须是2M的倍数。在一些版本的内核中,这个宏是DEFAULT_CONSISTENT_DMA_SIZE。如果上面没问题,但是仍然申请失败,可能是MAX_ORDER这个宏设置的太小,这个宏限制了一次请求所能分配的最大物理页数。如果申请5MB内存,MAX_ORDER要不小于12。

dma_pool_create

DMA POOL分配小型一致性DMA映射。调用dma_alloc_coherent函数获得的映射,最小大小为单个页。如果需要的DMA区域还小,就可以用DMA池。应用时,首先创建DMA池,size是分配的缓冲区的大小,align是硬件对齐字节数,allocation表示内存边界不能超越allocation。

struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
void dma_pool_destroy(struct dma_pool *pool);

分配和释放,

void * dma_pool_alloc(sturct dma_pool *pool, int mem_flags, dma_addr_t *handle);
void dma_pool_free(struct dma_pool *pool, void *addr, dma_addr_t addr);

在销毁之前必须向DMA池返回所有分配的内存。

kmalloc/__get_free_pages

最大4MB,带cache。

CMA

  1. 通过bootargs/cmdline导入参数cma=64M。
  2. 设备树分配

参考博客增大dma的分配,记录了分配大容量CMA的调试过程,总结一下就是实现1G的物理内存留700MB给硬件。第一,当CMA=500MB时,为了加载CMA成功,将内核,设备树,initrd/ramdisk放到前240MB,离散防止导致没有600MB连续空间。第二,通过开机预留256MB内存,为了ioremap 几百MB的内存,将user/kernel内存分配改为1G/3G,否则没有足够空间remap。第三,当CMA=700MB时,必须设置user/kernel内存分配改为1G/3G,否则无法加载CMA,因为1GB空间预留240MB给vmalloc(Linux内核版本从3.2到3.3,默认的vmalloc size由128M 增大到了240M),导致没有足够空间给CMA,可以看一下开机打印,修改之后的lowmem为1G整,否则为760M,这和HIGHMEM没有关系。

Memory: 416196K/1048576K available (5240K kernel code, 260K rwdata, 1616K rodata, 200K init, 301K bss, 632380K reserved)
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
    vmalloc : 0x80800000 - 0xff000000   (2024 MB)
    lowmem  : 0x40000000 - 0x80000000   (1024 MB)
    modules : 0x3f000000 - 0x40000000   (  16 MB)
      .text : 0x40008000 - 0x406ba454   (6858 kB)
      .init : 0x406bb000 - 0x406ed380   ( 201 kB)
      .data : 0x406ee000 - 0x4072f320   ( 261 kB)
       .bss : 0x4072f32c - 0x4077a7a4   ( 302 kB)

看下设备树分配,这里是共享的,参考dts中memreserve和reserved-memory的区别 ,reserved-memory有一些可选参数,比如no-map,如果使用了no-map,那么这段区域执行memblock_remove,反之执行memblock_reserve。在调用完memblock_reserve后,还会执行fdt_init_reserved_mem。如果reserved-memory下节点的compatible=,则这块内存会被用来进行Contiguous Memory Allocator for dma。initfn对应drivers/base/dma-contiguous.c下的rmem_cma_setup以及drivers/base/dma-coherent.c中的rmem_dma_setup,由于二者的compatible相同,所以前者优先。rmem_cma_setup会对这块内存做初始化,把这块区域加到cma_areas[cma_area_count]中,cma_areas保存着所有的CMA区域,稍后core_init_reserved_areas会对这个数组进行处理。

reserved-memory 
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
 
        ipu_cma@90000000 
            compatible = "shared-dma-pool";
            reg = <0x90000000 0x4000000>;
            reusable;
            status = "okay";
        ;
        
        cma_region: region@6a000000 
			compatible = "shared-dma-pool";
			no-map;
			reg = <0x6a000000 0x1000000>;
			linux,cma-default;
		;
;

zynq下的例子,

reserved-memory 
        #address-cells = <0x1>;
        #size-cells = <0x1>;
        ranges;

        linux,cma 
                compatible = "shared-dma-pool";
                reusable;
                reg = <0x2f000000 0x10000000>;
                /*size = <0x8000000>;
                alignment = <0x1000>;*/
                linux,cma-default;
        ;
;

cmem 
        compatible = "qe,cmem-dev";
;

1GB内存,此处在末尾预留了16MB,

/* global autoconfigured region for contiguous allocations */
linux,cma  
	compatible = "shared-dma-pool";
	reusable;
	size = <0x4000000>;
	alignment = <0x2000>;
	linux,cma-default; 
;

以上是关于Linux驱动开发之分配连续内存的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动之内存访问

驱动基础之分配内存

linux内存池能分配连续物理内存吗

Linux内存从0到1学习笔记(6.10 物理内存初始化之vmalloc分配器)

Linux内存从0到1学习笔记(六,物理内存初始化之四 --- 内存分配器)---持续更新

mmap 是不是连续分配堆内存?