当前位置: 首页 > news >正文

Linux cma预留内存使用与理解

1 概述

        本文主要介绍在Linux中如何预留出一块内存,以及预留内存的原理与使用方式,只停留在应用阶段,因为涉及Linux内存管理,没有深入Linux对预留管理的基础原理和源码,留待后续Linux内存管理章节分析。读者若只想了解如何在Linux下预留内存的应用,本文比较合适。

        本文验证环境使用qemu模拟vpress平台做的,关于该环境的搭建,参考笔者之前写的文章:

使用qemu搭建armv7嵌入式开发环境_qemu armv7-CSDN博客

2 Linux下的CMA

2.1 CMA

        Linux内核的连续内存分配器 (CMA),Contiguous Memory Allocator,是内存管理子系统中的一个模块,负责物理地址连续的内存分配。CMA的核心并不是设计精巧的算法来管理地址连续的内存块,实际上它的底层还是依赖内核伙伴系统这样的内存管理机制,或者说CMA是处于需要连续内存块的其他内核模块(例如DMA mapping framework)和内存管理模块之间的一个中间层模块。

        CMA同时也用于解决大内存预留带来的内存碎片,同时支持在预留内存空闲时返回系统使用,充分利用内存空间。

2.2 预留内存的声明

        预留内存可以指定常用的属性,常见有:

        (1). reusable:表示当前的内存区域除了被dma使用之外,还可以被内存管理(buddy)子系统reuse。

        (2). no-map:表示是否需要创建页表映射,对于通用的内存,必须要创建映射才可以使用,共享CMA是可以作为通用内存进行分配使用的,因此必须要创建页表映射。

        (3). linux,cma-default:如果要cma区域为共享区域,需要配置上linux,cma-default属性。 指定了 linux,cma-default 属性,内核在分配 cma 内存时会将这片内存当成默认的 cma 分配池使用,执行内存申请时如果没有指定对应的 cma 就使用默认 cma pool。

        (4). alignment:对齐参数,保留内存的起始地址需要向该参数对齐

        (5). alloc-ranges:指定可以用来申请动态保留内存的区间.

        (6). shared-dma-pool(compatible="shared-dma-pool"):

        有的时候设备驱动程序需要采用DMA的方式使用预留的内存,对于这种场景,可以dts中的节点属性设置为shared-dma-pool,从而生成为特定设备驱动程序预留的DMA内存池。这样,设备驱动程序仅需要以常规方式使用DMA API。

2.3 内核配置

        Linux要支持CMA内存,通过dma接口使用CMA内存池内存,需要打开以下配置:

CONFIG_DMA_CMA=y
CONFIG_CMA=y

也可大小相关调试开关

CONFIG_CMA_DEBUG=y
CONFIG_CMA_DEBUGFS=y

3 Linux下使用cma

        本节主要介绍驱动如何使用Linux的cam来预留内存方法,分别有预留内存给专门的设备驱动、通过DMA的API来预留内存、使用CAM内存池预留内存。同时Linux也支持通过启动参数来预留内存。

3.1 预留内存给专门设备驱动

        预留内存给设备驱动,不再将这些内存给系统使用时,需要在设备树申明no-map。如下设备树声明:

reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
    ...
        test_reserver_sh:test_reserver_sh_region@70000000 {
            compatible = "shared-dma-pool";
            reg = <0x70000000 0x6000000>;
            no-map;
        };
...
};
camsh_lab:camsh_lab {
        compatible = "test,cam_m_sh";
        memory-region = <&test_reserver_sh>;
        status = "okay";
  };

        在代码中使用:

static struct cma_lab_ampdata cmash_lab_amp = {
    .name = "lab_of_cma_sh",
    .type = TEST_CMA_SHARED,
};

static const struct of_device_id memory_cam_lab_match[] = {
    {
        .compatible = "test,cam_m",
        .data = &cma_lab_amp,
    },
    {
        .compatible = "test,cam_m_sh",
        .data = &cmash_lab_amp,
    },
    {
        .compatible = "test,cam_m_pool",
        .data = &cmash_lab_pool,
    },
    {}
};
MODULE_DEVICE_TABLE(of, memory_cam_lab_match);

