首页 > 系统相关 >设备驱动-15.内核kmalloc/vmalloc及CMA内存介绍

设备驱动-15.内核kmalloc/vmalloc及CMA内存介绍

时间:2024-03-23 21:22:39浏览次数:34  
标签:dma 15 cma 区域 CMA memory kmalloc 内存

1 kmalloc/vmalloc区别

函数 位置 特性 大小限制
kmalloc 物理内存映射区域 物理地址虚拟地址均连续 不能超过128K
kzalloc 物理内存映射区域 物理地址虚拟地址均连续 不能超过128K
vmalloc 虚拟内存映射区域 虚拟地址连续,物理地址不一定连续 无限制
vzalloc 虚拟内存映射区域 虚拟地址连续,物理地址不一定连续 无限制

kzalloc只是相当于附加了 __GFP_ZERO 标志。所以它除了申请内核内存外,还会对申请到的内存内容清零。
同理,vzalloc也是一样,会对申请内存内容清零。
image

kmalloc函数原型:

image

static __always_inline void *kmalloc(size_t size, gfp_t flags);

gpf flags含义:

|– 在进程上下文,可以睡眠     GFP_KERNEL
 |– 在进程上下文,不可以睡眠,如: GFP_ATOMIC
 |  |– 中断处理程序       GFP_ATOMIC
 |  |– 软中断          GFP_ATOMIC
 |  |– Tasklet         GFP_ATOMIC
 |– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL
 |– 用于DMA的内存,不可以睡眠  GFP_DMA |GFP_ATOMIC

如果进程上下文允许睡眠情况下尽量用GFP_KERNEL, 如果进程上下文禁止休眠的话(如中断,taskletd等)必须用GFP_ATOMIC

vmalloc函数原型:

image

extern void *vmalloc(unsigned long size);

注意:vmalloc和vfree可以睡眠,因此中断上下文禁止使用。

1.1 内存释放

void kfree(const void *);
extern void vfree(const void *addr);

2 kmalloc/vmalloc内存分配原理

3 CMA

3.1 CMA概述

连续内存分配器(Contiguous Memory Allocator),简称CMA。在系统长时间运行后,内存可能碎片化,很难找到连续的物理页,CMA很好的避免了这个问题。
举个例子:
手机上1300万像素的摄像头,一个像素占用3字节,拍摄一张照片需要大约37MB内存。在系统长时间运行后,内存可能碎片化,很难找到连续的物理页,页分配器(kmalloc)和块分配器(vmalloc)很可能无法分配这么大的连续内存块。

方案1:
最开始的一种解决方案是为设备保留一块大的内存区域,比如为摄像头驱动预留一块大内存,通过ioremap来映射后作为私有内存使用,缺点是:当设备驱动不使用的时候(大多数时间手机摄像头是空闲的),内核的其他模块不能使用这块内存。
方案2:
连续内存分配器CMA很好的解决了这个问题,保留一块大的内存区域,当设备驱动不使用的时候,内核的其他模块可以使用。一般我们把这块区域定义为reserved-memory。
image

3.2 CMA使用方法

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

3.3 CMA的定义

CMA每个区域实际上就是一个reserved memory。CMA分两种:

  1. 通用的CMA区域,该区域是给整个系统分配使用的;
  2. 专用的CMA区域,这种是专门为单个模块定义的。

dts中CMA属性:

1. reusable:表示当前的内存区域除了被dma使用之外,还可以被内存管理子系统reuse。
2. no-map:表示是否需要创建页表映射,对于通用的内存,必须要创建映射才可以使用,共享CMA是可以作为通用内存进行分配使用的,因此必须要创建页表映射。
3. 对于共享的CMA区域,需要配置上linux,cma-default属性,标志着它是共享的CMA。

下面定义了3段区域CMA:
1.全局CMA区域,节点名称是“linux,cma”,大小是2GB,8K对齐。
2.私有CMA区域,节点名字“de_mem0” “de_mem1”,128M给GPU 2D engine使用,私有无需建立页表映射。
3.私有CMA区域,节点名字“ion”,给video pipeline使用,私有无需建立页表映射。

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

            cma_reserved: linux,cma {
                    compatible = "shared-dma-pool";
                    reusable;
                    size = <0x0 0x80000000>; // 2GB
                    alignment = <0x0 0x2000>; // 8KB
                    linux,cma-default;
            };
            de_reserved0: de_mem0 {
                            reg = <0x1 0x10000000 0x0 0x8000000>; // 128M, for 2de
                            no-map;
            };
            de_reserved1: de_mem1 {
                            reg = <0x1 0x18000000 0x0 0x8000000>; // 128M, for 2de
                            no-map;
            };
            ion_reserved: ion {
                    compatible = "ion-region";
                    size = <0x0 0x04000000>; // 64MB
            };
            vo_2de0 {
                    compatible = "sophgo,vg-lite0";
                    memory-region = <&de_reserved0>;
                    interrupt-parent = <&gic>;
                    interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
                    interrupt-names = "vo_2de0";
            };
            vo_2de1 {
                    compatible = "sophgo,vg-lite1";
                    memory-region = <&de_reserved1>;
                    interrupt-parent = <&gic>;
                    interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
                    interrupt-names = "vo_2de1";
            };

