本文贴代码过头了,以后想起来再优化一下吧

目录

概述

数据结构

构建初始化(DTS+CONFIG_DMA_CMA)

页表与物理页初始化

分配器激活

分配器使用

CMA部署

实战


概述

CMA(Contiguous Memory Allocator)是连续内存分配技术,是 Linux Kernel 内存管理系统的扩展,目的在于解决视频播放 (特别对于 4K 视频) 等需要预留大量连续内存导致运行内存紧张的问题。

CMA 框架的主要作用不是分配内存,而是解析和管理内存配置,以及作为在设备驱动程序和可插拔的分配器之间的中间组件。

数据结构

1 . struct cma 结构用于维护一块 CMA 区域

struct cma {

unsigned long   base_pfn; //CMA 区域 起始物理地址对应的物理页帧号

unsigned long   count; //描述 CMA 区域总共维护的 page 数量

unsigned long   *bitmap; //该 CMA 区域的所有物理页维护在该 bitmap 中,bitmap 中每个 bit 代表一定数量的物理页,至于代表多少物理页与 order_per_bit 有关

unsigned int order_per_bit; //指明该 CMA 区域的 bitmap 中,每个 bit 代表 的 page 数量

struct mutex    lock;

const char *name; //CMA 区域的名字

};

cma模块使用bitmap来管理其内存的分配,0表示free,1表示已经分配。具体内存管理的单位和struct cma中的order_per_bit成员相关,如果order_per_bit等于0,表示按照一个一个page来分配和释放,如果order_per_bit等于1,表示按照2个page组成的block来分配和释放,以此类推。struct cma中的bitmap成员就是管理该cma area内存的bit map。count成员说明了该cma area内存有多少个page。它和order_per_bit一起决定了bitmap指针指向内存的大小。base_pfn定义了该CMA area的起始page frame number,base_pfn和count一起定义了该CMA area在内存在的位置。

2 . 维护 CMA 分配器中可用的 CMA 区域。 每个 CMA 区域包含了一段可用的物理内存

#define MAX_RESERVED_REGIONS 32

struct cma cma_areas[MAX_CMA_AREAS];

每一个struct cma抽象了一个CMA area,标识了一个物理地址连续的memory area。调用cma_alloc分配的连续内存就是从CMA area中获得的。默认定义32个cma_areas。

构建初始化(DTS+CONFIG_DMA_CMA)

CMA的分配初始化有三种:一种DTS,一种Kbuild配置,还一种通过cmdline常见。此处分析优先级最高的DTS方式。使用DTS方式时,要打开CONFIG_DMA_CMA宏,否则只会创建预留区,不会将改预留区加入到CMA中。

start_kernel

---->setup_arch

---->arm_memblock_init

--→early_init_fdt_scan_reserved_mem

