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

作者

QQ群:852283276
微信:arm80x86
微信公众号:青儿创客基地
B站:主页 https://space.bilibili.com/208826118

参考

Dynamic DMA mapping
dma_alloc_coherent memory with mmap
CMA模块学习笔记
A deep dive into CMA
Linux内核最新的连续内存分配器(CMA)——避免预留大块内存
CMA 详细分析
CMA连续物理内存用户空间映射—(一)
CMA连续物理内存用户空间映射—(二)
linux cma内存管理
Linux-3.14.12内存管理笔记【连续内存分配器(CMA)】
LINUX CMA 详细分析
Linux内存管理:CMA
dts中memreserve和reserved-memory的区别
内存初始化代码分析(一):identity mapping和kernel image mapping
内存初始化代码分析(二):内存布局
内存初始化代码分析(三):创建系统内存地址映射
meminfo与vmallocinfo实例
Cortex-A8处理器memcpy的优化方案
ARM64-memcpy.S 汇编源码分析
/dev/mem可没那么简单
DPDK中的memcpy性能优化及思考
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)定义了resuable属性。

dma_alloc_coherent

参考内核文档Documentation/DMA-API.txtDocumentation/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; 
};

否则会报错,

rmem_cma_setup line256 base[0x374d3000] size[0x8000000] mask[0x3fffff]
Reserved memory: incorrect alignment of CMA region

后续调试zynqmp发现uboot未配置MAPSZ,导致内核启动错误,受到启发,更新zynq配置,cma错误消除,可以看到预留地址变成0x30000000,

Reserved memory: created CMA memory pool at 0x30000000, size 256 MiB
Reserved memory: initialized node linux,cma, compatible id shared-dma-pool
Memory policy: Data cache writealloc
On node 0 totalpages: 262144
free_area_init_node: node 0, pgdat 40702b00, node_mem_map 6f778000
  Normal zone: 2048 pages used for memmap
  Normal zone: 0 pages reserved
  Normal zone: 262144 pages, LIFO batch:31
PERCPU: Embedded 9 pages/cpu @6f759000 s8128 r8192 d20544 u36864
pcpu-alloc: s8128 r8192 d20544 u36864 alloc=9*4096
pcpu-alloc: [0] 0 [0] 1 
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 260096

cma预留内存源代码在of_reserved_mem.c,