2de模块中定义memory-region属性,并且把对应dts定义的cma节点de_reserved0,de_reserved1传递给该模块。

3.4 从CMA区域分配内存

struct page   *page = NULL;
page = cma_alloc(dev_get_cma_area(dev),mem_size, 0, GFP_KERNEL);

dev_get_cma_area可以获取对应的cma handler,如果获取不到,比如对应模块中并未定义memory-region,那么就会返回共享的cma handler,还记的上面的 linux,cma-default属性吗,共享cma区域会被作为缺省cma来使用。

不过一般内核模块要使用CMA内存时,使用的接口依然是dma的接口:

extern void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag);
extern void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle);

最终也会进入dma_alloc_from_contiguous调用cma_alloc分配内存。

struct page *dma_alloc_from_contiguous(struct device *dev, size_t count,
	unsigned int align, bool no_warn)
{
	if (align > CONFIG_CMA_ALIGNMENT)
		align = CONFIG_CMA_ALIGNMENT;
	return cma_alloc(dev_get_cma_area(dev), count, align, no_warn);
}

bool dma_release_from_contiguous(struct device *dev, struct page *pages,
	 int count)
{
	return cma_release(dev_get_cma_area(dev), pages, count);
}

generic dma coherent:
对于dma framwork来说,当我们使能并且配置了CMA区域时会使用CMA进行内存分配,但是内核依然对于旧的实现方式进行了兼容,可以通过 CONFIG_HAVE_GENERIC_DMA_COHERENT 来进行配置。
image

定义 no-map 属性,进而这块内存就从系统中剥离出来了,无法被伙伴系统所使用,但是可以在dma核心层通过remap的形式创建页表映射来使用它。

3.5 CMA内存原理和流程

设备驱动程序不能直接使用连续内存分配器,而是调用DMA映射框架来使用连续内存分配器CMA。

3.5.1 CMA调用层次框架:

image

  1. 最底层为页分配器(以后分析),cma_alloc用来从CMA区域分配页,cma_release用来释放从CMA区域分配的页。
  2. 第3层为DMA映射框架专用的连续内存分配器,简称DMA专用连续内存分配器,提供的接口dma_alloc_from_contiguous用来从CMA区域分配页,接口dma_release_from_contiguous用来释放从CMA区域分配的页。
  3. 第4层就是DMA通用映射框架,供驱动程序调用dma_alloc_coherent和dma_alloc_noncoherent用来分配内存,接口dma_free_coherent和dma_free_noncoherent用来释放内存。

3.5.2 CMA结构体

mm/cma.h
struct cma {
	 unsigned long    base_pfn; //该CMA区域的起始页帧号
	 unsigned long    count; //该cma区域的页数
	 unsigned long    *bitmap; //位图,每个位描述对应的页的分配状态,0表示空闲,1表示已分配
	 unsigned int order_per_bit;//位图中的每个位描述的物理页的阶数,目前取值为0,表示每个位描述一页
	 struct mutex     lock;
	 const char *name;
};

mm/cma.c
struct cma cma_areas[MAX_CMA_AREAS];//定义多个CMA区域。
unsigned cma_area_count;//表示实际使用的cma区域数量

3.5.3 CMA区域初始化

linux内核首先需要解析dtb中节点“memory”,把内存块添加到memblock的memory类型,memory类型保存内存块的物理地址范围,reserved类型保存保留内存块的物理地址范围,CMA区域就属于保留内存块。
image
创建CMA区域的执行流程如下所示:
image
linux内核启动时,当调用到__reserved_mem_init_node时会调用所有使用RESERVEDMEM_OF_DECLARE声明的CMA区域。
其中全局CMA区域的初始化函数是rmem_cma_setup:

3.5.3.1 全局cma内存初始化

image

来看调用的cma_init_reserved_mem:
image
从数组cma_areas分配一个数组项,保存CMA区域的起始页帧号和页数。dts指定了属性“linux,cma-default”,那么这个CMA区域是默认的CMA区域,最后设置全局变量dma_contiguous_default_area指向这个CMA区域(默认全局CMA区域)
红色圈出了该cma区域的dts描述和dts是不是完全吻合。

如果内核参数或配置宏配置全局CMA区域,cma区域流程如下所示:
image
image
image

3.6 CMA内存分配

