linux中的DMA提速1:各种内存分配验证
前言
驱动需要优化驱动中的DMA速度。在此记录。使用DMA传输1280*800的数据就是1M字节。
使用外设的DMA功能,就需要给外设相应的DMA缓存寄存器设置一个地址,这个地址在内核中叫总线地址,而我们使用kmalloc分配的地址是虚拟地址,它是通过物理地址经过MMU转换得来的。内核中的三种地址,虚拟地址、物理地址、总线地址。
1)mmap时使用的是物理地址。
2)总线地址(例如:dma_addr_t),是通过虚拟地址得到的。在内核中,用的最多的就是虚拟地址。
3)设置外设寄存器时,使用的是总线地址,例如本次学习的dma_addr_t类型的地址。
看下面这段代码,virt_to_phys(p)就是虚拟机地址转换为物理地址。对了,mmap的时候使用的是物理地址。
virt_to_phys(p) >> PAGE_SHIFT
当前测试环境PAGE_SIZE为4KB,所以PAGE_SHIFT的值是12,这句代码是为了获得该物理地址的页帧号,由此可知,这个地址需要是页(PAGE)对齐的。
#define PAGE_SHIFT 12#ifdef __ASSEMBLY__
#define PAGE_SIZE (1 << PAGE_SHIFT)
#else
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#endif#define PAGE_MASK (~(PAGE_SIZE-1))static int csi_dev_mmap(struct file *file, struct vm_area_struct *vma) {
void *p = xxx;
...if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(p) >> PAGE_SHIFT,vma->vm_end-vma->vm_start, vma->vm_page_prot)) {printk( "remap_pfn_range error\n");return -EAGAIN;}
...return 0
}
相关的宏__phys_to_pfn就是获得物理地址的页帧号,__pfn_to_phys是计算出页帧号对应的物理地址。pfn是page frame number的缩写。
/** Convert a physical address to a Page Frame Number and back*/
#define __phys_to_pfn(paddr) ((unsigned long)((paddr) >> PAGE_SHIFT))
#define __pfn_to_phys(pfn) ((phys_addr_t)(pfn) << PAGE_SHIFT)
提到虚拟地址,肯定是有MMU的,MMU是啥,MMU就是月老,负责物理地址与虚拟地址配对,一夫一妻吗?月老出生的年代是一夫一妻吗?没研究过。反正MMU不是一夫一妻的,一个物理地址,可以有多个虚拟地址,当然可以有对应的总线地址,一个虚拟地址同一时间只能映射到一个物理地址。
本次学习的DMA提速探索,就需要全部使用这三个身份,物理地址<=>虚拟地址<=>总线地址(dma_addr_t)。
还有两个函数用于虚拟地址和总线地址的转换,暂时用不到,先记录下来。
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);
一 验证的方案
所以初级方案,并不是说,这种方案不好,而是这种办法适合于大部分场景,在内核中绝大多数代码也是这么写的。这种方案容易理解,在内核中可以很容易的借鉴到。
方案一:dma_alloc_coherent函数
这种方案操作简单,dma_alloc_coherent返回虚拟地址,并且给dma_addr赋值为对应的总线地址。这个地址就可以设置到外设寄存器中,外设就可以自动向该地址写入数据,写入完毕后就会触发中断。
char *buf;
dma_addr_t dma_addr;
buf_size = 1280*800;
buf = dma_alloc_coherent(dev,PAGE_ALIGN(buf_size),&dma_addr,GFP_DMA | GFP_KERNEL);if(IS_ERR(buf)){DEBUG_CM("kmalloc error");return -ENOMEM;}
方案二:dma_map_single+kmalloc
kmalloc赋值分配空间,然后通过dma_map_single找到对应的总线地址,经过测试相较于前一种方案。速度上并没有提升。几乎就是一样的。
char *buf;
dma_addr_t dma_addr;
buf_size = 1280*800;
buf = kmalloc(buf_size,GFP_KERNEL|GFP_DMA); if(IS_ERR(buf)){DEBUG_CM("kmalloc error");return -ENOMEM;}new_node->dma_addr = dma_map_single(cm_dev->dev,
buf,
CSI_DEV_IMAGE_SIZE,
DMA_FROM_DEVICE);
方案三:__get_free_pages+dma_map_single
8表示2的八次方,就是256个页,页大小是4096.然后就是1048576,速度没有明显变化。我都开始怀疑,我这样测试是不是不对啊。
new_node->buf = (void *)__get_free_pages(GFP_KERNEL|GFP_DMA,8); if(IS_ERR(new_node->buf)){kfree(new_node);DEBUG_CM("kmalloc error");spin_unlock_irqrestore(&cm_dev->slock,flags);return -ENOMEM;}new_node->buf_size = CSI_DEV_IMAGE_SIZE;new_node->dma_addr = dma_map_single(cm_dev->dev, new_node->buf, new_node->buf_size, DMA_FROM_DEVICE);
也可以写成下面这样,效果是一样的
int order;new_node->buf_size = CSI_DEV_IMAGE_SIZE;order = get_order(CSI_DEV_IMAGE_SIZE);new_node->buf = (void *)__get_dma_pages(GFP_KERNEL, order); if(IS_ERR(new_node->buf)){kfree(new_node);DEBUG_CM("kmalloc error");spin_unlock_irqrestore(&cm_dev->slock,flags);return -ENOMEM;}new_node->dma_addr = dma_map_single(cm_dev->dev, new_node->buf, new_node->buf_size, DMA_FROM_DEVICE);
二 后来验证的方案
方案一:pgprot_noncached
如下所示,加上这个以后,在用户空间memcpy1280*800的数据,时间增加了20毫秒。
static int csi_dev_mmap(struct file *file, struct vm_area_struct *vma) {
...
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
...
}
pgprot_noncached宏有何神奇之处呢?定义如下所示,L_PTE_MT_MASK, L_PTE_MT_UNCACHED这两个宏是和二级页表,三级页表相关的。
typedef struct {unsigned long pgprot;
} pgprot_t;#define pgprot_val(x) ((x).pgprot)
#define __pgprot(x) ((pgprot_t) { (x) } )#define __pgprot_modify(prot,mask,bits) \__pgprot((pgprot_val(prot) & ~(mask)) | (bits))#define pgprot_noncached(prot) \__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
pgprot_noncached()实际禁止了相关页的Cache和写缓冲(Write Buffer),pgprot_writecombine()则没有禁止写缓冲。ARM的写缓冲器是一个非常小的FIFO存储器,位于处理器核与主存之间,其目的在于将处理器核和Cache从较慢的主存写操作中解脱出来。写缓冲区与Cache在存储层次上处于同一层次,但是它只作用于写主存。
方案二:dma_mmap_coherent
相关头文件
#include <linux/dma-mapping.h>
#include <linux/dma-buf.h>
方案三:dma_alloc_writecombine,执行失败
DEBUG_CSI_INIT("");new_node->buf_size = CSI_DEV_IMAGE_SIZE;new_node->buf = dma_alloc_writecombine(cm_dev->dev,PAGE_ALIGN(new_node->buf_size),&new_node->dma_addr,GFP_DMA | GFP_KERNEL);if(new_node->buf == NULL || IS_ERR(new_node->buf)){//kfree(new_node);DEBUG_CSI_INIT("new_node->buf = %p",new_node->buf);DEBUG_CSI_INIT("dma_alloc_writecombine error");//mutex_unlock(&cm_dev->lock);return -ENOMEM;}
经过测试,如果要使用这个函数,就需要实现struct dma_map_ops*指针,如下所示,如果没有自定义,就使用默认的arm_dma_ops。
static inline struct dma_map_ops *__generic_dma_ops(struct device *dev)
{if (dev && dev->archdata.dma_ops)return dev->archdata.dma_ops;return &arm_dma_ops;
}
然后代码改成
DEBUG_CSI_INIT("");new_node->buf_size = CSI_DEV_IMAGE_SIZE;cm_dev->dev->archdata.dma_ops = NULL;new_node->buf = dma_alloc_writecombine(cm_dev->dev,PAGE_ALIGN(new_node->buf_size),&new_node->dma_addr,GFP_DMA | GFP_KERNEL);if(new_node->buf == NULL || IS_ERR(new_node->buf)){DEBUG_CSI_INIT("new_node->buf = %p",new_node->buf);DEBUG_CSI_INIT("dma_alloc_writecombine error");return -ENOMEM;}DEBUG_CSI_INIT("");
第一次执行失败,原因是dma_alloc_writecombine的调用两侧加了自旋锁,去掉自旋锁后。就执行成功了,得出结论:dma_alloc_writecombine的不能位于自旋锁区间。
这个运行速度,几乎和以前一样。
方案四:dmam_alloc_noncoherent,执行失败
或者dma_alloc_noncoherent,都执行失败。
DEBUG_CSI_INIT("");new_node->buf_size = CSI_DEV_IMAGE_SIZE;new_node->buf = dmam_alloc_noncoherent(cm_dev->dev,PAGE_ALIGN(new_node->buf_size),&new_node->dma_addr,GFP_DMA | GFP_KERNEL);if(new_node->buf == NULL || IS_ERR(new_node->buf)){//kfree(new_node);DEBUG_CSI_INIT("new_node->buf = %p",new_node->buf);DEBUG_CSI_INIT("dmam_alloc_noncoherent error");//mutex_unlock(&cm_dev->lock);return -ENOMEM;}
[ 62.372363] init:/big/csi_driver/csi_ov/csi_dev.c:csi_dev_node_array_init:1769: new_node->buf = (null)
[ 62.381852] init:/big/csi_driver/csi_ov/csi_dev.c:csi_dev_node_array_init:1770: dmam_alloc_noncoherent error
三 后来的后来加的
方案1:cma
Contiguous Memory Allocator, CMA,连续内存分配器,用于分配连续的大块内存。CMA分配器,会Reserve一片物理内存区域:设备驱动不用时,内存管理系统将该区域用于分配和管理可移动类型页面;设备驱动使用时,用于连续内存分配,此时已经分配的页面需要进行迁移;此外,CMA分配器还可以与DMA子系统集成在一起,使用DMA的设备驱动程序无需使用单独的CMA API。
cma的设备树:由内容可知,cma的大小是8X = 8XX1M = 128M
reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;linux,cma {compatible = "shared-dma-pool";reusable;size = <0x8000000>;linux,cma-default;};
};
相关的头文件
#include <linux/dma-contiguous.h>
dma_alloc_from_contiguous函数原型
#define CONFIG_CMA_ALIGNMENT 8
/*** dma_alloc_from_contiguous() - allocate pages from contiguous area* @dev: Pointer to device for which the allocation is performed.* @count: Requested number of pages.* @align: Requested alignment of pages (in PAGE_SIZE order).** This function allocates memory buffer for specified device. It uses* device specific contiguous memory area if available or the default* global one. Requires architecture specific dev_get_cma_area() helper* function.*/
struct page *dma_alloc_from_contiguous(struct device *dev, int count,unsigned int align)
{if (align > CONFIG_CMA_ALIGNMENT)align = CONFIG_CMA_ALIGNMENT;return cma_alloc(dev_get_cma_area(dev), count, align);
}
/*** dma_alloc_from_contiguous() - allocate pages from contiguous area* @dev: Pointer to device for which the allocation is performed.* @count: Requested number of pages.* @align: Requested alignment of pages (in PAGE_SIZE order).** This function allocates memory buffer for specified device. It uses* device specific contiguous memory area if available or the default* global one. Requires architecture specific dev_get_cma_area() helper* function.*/
struct page *dma_alloc_from_contiguous(struct device *dev, int count,unsigned int align)
{if (align > CONFIG_CMA_ALIGNMENT)align = CONFIG_CMA_ALIGNMENT;return cma_alloc(dev_get_cma_area(dev), count, align);
}
使用这个函数时,编译报错,这不是WARNING吗?函数都找不到,就是错误。
WARNING: "dma_alloc_from_contiguous" [/big/csi_driver/csi_ov/csi_dev.ko] undefined!
dma_alloc_from_contiguous函数默认情况下并没有导出符号,这是不是意味着内核不想让我们使用它呢?修改文件drivers\base\dma-contiguous.c,添加导出符号。
EXPORT_SYMBOL(dma_alloc_from_contiguous);
测试代码,测试结果能工作,相比使用__get_dma_pages分配的内存缓冲区,速度没有提升。
DEBUG_CSI_INIT("CONFIG_CMA_ALIGNMENT =%d",CONFIG_CMA_ALIGNMENT);new_node->buf_size = CSI_DEV_IMAGE_SIZE;order = get_order(new_node->buf_size);new_node->page = dma_alloc_from_contiguous(cm_dev->dev,new_node->buf_size >> PAGE_SHIFT,order);if(new_node->page == NULL || IS_ERR(new_node->page)){DEBUG_CSI_INIT("new_node->page = %p",new_node->buf);DEBUG_CSI_INIT("dma_alloc_from_contiguous error");return -ENOMEM;}new_node->buf = (char*)page_to_phys(new_node->page);new_node->dma_addr = dma_map_single(cm_dev->dev, new_node->buf, new_node->buf_size, DMA_FROM_DEVICE);
结束
linux中的DMA提速1:各种内存分配验证相关推荐
- 在malloc函数中为什么常用sizeof来设定内存分配的大小?
在malloc函数中为什么常用sizeof来设定内存分配的大小? 例子:为40个整数变量分配内存并赋值,然后系统在收回这些内存. #include<stdlib.h> #include&l ...
- 【VS开发】【编程开发】【C/C++开发】结构体中的数组与指针的内存分配情况说明...
[VS开发][编程开发][C/C++开发]结构体中的数组与指针的内存分配情况说明 标签:[VS开发] [编程开发] 主要是疑惑在结构体定义的数组的内存空间与指针动态分配的内存空间,在地址上连续性.以及 ...
- linux线程堆分配,如何在Linux中的相同进程下为线程分配堆栈或内存
Linux中当前的"线程"概念是 NPTL. NPTL使用 clone(),包装 sys_clone().为新的'线程'分配堆栈在用户空间(即libc)中处理,而不是在内核(即Li ...
- Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配
Unix/Linux操作系统分析实验一 进程控制与进程互斥 Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件 Unix/Linux操作系统分析实验四 设备驱动: ...
- linux 校验文件生成,在Linux中了解如何使用MD5校验和生成和验证文件
校验和是用作在数据正确数字的总和,其可以稍后用于存储或传输期间检测数据中的错误的一个数字.MD5( 消息摘要5)的款项可以用作校验和,以验证文件或字符串在Linux的文件系统 . MD5校验和是128 ...
- C#中字符“.NET研究”串的内存分配与驻留池
刚开始学习C#的时候,就听说CLR对于String类有一种特别的内存管理机制:有时候,明明声明了两个String类的对象,但是他们偏偏却指向同一个实例.如下: String s1 = "He ...
- 操作系统 非连续分配_操作系统中的连续和非连续内存分配
操作系统 非连续分配 In this article, we will learn about the different types of memory management techniques ...
- Linux中的消息队列、共享内存,你确定都掌握了吗?
消息队列(message queue) 消息队列是消息的链表,存放在内存中,由内核维护 消息队列的特点 1.消息队列中的消息是有类型的. 2.消息队列中的消息是有格式的. 3.消息队列可以实现消息的随 ...
- aix oracle 内存限制,请教 AIX 与 Linux 中,怎样分析Oracle的内存占用?
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 列出如下脚本对应输出: ps vg|grep ora|awk '{sum=sum+$7-$10} END {print sum/1024}' svmon ...
最新文章
- 多线程下获取随机数的王者-ThreadLocalRandom
- Java笔试题二:读程序
- 卷积神经网络要点解析
- java课程设计 成绩_java课程设计 学生成绩管理
- android菜单点击功能怎么做的,单击android中的Menu按钮打开上下文菜单
- 大数据时代的数据存储,非关系型数据库MongoDB(一)(转)
- 【NOI 2018】归程(Kruskal重构树)
- LeetCode MySQL 1149. 文章浏览 II
- python scrapy框架爬虫_Python Scrapy爬虫框架
- 写java代码的软件_新手编写java代码使用什么软件
- 基于php的宠物领养系统
- laravel 框架使用hdjs 实现多图上传功能
- K8s中Secrets
- 人人车创始人李健的创业之路
- I am Groot java题解
- 如何设置和取消Mac电脑的开机密码?
- RestTemplate的ParameterizedTypeReference
- 自然语言处理(七):AG_NEWS新闻分类任务(TORCHTEXT)
- PHP通过微信网页授权获取用户基本信息
- 学习C++ 实践者之路
热门文章
- Matlab中pickic_MATLAB的数字基带传输系统
- python图标icon_【Python】站长之家icon图标爬虫
- TP4333移动电源芯片
- 埃拉托斯特尼筛法 the Sieve of Eratosthenes method
- win10 vmware苹果虚拟机(10.13High Sierra)rbenv安装cocoapods
- php万能文章采集器,PHPcms采集器网站采集软件使用文字教程
- 文档词频矩阵_如何通过词向量技术来计算 2 个文档的相似度?
- python小工具-使用百度语音接口进行划字朗读
- 多维前缀和 学习笔记 模板及例题
- 2022影视产业链分析白皮书