static int __init __reserved_mem_alloc_size(unsigned long node,
	const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
{
	int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
	phys_addr_t start = 0, end = 0;
	phys_addr_t base = 0, align = 0, size;
	int len;
	const __be32 *prop;
	int nomap;
	int ret;

	prop = of_get_flat_dt_prop(node, "size", &len);
	if (!prop)
		return -EINVAL;

	if (len != dt_root_size_cells * sizeof(__be32)) {
		pr_err("invalid size property in '%s' node.\n", uname);
		return -EINVAL;
	}
	size = dt_mem_next_cell(dt_root_size_cells, &prop);

	nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

	prop = of_get_flat_dt_prop(node, "alignment", &len);
	if (prop) {
		if (len != dt_root_addr_cells * sizeof(__be32)) {
			pr_err("invalid alignment property in '%s' node.\n",
				uname);
			return -EINVAL;
		}
		align = dt_mem_next_cell(dt_root_addr_cells, &prop);
	}

	/* Need adjust the alignment to satisfy the CMA requirement */
	if (IS_ENABLED(CONFIG_CMA)
	    && of_flat_dt_is_compatible(node, "shared-dma-pool")
	    && of_get_flat_dt_prop(node, "reusable", NULL)
	    && !of_get_flat_dt_prop(node, "no-map", NULL)) {
		unsigned long order =
			max_t(unsigned long, MAX_ORDER - 1, pageblock_order);

		align = max(align, (phys_addr_t)PAGE_SIZE << order);
	}
	...
}

CMA区域的空闲部分可以被用户态程序,

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 操作系统概述 1 1.1 认识操作系统 1 1.1.1 从使用者角度 1 1.1.2 从程序开发者角度 2 1.1.3 从操作系统在整个计算机系统中所处位置 2 1.1.4 从操作系统设计者的角度 3 1.2 操作系统的发展 4 1.2.1 操作系统的演变 4 1.2.2 硬件的发展轨迹 5 1.2.3 软件的轨迹 6 1.2.4 单内核与微内核操作系统 7 1.3 开放源代码的Unix/Linux操作系统 8 1.3.1 Unix的诞生和发展 8 1.3.2 Linux的诞生 9 1.3.3 操作系统标准POSIX 9 1.3.4 GNU和Linux 9 1.3.5 Linux开发模式 10 1.4 Linux内核 10 1.4.1 Linux内核的位置 10 1.4.2 Linux内核的作用 11 1.4.3 Linux内核子系统 11 1.5 Linux内核源代码 13 1.5.1 多版本的内核源代码 13 1.5.2 Linux内核源代码的结构 13 1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存寻址简介 17 2.1.1 Intel x86 CPU寻址方式的演变 18 2.1.2 IA32寄存器简介 19 2.1.3 物理地址、虚拟地址及线性地址 21 2.2 分段机制 22 2.2.1 地址转换及保护 24 2.2.2 Linux中的段 24 2.3 分页机制 25 2.3.1 页与页表 25 2.3.2 线性地址到物理地址的转换 28 2.3.3 分页示例 28 2.3.4 页面高速缓存(cache) 29 2.3.5 Linux中的分页机制 30 2.4 Linux中的汇编语言 31 2.4.1 AT&T与Intel汇编语言的比较 31 2.4.2 AT&T汇编语言的相关知识 32 2.5 Linux系统地址映射示例 33 习题2 35 第3章 进程 37 3.1 进程介绍 37 3.1.1 程序和进程 37 3.1.2 进程的层次结构 38 3.1.3 进程状态 39 3.1.4 进程实例 40 3.2 进程控制块 41 3.2.1 进程状态 42 3.2.2 进程标识符 43 3.2.3 进程之间的亲属关系 43 3.2.4 进程控制块的存放 44 3.3 进程的组织方式 45 3.3.1 进程链表 45 3.3.2 散列表 46 3.3.3 可运行队列 47 3.3.4 等待队列 47 3.4 进程调度 48 3.4.1 基本原理 48 3.4.2 时间片 50 3.4.3 Linux进程调度时机 50 3.4.4 进程调度的依据 51 3.4.5 调度函数schedule()的实现 52 3.5 进程的创建 54 3.5.1 创建进程 55 3.5.2 线程及其创建 56 3.6 与进程相关的系统调用及其应用 58 3.6.1 fork系统调用 58 3.6.2 exec系统调用 59 3.6.3 wait系统调用 60 3.6.4 exit系统调用 62 3.6.5 进程的一生 63 3.7 与调度相关的系统调用及应用 63 习题3 65 第4章 内存管理 67 4.1 Linux内存管理概述 67 4.1.1 虚拟内存、内核空间和用户空间 67 4.1.2 虚拟内存实现机制间的关系 69 4.2 进程用户空间的管理 70 4.2.1 进程用户空间的描述 71 4.2.2 进程用户空间的创建 74 4.2.3 虚存映射 76 4.2.4 进程的虚存区示例 76 4.2.5 与用户空间相关的系统调用 78 4.3 请页机制 79 4.3.1 缺页异常处理程序 79 4.3.2 请求调页 81 4.3.3 写时复制 83 4.4 物理内存分配与回收 83 4.4.1 伙伴算法 85 4.4.2 物理页面的分配 86 4.4.3 物理页面的回收 88 4.4.4 slab分配模式 89 4.4.5 内核空间非连续内存区的分配 93 4.5 交换机制 95 4.5.1 交换的基本原理 95 4.5.2 页面交换守护进程kswapd 99 4.6 内存管理实例 99 4.6.1 相关背景知识 100 4.6.2 代码体系结构介绍 100 4.6.3 实现步骤 103 4.6.4 程序代码 103 习题4 108 第5章 中断和异常 110 5.1 中断的基本知识 110 5.1.1 中断向量 110 5.1.2 外设可屏蔽中断 111 5.1.3 异常及非屏蔽中断 112 5.1.4 中断描述符表 112 5.1.5 相关汇编指令 113 5.2 中断描述符表的初始化 114 5.2.1 IDT表项的设置 114 5.2.2 对陷阱门和系统门的初始化 115 5.2.3 中断门的设置 116 5.3 中断处理 116 5.3.1 中断和异常的硬件处理 116 5.3.2 中断请求队列的建立 117 5.3.3 中断处理程序的执行 119 5.3.4 从中断返回 121 5.4 中断的下半部处理机制 121 5.4.1 为什么把中断分为两部分来处理 122 5.4.2 小任务机制 122 5.4.3 下半部 124 5.4.4 任务队列 125 5.5 中断应用——时钟中断 125 5.5.1 时钟 125 5.5.2 时钟运作机制 126 5.5.3 Linux的时间系统 127 5.5.4 时钟中断处理程序 128 5.5.5 时钟中断的下半部处理 129 5.5.6 定时器及其应用 129 习题5 132 第6章 系统调用 133 6.1 系统调用与应用编程接口、系统命令、内核函数的关系 133 6.1.1 系统调用与API 133 6.1.2 系统调用与系统命令 134 6.1.3 系统调用与内核函数 134 6.2 系统调用处理程序及服务例程 135 6.2.1 初始化系统调用 136 6.2.2 system_call()函数 136 6.2.3 参数传递 137 6.2.4 跟踪系统调用的执行 139 6.3 封装例程 140 6.4 添加新系统调用 141 6.5 实例——利用系统调用实现一个调用日志收集系统 143 6.5.1 代码体系结构 143 6.5.2 把代码集成到内核中 146 6.5.3 实现步骤 148 习题6 148 第7章 内核中的同步 149 7.1 临界区和竞争状态 149 7.1.1 临界区举例 149 7.1.2 共享队列和加锁 150 7.1.3 确定保护对象 151 7.1.4 死锁 152 7.1.5 并发执行的原因 153 7.2 内核同步方法 153 7.2.1 原子操作 153 7.2.2 自旋锁 155 7.2.3 信号量 156 7.3 并发控制实例 157 7.3.1 内核任务及其并发关系 158 7.3.2 实现机制 158 7.3.3 关键代码解释 162 7.3.4 实现步骤 163 习题7 164 第8章 文件系统 165 8.1 Linux文件系统基础 165 8.1.1 Linux文件结构 165 8.1.2 Linux文件系统 166 8.1.3 文件类型 167 8.1.4 文件访问权限 168 8.2 虚拟文件系统 168 8.2.1 虚拟文件系统的引入 168 8.2.2 VFS中的数据结构 170 8.2.3 VFS超级块数据结构 171 8.2.4 VFS的索引节点 173 8.2.5 目录项对象 174 8.2.6 与进程相关的文件结构 176 8.2.7 主要的数据结构之间的关系 179 8.3 文件系统的注册、安装与卸载 180 8.3.1 文件系统的注册和注销 180 8.3.2 文件系统的安装 181 8.3.3 文件系统的卸载 183 8.4 页缓冲区 183 8.4.1 address_space对象 183 8.4.2 address_space对象的操作函数表 184 8.5 文件的打开与读写 185 8.5.1 打开文件 185 8.5.2 读写文件 187 8.6 编写一个文件系统 189 8.6.1 Linux文件系统的实现要素 189 8.6.2 什么是romfs文件系统 191 8.6.3 romfs文件系统的布局与文件结构 191 8.6.4 具体实现的对象 192 习题8 195 第9章 设备驱动 196 9.1 概述 196 9.2 设备驱动程序基础 198 9.2.1 I/O端口 199 9.2.2 设备文件 200 9.2.3 中断处理 201 9.2.4 设备驱动程序框架 203 9.3 字符设备驱动程序 204 9.3.1 字符设备驱动程序的注册 204 9.3.2 简单的字符设备驱动程序示例 205 9.4 块设备驱动程序 208 9.4.1 块设备驱动程序的注册 209 9.4.2 块设备请求 212 习题9 215 附录A 内核中的链表 216 A.1 链表数据结构简介 216 A.2 内核链表数据结构的定义及初始化 217 A.3 操作链表的接口 218 A.4 遍历链表 219 附录B 内核模块 221 B.1 什么是模块 221 B.2 编写一个简单的模块 221 B.3 模块编程的基础知识 222 B.4 模块的编译 224 B.5 模块实用程序modutils 226 附录C Linux内核编译 228 C.1 内核简介 228 C.2 为什么重新编译内核 228 C.3 内核编译模式 229 C.4 新版本内核的获取和更新 229 C.5 内核编译 230 C.6 修改并重启管理器 232 附录D Linux编程基础(C语言环境) 233 D.1 Linux编程常识 233 D.1.1 相关标准(ANSI C、POSIX、SVID、XPG) 233 D.1.2 函数库和系统调用 234 D.1.3 在线文档(man、info、HOWTO) 235 D.1.4 C语言编程风格 237 D.2 Linux上的C/C++编译器和调试器 238 D.2.1 运行gcc/egcs 238 D.2.2 gcc/egcs的主要选项 240 D.2.3 gdb简介 240 D.2.4 gdb的常用命令 241 D.2.5 gdb使用示例 242 D.3 GNU make和makefile 243 D.3.1 GNU make 243 D.3.2 makefile的基本结构 243 D.3.3 makefile的变量 244 D.3.4 GNU make的主要预定义变量 245 D.3.5 GNU make的隐含规则 245 D.3.6 运行make 246

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值