/**
 * cma_alloc() - allocate pages from contiguous area
 * @cma:   Contiguous memory region for which the allocation is performed.
 * @count: Requested number of pages.
 * @align: Requested alignment of pages (in PAGE_SIZE order).
 * @no_warn: Avoid printing message about failed allocation
 *
 * This function allocates part of contiguous memory on specific
 * contiguous memory area.
 */
struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align,
			   bool no_warn);

函数cma_alloc负责从CMA区域分配内存,如果CMA区域中的物理页已经被页分配器分配出去(页被借走),需要把物理页迁移到其他地方。

3.7 CMA内存回收

/**
 * cma_release() - release allocated pages
 * @cma:   Contiguous memory region for which the allocation is performed.
 * @pages: Allocated pages.
 * @count: Number of allocated pages.
 *
 * This function releases memory allocated by cma_alloc().
 * It returns false when provided pages do not belong to contiguous area and
 * true otherwise.
 */
bool cma_release(struct cma *cma, const struct page *pages, unsigned int count);

标签:dma,15,cma,区域,CMA,memory,kmalloc,内存
From: https://www.cnblogs.com/fuzidage/p/17664807.html

相关文章

  • P3592 [POI2015] MYJ
    P3592[POI2015]MYJ要求总和最大,有两张思路:贪心和dp。稍微想一下,发现贪心思考量太大,考虑dp观察n的数据范围,以及转移方式,可以想到区间dp发现转移跟区间最小值有关,设\(f_{l,r,k}\)为区间\([l,r]\)中最小值不小于\(x\)的答案。转移枚举最小值的位置\(p\),\(f_{l,r,k}......
  • CF1615F LEGOndary Grandmaster
    CF1615FLEGOndaryGrandmaster计数好题,转换条件+转化贡献+组合数首先题目的操作没有什么好的性质,考虑一个经典的trick,将奇数位置上的数字取反,于是题目的操作变成\(01\rightarrow10\)或\(10\rightarrow01\)。这个操作的性质就是序列中\(1\)的总数不变,并且操作可以抽象......
  • Day 15(操作符)赋值+单目+关系+逻辑+条件+逗号表达式+下标引用+函数调用
    1.赋值操作符:=   复合赋值符:+=         -=       *=       /=     &=      |=     ^=       %=    >>=    <<=eg: a=a+2→a+=2  a=a>>1→a>>=1连续赋值:a=b=c(从右向左运行)(不推荐此方法)2......
  • CMU15445 2022fall project3
    CMU154452022fallproject3project3相对project2的b+树来说简单太多了,整体没有什么痛苦的debug,基本就看看其他算子的实现参考一下,很快就能写出来。Task1-AccessMethodExecutorsSeqScan首先我们需要知道:init是做一些初始化工作的,next是留给上层节点调用的,SeqScanExecuto......
  • stp的监听和学习状态为什么需要15秒
    STP(生成树协议)的监听和学习状态各自需要15秒,这主要是为了确保网络在角色选举和地址学习的过程中有足够的稳定性和准确性。1.监听状态需要15秒,主要是为了避免STP协议在收敛过程中产生临时环路。监听状态会持续15秒,以确保BPDU(桥接协议数据单元)有足够的时间在整个网络进行传递。......
  • 代码随想录算法训练营第day54|392.判断子序列 、 115.不同的子序列
    目录392.判断子序列115.不同的子序列392.判断子序列力扣题目链接(opensnewwindow)给定字符串s和t,判断s是否为t的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而......
  • 15,zabbix-elk
    1、安装logstash2、监控/home/elk/test.log文件[[email protected]]#[[email protected]]#ifconfigeth0:flags=4163<UP,BROADCAST,RUNNING,MULTICAST>mtu8500inet10.206.16.11netmask255.255.240.0broadcast10......
  • Android开发笔记[15]-设置页
    摘要使用MMKV数据框架实现设置页数据同步,设置页可以对其他页面进行设置;设置页数据通过MMKV框架持久化存储,重启APP不丢失.关键信息AndroidStudio:Iguana|2023.2.1Gradle:distributionUrl=https://services.gradle.org/distributions/gradle-8.4-bin.zipjvmTarget='1.......
  • 【LeetCode-153.寻找旋转排序数组的最小值】
    已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums=[0,1,2,4,5,6,7] 在变化后可能得到:若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]注意,数组 [a[0],a[1],a[2],...,a[n-1......
  • 洛谷题单指南-集合-P1525 [NOIP2010 提高组] 关押罪犯
    原题链接:https://www.luogu.com.cn/problem/P1525题意解读:有很多罪犯,要关到两座监狱,有一些罪犯之间有仇,并且可以量化出仇恨值,如果关在一起就会冲突,造成的影响就是仇恨值,要使得造成的影响最小,如果可以完全不起冲突,输出0。解题思路:首先,要让冲突影响最小化,显然应该把仇恨大的罪犯......