Linux内核机制总结内存管理之连续内存分配器(二十七)

13 篇文章 0 订阅

文章目录

1 连续内存分配器

在系统长时间运行后,内存可能碎片化,很难找到连续的物理页,连续内存分配器(Contiguous Memory Allocator,CMA)使得这种情况下分配大的连续内存块成为可能。

嵌入式系统中的许多设备不支持分散聚集和I/O映射,需要连续的大内存块。例如手机上1300万像素的摄像头,一个像素占用3字节,拍摄一张照片需要大约37MB内存。在系统长时间运行后,内存可能碎片化,很难找到连续的物理页,页分配器和块分配器很可能无法分配这么大的连续内存块。

一种解决方案是为设备保留一块大的内存区域,缺点是:当设备驱动不使用的时候(大多数时间手机摄像头是空闲的),内核的其他模块不能使用这块内存。

连续内存分配器试图解决这个问题,保留一块大的内存区域,当设备驱动不使用的时候,内核的其他模块可以使用,当然有要求:只有申请可移动类型的页时可以借用;当设备驱动需要使用的时候,把已经分配的页迁移到其他地方,形成物理地址连续的大内存块。

1.1 使用方法

编译内核时需要开启以下配置宏:
(1)配置宏CONFIG_CMA,启用连续内存分配器。
(2)配置宏CONFIG_CMA_AREAS,指定CMA区域的最大数量,默认值是7。
(3)配置宏CONFIG_DMA_CMA,启用允许设备驱动分配内存的连续内存分配器。

CMA区域分为全局CMA区域和设备私有CMA区域。全局CMA区域是由所有设备驱动共享的,设备私有CMA区域由指定的一个或多个设备驱动使用。

配置CMA区域有3种方法:
(1)通过内核参数“cma”配置全局CMA区域的大小。
使用内核参数“cma=nn[MG]@[start[MG][-end[MG]]]”设置全局CMA区域的大小和物理地址范围。
(2)通过配置宏配置全局CMA区域的大小。

首先选择指定大小的方式:CONFIG_CMA_SIZE_SEL_MBYTES表示指定兆字节数,CONFIG_CMA_SIZE_SEL_PERCENTAGE表示指定物理内存容量的百分比,默认使用指定兆字节数的方式。

如果选择指定兆字节数的方式,那么通过配置宏CONFIG_CMA_SIZE_MBYTES配置大小。如果配置为0,表示禁止CMA,但是可以传递内核参数“cma=nn[MG]”以启用CMA。

如果选择指定物理内存容量的百分比的方式,那么通过配置宏CONFIG_CMA_SIZE_PERCENTAGE指定百分比。如果配置为0,表示禁用连续内存分配器,但是可以传递内核参数“cma=nn[MG]”以启用连续内存分配器。

(3)通过设备树源文件的节点“/reserved-memory”配置CMA区域,如果子节点的属性“compatible”的值是“shared-dma-pool”,表示全局CMA区域,否则表示设备私有CMA区域。

例如配置3个CMA区域:
1)全局CMA区域,节点名称是“linux,cma”,大小是64MB。
2)帧缓冲设备专用的CMA区域,节点名称是“framebuffer@78000000”,大小是8MB。
3)多媒体处理专用的CMA区域,节点名称是“multimedia-memory@77000000”,大小是64MB。

设备树源文件如下:

/ {
     #address-cells = <1>;
     #size-cells = <1>;

     memory {
        reg = <0x40000000 0x40000000>;
     };

     reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        /* 全局的自动配置的连续分配区域 */
        linux,cma {
             compatible = "shared-dma-pool";
             reusable;
             size = <0x4000000>;
             alignment = <0x2000>;
             linux,cma-default; 
        };

 display_reserved: framebuffer@78000000 {
             reg = <0x78000000 0x800000>;
        };

        multimedia_reserved: multimedia@77000000 {
             compatible = "acme,multimedia-memory";
             reg = <0x77000000 0x4000000>;
        };
     };
     /* ... */

     fb0: video@12300000 {
          memory-region = <&display_reserved>;
          /* ... */ 
     };

     scaler: scaler@12500000 {
          memory-region = <&multimedia_reserved>;
          /* ... */ 
     };

     codec: codec@12600000 {
          memory-region = <&multimedia_reserved>;
          /* ... */ 
     };
};

1.2 技术原理

连续内存分配器是DMA映射框架的辅助框架,设备驱动程序不能直接使用连续内存分配器,软件层次如下所示:
在这里插入图片描述
(1)连续内存分配器是在页分配器的基础上实现的,提供的接口cma_alloc用来从CMA区域分配页,接口cma_release用来释放从CMA区域分配的页。

(2)在连续内存分配器的基础上实现了DMA映射框架专用的连续内存分配器,简称DMA专用连续内存分配器,提供的接口dma_alloc_from_contiguous用来从CMA区域分配页,接口dma_release_from_contiguous用来释放从CMA区域分配的页。

(3)DMA映射框架从DMA专用连续内存分配器分配或释放页,为设备驱动程序提供的接口dma_alloc_coherent和dma_alloc_noncoherent用来分配内存,接口dma_free_coherent和dma_free_noncoherent用来释放内存。

(4)设备驱动程序调用DMA映射框架提供的函数来分配或释放内存。

1.数据结构
内核定义了结构体cma来描述一个CMA区域的信息,其代码如下:

mm/cma.h
struct cma {
     unsigned long    base_pfn;
     unsigned long    count;
     unsigned long    *bitmap;
     unsigned int order_per_bit; 
     struct mutex     lock;
     const char *name;
};

(1)成员base_pfn是CMA区域的起始页帧号。
(2)成员count是页数。
(3)成员bitmap是位图,每个位描述对应的页的分配状态,0表示空闲,1表示已分配。
(4)成员order_per_bit指示位图中的每个位描述的物理页的阶数,目前取值为0,表示每个位描述一页。

可以配置多个CMA区域,内核定义了一个数组用来管理CMA区域,全局变量cma_area_count存放配置的CMA区域的数量:

mm/cma.c
struct cma cma_areas[MAX_CMA_AREAS];
unsigned cma_area_count;

页分配器为CMA区域的物理页定义了迁移类型MIGRATE_CMA:

include/linux/mmzone.h
enum migratetype {
    …
#ifdef CONFIG_CMA
    MIGRATE_CMA,
#endif
    …
};

2.创建CMA区域
内存管理子系统初始化时,解析设备树二进制文件得到物理内存的布局,使用memblock保存布局信息,memblock的memory类型保存内存块的物理地址范围,reserved类型保存保留内存块的物理地址范围,CMA区域属于保留内存块。

首先解析设备树二进制文件中的节点“memory”,把内存块添加到memblock的memory类型。

如果通过设备树源文件配置CMA区域,创建CMA区域的执行流程如下所示:
在这里插入图片描述
(1)解析设备树二进制文件中的节点“reserved-memory”,把保留内存块添加到memblock的reserved类型。
(2)函数__reserved_mem_init_node调用注册的所有保留内存初始化函数,保留内存初始化函数是使用宏RESERVEDMEM_OF_DECLARE定义的,放在“__reservedmem_of_table”节里面,其中全局CMA区域的初始化函数是rmem_cma_setup。
(3)函数rmem_cma_setup从数组cma_areas分配一个数组项,保存CMA区域的起始页帧号和页数。如果指定了属性“linux,cma-default”,那么这个CMA区域是默认的CMA区域,设置全局变量dma_contiguous_default_area指向这个CMA区域。

如果通过内核参数或配置宏配置全局CMA区域,创建CMA区域的执行流程如下所示,函数 dma_contiguous_reserve 负责创建全局CMA区域:
在这里插入图片描述
3.把CMA区域释放给页分配器
memblock是内核初始化的时候使用的内存分配器,内核初始化完以后使用伙伴分配器管理物理页。内核初始化完成的时候,把空闲的内存释放给伙伴分配器,不会把保留的内存释放给伙伴分配器。CMA区域属于保留的内存,但是我们需要把CMA区域的物理页交给伙伴分配器管理。

连续内存分配器注册了初始化函数cma_init_reserved_areas:

mm/cma.c
core_initcall(cma_init_reserved_areas);

函数cma_init_reserved_areas负责把所有CMA区域的物理页释放给伙伴分配器,执行流程如下所示。针对每个CMA区域,先把页块的迁移类型设置为MIGRATE_CMA,然后调用函数__free_pages,把页块释放给伙伴分配器:
在这里插入图片描述

4.从CMA区域借用页
当设备驱动程序不使用CMA区域的时候,内核的其他模块可以借用CMA区域的物理页,页分配器只允许可移动类型从CMA类型借用物理页。

如下所示,页分配器分配物理页的时候,执行流程如下:
在这里插入图片描述
(1)从指定迁移类型分配页。
(2)如果分配失败,从备用迁移类型借用物理页。

  • 如果指定迁移类型是可移动类型,首先从CMA类型借用物理页。
  • 从备用迁移类型列表中的每个迁移类型借用物理页。

5.从CMA区域分配内存
当设备驱动程序需要使用CMA区域的时候,如果CMA区域中的物理页已经被页分配器分配出去,需要把物理页迁移到其他地方。

函数cma_alloc负责从CMA区域分配内存,执行流程如下所示:
在这里插入图片描述
(1)在CMA区域的位图中查找一个足够大的空闲页块。
(2)在位图中把物理页的分配状态设置为已分配。
(3)调用函数alloc_contig_range,把页分配器已分配出去的物理页迁移到其他地方。
(4)如果迁移失败,回到第1步,查找下一个足够大的空闲页块并尝试分配,直到分配成功或者尝试完所有空闲页块。

函数alloc_contig_range的执行流程如下:
(1)调用函数start_isolate_page_range,把物理页的迁移类型设置为隔离类型(MIGRATE_ISOLATE),隔离物理页,防止页分配器把空闲页分配出去。
(2)调用函数__alloc_contig_migrate_range,处理页分配器已分配出去的物理页。

  • 调用函数reclaim_clean_pages_from_list,回收干净的文件页,文件页不可移动,只可回收。
  • 调用函数migrate_pages,把可移动的物理页迁移到其他地方。

(3)调用函数isolate_freepages_range处理空闲页,把空闲页从页分配器的空闲链表中删除。
(4)调用函数undo_isolate_page_range,撤销对物理页的隔离,把物理页的迁移类型设置为CMA类型。

6.释放CMA区域的内存
函数cma_release负责释放CMA区域的内存,执行流程如下所示:
在这里插入图片描述
(1)检查物理页是否属于CMA区域。
(2)把物理页释放给页分配器。
(3)在CMA区域的位图中把物理页的分配状态设置为空闲。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值