static int cma_lab_probe(struct platform_device *pdev)
{
    const struct cma_lab_ampdata *cma_match_data = NULL;
    struct device *dev = &pdev->dev;
    unsigned int dma_addr = 0;
    void *addr;
    struct device_node* dev_node;
    struct resource res;
    void* base;
    int ret = 0;
    dev_info(dev, "cma data probe hit\n");
    cma_match_data = of_device_get_match_data(&pdev->dev);
    if (!cma_match_data) {
        dev_err(dev, "get driver data fail\n");
        return -ENODEV;
    }
    dev_info(dev, "test cma type %s, name %s\n", cma_match_data->type == TEST_CMA_PRIV?"private":"shared", cma_match_data->name);
    if(cma_match_data->type == TEST_CMA_PRIV) {
        ...
    } else if(cma_match_data->type == TEST_CMA_SHARED) {
        /* Initialize reserved memory resources */
        ret = of_reserved_mem_device_init(dev);
        if(ret) {
            dev_err(dev, "Could not get reserved memory\n");
            goto out_free_pages;
        }
        /* Allocate memory */
        dma_set_coherent_mask(dev, 0xFFFFFFFF);
        addr = dma_alloc_coherent(dev, PAGE_SIZE, &dma_addr, GFP_KERNEL);
        dev_info(dev, "get virtual addr 0x%p\n", addr);
        dev_info(dev, "get phy addr 0x%x\n", dma_addr);
    } else if(cma_match_data->type == TEST_CMA_POOL) {
       ...
    } else {
        dev_info(dev, "err of type %d\n", cma_match_data->type);
        return -ENODEV;
    }
    return 0;
out_free_pages:
    if(cma_match_data->type == TEST_CMA_SHARED) {
        dma_free_coherent(dev, PAGE_SIZE, addr, dma_addr);
    }
    return -ENXIO;
}

 运行结果

        内核日志:

        驱动日志:

3.2 通过DMA的API来预留内存

        通过获取设备树的预留内存信息后,使用dma接口初始化内存为预留内存,设备树声明如下:

   reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
...
        test_reserver_priv:test_reserver_priv@77000000 {
            reg = <0x77000000 0x00700000>;
            no-map;
        };
...
};
    memory_cam_lab:memory_cam_lab {
        compatible = "test,cam_m";
        contiguous-area = <&test_reserver_priv>;
        status = "okay";
    };

驱动中引用:

static struct cma_lab_ampdata cma_lab_amp = {
    .name = "lab_of_cma",
    .type = TEST_CMA_PRIV,
};
static const struct of_device_id memory_cam_lab_match[] = {
    {
        .compatible = "test,cam_m",
        .data = &cma_lab_amp,
    },
  ...
};
MODULE_DEVICE_TABLE(of, memory_cam_lab_match);

static int cma_lab_probe(struct platform_device *pdev)
{
    const struct cma_lab_ampdata *cma_match_data = NULL;
    struct device *dev = &pdev->dev;
    unsigned int dma_addr = 0;
    void *addr;
    struct device_node* dev_node;
    struct resource res;
    void* base;
    int ret = 0;
    dev_info(dev, "cma data probe hit\n");
    cma_match_data = of_device_get_match_data(&pdev->dev);
    if (!cma_match_data) {
        dev_err(dev, "get driver data fail\n");
        return -ENODEV;
    }
    dev_info(dev, "test cma type %s, name %s\n", cma_match_data->type == TEST_CMA_PRIV?"private":"shared", cma_match_data->name);
    if(cma_match_data->type == TEST_CMA_PRIV) {
        dev_node = of_parse_phandle(pdev->dev.of_node, "contiguous-area", 0);
        ret = of_address_to_resource(dev_node, 0, &res);
        if (ret) {
            dev_err(dev, "Failed to parse contiguous-area\n");
            return -EINVAL;
        }
        base =memremap(res.start, resource_size(&res), MEMREMAP_WB);
        dev_info(dev, "paddr:0x%x  vaddr:0x%p\n", (unsigned int)res.start, base);
    } else if(cma_match_data->type == TEST_CMA_SHARED) {
       ...
    } else if(cma_match_data->type == TEST_CMA_POOL) {
        ....
    } else {
        dev_info(dev, "err of type %d\n", cma_match_data->type);
        return -ENODEV;
    }
    return 0;
out_free_pages:
    if(cma_match_data->type == TEST_CMA_SHARED) {
        dma_free_coherent(dev, PAGE_SIZE, addr, dma_addr);
    }
    return -ENXIO;
}

        驱动日志:

3.3 使用CMA内存池

        使用CMA内存池,允许系统在内存空闲时回收使用这些内存,驱动可通过cma预留内存接口,获取使用CMA内存池的内存,设备树声明如下:

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

        ...
        test_reserver_cam:test_reserver_cam_region@78000000 {
            compatible = "shared-dma-pool";
            reg = <0x78000000 0x6000000>;
            reusable;
            linux,cma-default;
        };
};
    memory_cam_pool:memory_cam_pool {
        compatible = "test,cam_m_pool";
        status = "okay";
        memory-region = <&test_reserver_cam>;
    };