void __init early_init_fdt_scan_reserved_mem(void) {int n;u64 base, size;early_init_dt_reserve_memory_arch(__pa(initial_boot_params),fdt_totalsize(initial_boot_params), 0);//遍历DTS中的节点,然后把节点信息传入__fdt_scan_reserved_mem函数,该函数用于筛选“reserved-memory”节点,然后将信息存储在reserved_mem数组里of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);//将预留区中的区域在 MEMBLOCK 分配之后加入到 CMA 和 DMA 区域中fdt_init_reserved_mem();}

为 DTS 中的预留区分配内存。 DTS 中预留区分做两类,一类是 DTB 本身需要预留的区域,另一类是 “/reserved-memory” 节点中描述的预留区。在后者中,预留区分配需要的内存之后,还会将这些预留区加入到 CMA 或 DMA 中

void __init fdt_init_reserved_mem(void) {int i;for (i = 0; i < reserved_mem_count; i++) {struct reserved_mem *rmem = &reserved_mem[i];unsigned long node = rmem->fdt_node;int len;int err = 0;//将系统预留数组 reserved_mem[] 中 size 成员为 0 的成员分配对应长度的物理内存作为预留区。实现连续物理内存的最初分配,并完成将该区域加入到系统的预留区 数组 reserved-mem[] 中。//注意:当在dts中指定了size大小,那么这里的rmem->size不为0,无需执行alloc size动作if (rmem->size == 0)err = __reserved_mem_alloc_size(node, rmem->name, &rmem->base,           &rmem->size);//遍历 __reservedmem_of_tableif (err == 0)__reserved_mem_init_node(rmem);}}

__reserved_mem_init_node函数中一些代码无法追踪,遍历 __reservedmem_of_table section 内的预留区时,函数会调用 rmem_cma_setup() 函数,该函数用于将全局 reserved-mem[] 数组的区域加入到 CMA 分配器中,即添加 一块新的 CMA 区域。在该函数内,涉及从 MEMBLOCK 分配物理内存和加入 新的 CMA 区域,也包含了设置 CMA 分配器使用的默认分配区。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

将预留区添加到 CMA 子系统

static int __init rmem_cma_setup(struct reserved_mem *rmem) {phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);phys_addr_t mask = align - 1;unsigned long node = rmem->fdt_node;struct cma *cma;int err;//包含 “no-map” 或者不包含 “reusable” 属性预留区不建立映射关系if (!of_get_flat_dt_prop(node, "reusable", NULL) || of_get_flat_dt_prop(node, "no-map", NULL))return -EINVAL;//对预留区的 基地址和长度进行对齐检测if ((rmem->base & mask) || (rmem->size & mask)) {pr_err("Reserved memory: incorrect alignment of CMA region\n");return -EINVAL;}//将预留区 加入到一块可用的 CMA 区域内,并初始化这块 CMA 区域的管理数据结构体成员。【详细见后】err = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, &cma);//将预留区加入到 dma_mmu_remap[] 数组,以供系统初始化 DMA 映射时使用dma_contiguous_early_fixup(rmem->base, rmem->size);//设备数节点含有linux,cma-default属性,则将当前 CMA 区域作为系统设备默认 使用的 CMA 区域if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))dma_contiguous_set_default(cma);rmem->ops = &rmem_cma_ops; //操作方法 【详细见后】rmem->priv = cma;return 0;}RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);

=====================================================================

//cma结构体初始化的同时也指向了当前cma area count的cma_areas全局数组。

int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size, int order_per_bit, struct cma **res_cma) {struct cma *cma;phys_addr_t alignment;alignment = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);cma = &cma_areas[cma_area_count];cma->base_pfn = PFN_DOWN(base);cma->count = size >> PAGE_SHIFT;cma->order_per_bit = order_per_bit;*res_cma = cma;cma_area_count++;totalcma_pages += (size / PAGE_SIZE);return 0;}

================================================================

rmem_cma_ops 包含 rmem_cma_device_init 和 rmem_cma_device_release

前者设置设备使用的 CMA 区域                dev->cma_area = cma;

后者用于将设备预留区信息设置为 NULL dev->cma_area = NULL;

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

至此连续物理内存并未初始成功,只是做出了内存预留。

页表与物理页初始化

构建完 CMA 区域之后,CMA 需要将每个 CMA 区域的页表进行映射,以及将 CMA 区域内的物理页进行初始化。该阶段初始化完毕之后还不能使用 CMA 分配器。

start_kernel

→setup_arch

-→paging_init

→dma_contiguous_remap

该函数用于创建映射关系。【建立映射的套路都是如下代码】

void __init dma_contiguous_remap(void) {int i;for (i = 0; i < dma_mmu_remap_num; i++) {phys_addr_t start = dma_mmu_remap[i].base;phys_addr_t end = start + dma_mmu_remap[i].size;struct map_desc map;unsigned long addr;if (end > arm_lowmem_limit) end = arm_lowmem_limit;if (start >= end) continue;map.pfn = __phys_to_pfn(start);map.virtual = __phys_to_virt(start);map.length = end - start;map.type = MT_MEMORY_DMA_READY;for (addr = __phys_to_virt(start); addr < __phys_to_virt(end);addr += PMD_SIZE)pmd_clear(pmd_off_k(addr));flush_tlb_kernel_range(__phys_to_virt(start), __phys_to_virt(end));//建立映射iotable_init(&map, 1);}}

分配器激活

间接对 CMA 进行激活初始化,激活之后 CMA 就可用供其他模块、设备和子系统使用。

static int __init cma_init_reserved_areas(void) {int i;//CMA 的激活入口for (i = 0; i < cma_area_count; i++) {int ret = cma_activate_area(&cma_areas[i]); //【详细见后】if (ret) return ret;}return 0;}core_initcall(cma_init_reserved_areas);

=============================================================

函数用于将 CMA 区域内的预留页全部释放添加到 Buddy 管理器内,然后激活 CMA 区域供系统使用。此函数将cam结构体剩余成员的初始化。

static int __init cma_activate_area(struct cma *cma) {//比如CMA为8M,则bitmap size为2^8int bitmap_size = BITS_TO_LONGS(cma_bitmap_maxno(cma)) * sizeof(long);unsigned long base_pfn = cma->base_pfn, pfn = base_pfn;unsigned i = cma->count >> pageblock_order;struct zone *zone;cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL);zone = page_zone(pfn_to_page(pfn));//检查 CMA 区域的每个 pageblock 内所有页是否有效,并且所有页与起始页是在同一个 ZONE 分区 内do {unsigned j;base_pfn = pfn;for (j = pageblock_nr_pages; j; --j, pfn++) {if (page_zone(pfn_to_page(pfn)) != zone)goto not_in_zone;}//将 pageblock 内所有的物理页的 RESERVED 标志清除,让后将这些页都返回 给 Buddy 系统使用 【详细见后】init_cma_reserved_pageblock(pfn_to_page(base_pfn));} while (--i);mutex_init(&cma->lock);return 0;not_in_zone:pr_err("CMA area %s could not be activated\n", cma->name);kfree(cma->bitmap);cma->count = 0;return -EINVAL;}

========================================================================

void __init init_cma_reserved_pageblock(struct page *page) {unsigned i = pageblock_nr_pages;struct page *p = page;do {__ClearPageReserved(p); //所有 page 清除 Reserved 标志set_page_count(p, 0);} while (++p, --i);//将cma区域 page的迁移类型设置为 MIGRATE_CMAset_pageblock_migratetype(page, MIGRATE_CMA);//内核中默认pageblock_order = MAX_ORDER-1,可以直接从else看起if (pageblock_order >= MAX_ORDER) {i = pageblock_nr_pages;p = page;do {set_page_refcounted(p);__free_pages(p, MAX_ORDER - 1);p += MAX_ORDER_NR_PAGES;} while (i -= MAX_ORDER_NR_PAGES);} else {set_page_refcounted(page);              //page 引用 计数设置为 1__free_pages(page, pageblock_order); //释放页}//将释放的 page 数量全部加到系统里进行维护adjust_managed_page_count(page, pageblock_nr_pages);}

cma默认是从reserved memory中分配的,通常情况这块内存是直接分配并预留不做任何使用,无形之中造成了浪费。所以在不用的时候放入伙伴系统,作为普通内存使用。

分配器使用

CMA 激活之后,内核可以使用 CMA API 就可以使用连续物理内存

分配 CMA 里面的连续物理内存,可以使用:

struct page *dma_alloc_from_contiguous(struct device *dev, size_t count, unsigned int align, gfp_t gfp_mask)

指针dev 指向需要分配CMA的设备,

参数 count 指明需要分配的page数,

align 参数 指明对齐的方式,align = CONFIG_CMA_ALIGNMENT;

no_warn 控制警告消息的打印。

核心函数cma_alloc:根据pfn获取page

当使用完 CMA 连续物理内存之后,可以将物理内存归还给 CMA 内存管理器

bool dma_release_from_contiguous(struct device *dev, struct page *pages, int count)

参数 dev 指向一个设备,

pages 指向连续物理内存的起始页,

参数 count 表示分配的page数

核心函数cma_release:根据page找到pfn,然后根据pfn释放当前分配的页

当然还有更高级的接口

cma_allocation_alloc

cma_allocation_free

CMA部署

CMA 问题的本质就是如何规划系统的 物理内存

第一个比较重要的是获得系统物理内存的范围

cat /proc/iomem

找到 “System RAM”, 其代表系统物理内存的起始物理地址和终止物理地址,分配的cma区域不能超过这段范围。

第二个查看当前系统的预留区

系统已经预留的不可使用

cat /sys/kernel/debug/memblock/reserved

通过这个命令可以知道系统已预留的内存信息,这些已预留的内存信息不可使用。但排除这些预留区域,再在RAM范围内找出可用内存,再满足对其需求就可以自己手动找出可用于CMA的区域。

第三个,在DTS中说明cma信息

dts方式部署cma的好处是既可以指定起始地址和长度,还可以命名该cma

linux,cma {                                        //cma 名字

compatible = "shared-dma-pool";            //默认属性

reusable;                                  //默认属性

size = <0x00800000>; /* 8M */              //通常size为8M的倍数

alloc-ranges = <0x69000000 0x00800000>;    //起始地址和长度

linux,cma-default;                         //默认使用这段区域

};

这些属性可以查看rmem_cma_setup函数

实战

1 查看memory范围

2 查看预留区信息

3 dts添加代码

reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;//此处为要添加的cma。假设为video预留一块8M内存video_cma: video_cma@69000000 {compatible = "shared-dma-pool";reusable;reg = <0x69000000 0x800000>;};};

添加完dts信息后,系统在启动阶段会去自动读取并解析。

4 编写驱动

#include <linux/module.h>#include <linux/init.h>#include <linux/device.h>#include <linux/cma.h>#include <linux/mm.h>#include <linux/of.h>#include <linux/dma-contiguous.h>#include <linux/fs.h>#include <linux/miscdevice.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/types.h>#include <linux/uaccess.h>#include "cma.h"#define CMA_NAME "video_cma@69000000"struct cma_allocation {struct list_head list;struct page *cma_page;int count;unsigned long vaddr;};static struct device *cma_dev;static LIST_HEAD(cma_allocations);static DEFINE_SPINLOCK(cma_lock);/** struct cma *find_cma_by_name(const char *name) {*     int idx;*     for (idx = 0; idx < MAX_CMA_AREAS; idx++) {*         if (!strcmp(name, cma_areas[idx].name))*             return &cma_areas[idx];*     }*     return NULL;* }*/static ssize_tcma_test_read(struct file * file, char __user *buf, size_t count, loff_t *ppos){struct cma_allocation *alloc = NULL;bool ret;spin_lock(&cma_lock);if(!list_empty(&cma_allocations)) {alloc = list_first_entry(&cma_allocations, struct cma_allocation, list);list_del(&alloc->list);}spin_unlock(&cma_lock);if(!alloc)return -EIDRM;ret = dma_release_from_contiguous(cma_dev, alloc->cma_page, alloc->count);if (ret)dev_info(cma_dev, "free %d pages. vaddr: 0x%lx paddr: 0x%x\n",     alloc->count, alloc->vaddr, __pa(alloc->vaddr));kfree(alloc);return 0;}static ssize_tcma_test_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){struct cma_allocation *alloc;int ret;alloc = kmalloc(sizeof(struct cma_allocation), GFP_KERNEL);if(!alloc)return -ENOMEM;memset(alloc, 0, sizeof(struct cma_allocation));ret = kstrtouint_from_user(buf, count, 0, &alloc->count);if (ret != 0) {pr_notice("copy_from_user failed\n");return -EFAULT;}alloc->cma_page = dma_alloc_from_contiguous(cma_dev, alloc->count, 8, GFP_KERNEL);if (!alloc->cma_page) {dev_info(cma_dev, "alloc cma pages failed!\n");return -EFAULT;}alloc->vaddr = (unsigned long)page_address(alloc->cma_page);if(alloc->vaddr) {dev_info(cma_dev, "alloc %d pages, vaddr: 0x%lx paddr: 0x%x \n", alloc->count, alloc->vaddr, __pa(alloc->vaddr));spin_lock(&cma_lock);list_add_tail(&alloc->list, &cma_allocations);spin_unlock(&cma_lock);return count;} else {dev_err(cma_dev, "no mem in CMA area\n");kfree(alloc);return -ENOSPC;}}static const struct file_operations cma_test_fops = {.owner = THIS_MODULE,.read  = cma_test_read,.write = cma_test_write,};static struct miscdevice cma_test_misc = {.name = "cma_test",.fops = &cma_test_fops,};static int __init cma_test_init(void){struct device_node *np;struct cma *cma;int ret;ret = misc_register(&cma_test_misc);if(unlikely(ret)) {pr_err("failed to register cma test misc device!\n");goto err;}    cma_dev = cma_test_misc.this_device;dev_info(cma_dev, "registered.\n");np = of_find_node_by_path("/reserved-memory/video_cma@69000000");if (!np) {dev_info(cma_dev, "find node %s failed!\n", CMA_NAME);goto err;}dev_info(cma_dev, "find node %s ok!\n", CMA_NAME);cma = find_cma_by_name(CMA_NAME);if (cma) {dev_info(cma_dev, "find cma %s success, base pfn 0x%lx\n", cma->name,         cma->base_pfn);cma_dev->cma_area = cma;} else {dev_info(cma_dev, "find cma err!\n");goto err;}return 0;err:return -EFAULT;}static void __exit cma_test_exit(void){misc_deregister(&cma_test_misc);}module_init(cma_test_init);module_exit(cma_test_exit);MODULE_LICENSE("GPL");

5 测试,基于内核5.0

注:旧的内核中cma结构体不含name成员,使用不是很方便。

首先对比内存信息

未添加video cma

cat /proc/meminfo

CmaTotal: 16384 kB

CmaFree: 14592 kB

free -m

total used free shared buffers cached

Mem:                497  18   478    0    0        1

-/+ buffers/cache:       17   480

添加video cma

cat /proc/meminfo

CmaTotal: 24576kB

CmaFree: 22784kB

free -m

total used free shared buffers cached

Mem:                497  18   478    0    0        1

-/+ buffers/cache:       17   480

对比得到,cmatotal和cmafree都增加了8M,符合添加8M预留区并加入cma系统的预期。

free看到total不变,说明预留区已放入伙伴系统。

使用这段CMA

echo 1024 > /dev/cma_test 
misc cma_test: alloc 1024 pages, vaddr: 0xc9000000 paddr: 0x69000000

cat /proc/meminfo

CmaTotal: 24576 kB
CmaFree: 18688 kB

成功使用4M内存

释放这段内存

cat /dev/cma_test 
misc cma_test: free 1024 pages. vaddr: 0xc9000000 paddr: 0x69000000

cat /proc/meminfo

CmaTotal: 24576 kB
CmaFree: 22244 kB

可以发现,内存并未完全释放。原因是产生了碎片,无法合并。

cam的碎片问题还是蛮严重的,建议一个需求一块cma。

总结一下:

在内存不断使用的过程中,会产生碎片,日益使用对内存消耗大的设备比如camera,video,将很难分配到足够大的内存。cma的技术提前分配一块专用内存,在不用的时候释放到伙伴系统中,在使用的时候通过页迁移腾出这块内存。

cma也是一种内存调节技术之一。

*部分内容参考网络

linux内存调节之CMA相关推荐

  1. Linux内存管理:CMA(连续内存分配)(DMA)

    目录 什么是CMA 数据结构 CMA区域 cma_areas 的创建 dts方式 command line方式 将CMA区域添加到Buddy System CMA分配 <Linux内存管理:什么 ...

  2. linux cma内存,【原创】(十六)Linux内存管理之CMA,

    [原创](十六)Linux内存管理之CMA, 背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. ...

  3. 浅浅讲解下Linux内存管理之CMA

    说明: Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 Contiguous Memory Allo ...

  4. Linux内存管理之CMA简介

    1.概述 在linux驱动开发过程中经常需要使用到连续大块物理内存,尤其是DMA设备.而实际在系统经过长时间的允许之后,物理内存会出现比较严重的碎片化现象,虽然通过内存规整,内存回收等手动可以清理出一 ...

  5. linux 内存 cma,【原创】(十六)Linux内存管理之CMA

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. linux内存回收(二)--直接内存回收机制

    上一章,我们学习了kswapd的内存回收的机制,其本身是一个内核线程,它和调用者的关系是异步的,那么本章就开始学习内核的内存回收的方式.因为在不同的内存分配路径中,会触发不同的内存回收方式,内存回收针 ...

  7. 万字整理,图解Linux内存管理所有知识点

    Linux的内存管理可谓是学好Linux的必经之路,也是Linux的关键知识点,有人说打通了内存管理的知识,也就打通了Linux的任督二脉,这一点不夸张.有人问网上有很多Linux内存管理的内容,为什 ...

  8. Linux内存管理:知识点总结(ARM64)

    https://mp.weixin.qq.com/s/7zFrBuJUK9JMQP4TmymGjA 目录 Linux内存管理之CPU访问内存的过程 虚拟地址转换为物理地址的本质 Linux内存初始化 ...

  9. 万字整理,肝翻Linux内存管理所有知识点

    Linux的内存管理可谓是学好Linux的必经之路,也是Linux的关键知识点,有人说打通了内存管理的知识,也就打通了Linux的任督二脉,这一点不夸张.有人问网上有很多Linux内存管理的内容,为什 ...

最新文章

  1. 获取DOM节点的几种方式
  2. html标签一对一绑定的组件,一种原生组件替换HTML标签的轻量级方法尝试
  3. 一文详解Inception家族的前世今生(从InceptionV1-V4、Xception)附全部代码实现
  4. 驰骋工作流引擎设置消息收听
  5. 设置python编程环境_JupyterNotebook设置Python环境的方法步骤
  6. docker privileged作用_Docker环境下秒建Redis集群,连SpringBoot也整上了!
  7. 三星集团和华为集团,哪个更厉害?
  8. 变异系数法之matlab
  9. 工程之道,解读业界最佳的深度学习推理性能优化方案
  10. UI基本设计素材模板|完整的线框图
  11. java ASM看到写的比较好的文章
  12. Atitit 调用另外语言的功能 目录 1. Waht 常见的语言java python js sql xml h5 c# php等之间的互相调用 1 2. 为什么需要互相调用why 1 3. 常
  13. 【计算机视觉】数字图像处理(五)—— 图像的退化与复原
  14. 团队分享心得体会_团队合作心得体会总结
  15. Processing鼠标键盘
  16. 【计算机毕业设计】512网上商城购物系统
  17. PS练习2——相扣的五环
  18. DC/DCT/DCG 差别和联系
  19. 深析 | 手机摄像产业趋势—多摄/TOF/高倍变焦或成行业新风口
  20. 世界杯小组赛频繁爆冷?这或许是强队的谋略 一分钟带你了解2022卡塔尔世界杯赛制

热门文章

  1. 计算机学校用的哪种ps,经常用ps用什么配置电脑
  2. 卷积、自相关函数、功率谱密度
  3. wine安装迅雷、qq2009
  4. 超声波风速风向传感器的技术参数
  5. HTC ruu 解密 工具编译 提取解密key 自行解密 流程记录
  6. C# 特殊运算符 单问号(?)和双问号(??)
  7. Q for Mortals2笔记 -- 基本操作
  8. 网易云音乐搜索引擎 python+whoosh---(1)背景和环境介绍
  9. Python爬一爬世界杯赛程
  10. Netronome智能网卡——XDP卸载