在设备驱动中使用内存池:

static int cma_lab_probe(struct platform_device *pdev)
{
    const struct cma_lab_ampdata *cma_match_data = NULL;
    struct device *dev = &pdev->dev;
    unsigned int dma_addr = 0;
    void *addr;
    struct device_node* dev_node;
    struct resource res;
    void* base;
    int ret = 0;

    dev_info(dev, "cma data probe hit\n");
    cma_match_data = of_device_get_match_data(&pdev->dev);
    if (!cma_match_data) {
        dev_err(dev, "get driver data fail\n");
        return -ENODEV;
    }

    dev_info(dev, "test cma type %s, name %s\n", cma_match_data->type == TEST_CMA_PRIV?"private":"shared", cma_match_data->name);
    if(cma_match_data->type == TEST_CMA_PRIV) {
       ...
    } else if(cma_match_data->type == TEST_CMA_SHARED) {
       ...
    } else if(cma_match_data->type == TEST_CMA_POOL) {
        struct page *page;
        void *virt_addr;
        phys_addr_t phys_addr;
        size_t size = 1024 * 1024; // 申请1MB内存 
        dev->dma_parms = &(struct device_dma_parameters){
            .max_segment_size = UINT_MAX,
        };
        // 2. 设置DMA掩码(确保地址范围合法)
        dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
        // 3. 申请CMA内存 
        page = dma_alloc_contiguous(dev, size, GFP_KERNEL);
        if (!page) {
            dev_err(dev, "Failed to allocate CMA memory\n");
            return -ENOMEM;
        }
        // 4. 获取物理地址和虚拟地址 
        phys_addr = page_to_phys(page);
        virt_addr = page_address(page);
        dev_info(dev, "CMA allocated: phys=0x%llx, virt=%p\n", (u64)phys_addr, virt_addr);
    } else {
        dev_info(dev, "err of type %d\n", cma_match_data->type);
        return -ENODEV;
    }
    return 0;
out_free_pages:
    if(cma_match_data->type == TEST_CMA_SHARED) {
        dma_free_coherent(dev, PAGE_SIZE, addr, dma_addr);
    }
    return -ENXIO;
}

static struct cma_lab_ampdata cmash_lab_pool = {
    .name = "lab_of_cma_pool",
    .type = TEST_CMA_POOL,
};
static const struct of_device_id memory_cam_lab_match[] = {
    ....
    {
        .compatible = "test,cam_m_pool",
        .data = &cmash_lab_pool,
    },
    {}
};
MODULE_DEVICE_TABLE(of, memory_cam_lab_match);

如上使用了cma内存预留接口 dma_alloc_contiguous,也可使用原始dma接口获取cma内存池的内存。最终都调用了cam_alloc接口。

内核日志:

驱动日志:

3.4 通过启动参数预留内存

        通过启动参数也可以为CMA预留内存,该方式预留的内存,将和系统公用,即在预留内存空闲时,将被系统使用,与上一节“使用CMA内存池”效果一样。

        在启动参数声明的cma标签语法如下:

 cma=nn[MG]@[start[MG][-end[MG]]]
            [KNL,CMA]
            Sets the size of kernel global memory area for
            contiguous memory allocations and optionally the
            placement constraint by the physical address range of
            memory allocations. A value of 0 disables CMA
            altogether. For more information, see
            kernel/dma/contiguous.c

        以下是笔者的实际例子:

setenv bootargs "init=/linuxrc root=/dev/mmcblk0p2 rw rootwait earlyprintk console=ttyAMA0 cma=96M@0x78000000"

        即在0x78000000开始处,预留96M(0x6000000)的内存。加载后得到日志如下:

驱动仍然使用3.3节的代码,从内存池里取内存使用,实际分配的地址为0x78100000不是0x78000000,前面1M用于CMA数据管理。

4 关于内存大小声明

        CMA内存声明预留内存大小和基地址,都要进行页内存对齐,否则将预留失败,一般页大小为4K,也有4M的页大小,根据具体平台而定,VP平台是4M空间,所以如果配置的内存没有4M对齐会失败。

        如下是一个大小(0x600000)没有对齐的结果:可看到第三个预留内存打印incorrect alignment of CMA region,预留内存对齐不合法失败。

正常预留日志:

5 完整测试代码

        设备树:

   reserved-memory {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        /* Chipselect 3 is physically at 0x4c000000 */
        vram: vram@4c000000 {
            /* 8 MB of designated video RAM */
            compatible = "shared-dma-pool";
            reg = <0x4c000000 0x00800000>;
            no-map;
        };
        test_reserver_sh:test_reserver_sh_region@70000000 {
            compatible = "shared-dma-pool";
            reg = <0x70000000 0x6000000>;
            no-map;
        };
        test_reserver_priv:test_reserver_priv@77000000 {
            reg = <0x77000000 0x00700000>;
            no-map;
        };
        test_reserver_cam:test_reserver_cam_region@78000000 {
            compatible = "shared-dma-pool";
            reg = <0x78000000 0x6000000>;
            reusable;
            linux,cma-default;
        };
    };
    camsh_lab:camsh_lab {
        compatible = "test,cam_m_sh";
        memory-region = <&test_reserver_sh>;
        status = "okay";
    };
    memory_cam_lab:memory_cam_lab {
        compatible = "test,cam_m";
        contiguous-area = <&test_reserver_priv>;
        status = "okay";
    };
    
    memory_cam_pool:memory_cam_pool {
        compatible = "test,cam_m_pool";
        status = "okay";
        memory-region = <&test_reserver_cam>;
    };

        驱动代码:

#include <linux/device.h>
#include <linux/err.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/pagemap.h>
#include <linux/scatterlist.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/dma-map-ops.h>

#define TEST_CMA_PRIV 2
#define TEST_CMA_SHARED 1
#define TEST_CMA_POOL 3
typedef struct cma_lab_ampdata {
    const char *name;
    unsigned int type;
}cma_lab_ampdata_t;
static int cma_lab_probe(struct platform_device *pdev)
{
    const struct cma_lab_ampdata *cma_match_data = NULL;
    struct device *dev = &pdev->dev;
    unsigned int dma_addr = 0;
    void *addr;
    struct device_node* dev_node;
    struct resource res;
    void* base;
    int ret = 0;
    dev_info(dev, "cma data probe hit\n");
    cma_match_data = of_device_get_match_data(&pdev->dev);
    if (!cma_match_data) {
        dev_err(dev, "get driver data fail\n");
        return -ENODEV;
    }
    dev_info(dev, "test cma type %s, name %s\n", cma_match_data->type == TEST_CMA_PRIV?"private":"shared", cma_match_data->name);
    if(cma_match_data->type == TEST_CMA_PRIV) {
        dev_node = of_parse_phandle(pdev->dev.of_node, "contiguous-area", 0);
        ret = of_address_to_resource(dev_node, 0, &res);
        if (ret) {
            dev_err(dev, "Failed to parse contiguous-area\n");
            return -EINVAL;
        }
        base =memremap(res.start, resource_size(&res), MEMREMAP_WB);
        dev_info(dev, "paddr:0x%x  vaddr:0x%p\n", (unsigned int)res.start, base);
    } else if(cma_match_data->type == TEST_CMA_SHARED) {
        /* Initialize reserved memory resources */
        ret = of_reserved_mem_device_init(dev);
        if(ret) {
            dev_err(dev, "Could not get reserved memory\n");
            goto out_free_pages;
        }
        /* Allocate memory */
        dma_set_coherent_mask(dev, 0xFFFFFFFF);
        addr = dma_alloc_coherent(dev, PAGE_SIZE, &dma_addr, GFP_KERNEL);
        dev_info(dev, "get virtual addr 0x%p\n", addr);
        dev_info(dev, "get phy addr 0x%x\n", dma_addr);
    } else if(cma_match_data->type == TEST_CMA_POOL) {
        struct page *page;
        void *virt_addr;
        phys_addr_t phys_addr;
        size_t size = 3 * 1024 * 1024; // 申请1MB内存 
        // dev->dma_parms = &(struct device_dma_parameters){
        //  .max_segment_size = UINT_MAX,
        // };
        // 2. 设置DMA掩码(确保地址范围合法)
        // dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
        // 3. 申请CMA内存 
        page = dma_alloc_contiguous(dev, size, GFP_KERNEL);
        if (!page) {
            dev_err(dev, "Failed to allocate CMA memory\n");
            return -ENOMEM;
        }
        // 4. 获取物理地址和虚拟地址 
        phys_addr = page_to_phys(page);
        virt_addr = page_address(page);
        dev_info(dev, "CMA allocated: phys=0x%llx, virt=%p\n", (u64)phys_addr, virt_addr);
    } else {
        dev_info(dev, "err of type %d\n", cma_match_data->type);
        return -ENODEV;
    }
    return 0;
out_free_pages:
    if(cma_match_data->type == TEST_CMA_SHARED) {
        dma_free_coherent(dev, PAGE_SIZE, addr, dma_addr);
    }
    return -ENXIO;
}
static struct cma_lab_ampdata cma_lab_amp = {
    .name = "lab_of_cma",
    .type = TEST_CMA_PRIV,
};
static struct cma_lab_ampdata cmash_lab_amp = {
    .name = "lab_of_cma_sh",
    .type = TEST_CMA_SHARED,
};
static struct cma_lab_ampdata cmash_lab_pool = {
    .name = "lab_of_cma_pool",
    .type = TEST_CMA_POOL,
};
static const struct of_device_id memory_cam_lab_match[] = {
    {
        .compatible = "test,cam_m",
        .data = &cma_lab_amp,
    },
    {
        .compatible = "test,cam_m_sh",
        .data = &cmash_lab_amp,
    },
    {
        .compatible = "test,cam_m_pool",
        .data = &cmash_lab_pool,
    },
    {}
};
MODULE_DEVICE_TABLE(of, memory_cam_lab_match);
static struct platform_driver cma_lab_driver = {
    .probe = cma_lab_probe,
    .driver = {
        .name = "cma_test",
        .of_match_table = of_match_ptr(memory_cam_lab_match),
    },
};
static int __init cmatest_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&cma_lab_driver);
    if (ret)
        printk(KERN_ERR "platform_driver_register fail %d", ret);
    printk(KERN_INFO "%s:%d exit\n", __func__, __LINE__);
    return 0;
}
module_init(cmatest_init);
static void __exit cmatest_exit(void)
{
    printk(KERN_INFO "%s:%d exit\n", __func__, __LINE__);
}
module_exit(cmatest_exit);
// module_platform_driver(cma_lab_driver);
MODULE_AUTHOR("vincent.donnal@bb.com");
MODULE_DESCRIPTION("a verifying using of cma driver");
MODULE_LICENSE("GPL v2");

        内核和驱动打印日志:

6 查看内存分配情况

6.1 查看cma分配

        通过/proc/meminfo查看cma分配内存大小和使用大小

一共分配了96M,使用了3M空间。

6.2 查看系统能使用的内存大小

        通过/proc/iomen查看sysram大小

        其中0x70000000 - 0x76000000和0x77000000 - 0x77700000预留专门设备驱动,不计入系统内存。

7 附录

相关文章:

  • “产业大数据”区域产业经济发展的新引擎!
  • dbeaver连接mongodb 插入日期变成了字符串
  • 希尔排序:算法原理与应用解析
  • 云端存储新纪元:SAN架构驱动的智能网盘解决方案
  • 机器学习实战之数据预处理、监督算法、无监督算法、模型评估与改进-思维导图拆分篇
  • Verilog 中寄存器类型(reg)与线网类型(wire)的区别
  • leetcode 2255. 统计是给定字符串前缀的字符串数目 简单
  • flutter-实现瀑布流布局及下拉刷新上拉加载更多
  • 详解java体系实用知识总结
  • 创新前沿 | 接管主机即刻增量CDP备份,高效保障接管期间业务安全!
  • 工业4G路由器赋能智慧停车场高效管理
  • 基于Linux下的MyBash命令解释器
  • 【13】Ajax爬取案例实战
  • 通过Docker快速搭建VoceChat | 开源轻量自托管聊天工具
  • 基于Spring Boot的网上商城系统的设计与实现(LW+源码+讲解)
  • 213.SpringSecurity:授权,授权实战,OAuth2,SpringSecurity中OAuth2认证服务器、资源服务器搭建,JWT
  • Oracle 19C 备份
  • vue3中<script setup>语法糖是什么意思。为什么叫语法糖,为什么叫糖,它甜吗
  • vue2前端日志数据存储(indexedD)自动清理3天前的数据
  • 数据结构初阶-二叉树链式
  • 中方拟解除对5名欧洲议会议员制裁?外交部:望中欧立法机构相向而行
  • 年轻人的事业!6家上海人工智能企业畅想“模范生”新征程
  • 万科:一季度营收近380亿元,销售回款率超100%
  • 何立峰出席驻沪中央金融机构支持上海建设国际金融中心座谈会并讲话
  • 习近平访问金砖国家新开发银行
  • 西班牙葡萄牙遭遇史上最严重停电:交通瘫了,通信崩了,民众疯抢物资