1.背景
在日常嵌入式开发中总会发现cpu性能不够用,动不动cpu占用率就飙到90%以上了。这篇文章笔者就从系统性能优化的方向,介绍一种优化的思路,即如标题所示运用DMA的方式搬运数据替代memcpy()C库函数拷贝内存数据。在实现相关代码方案后,并运用一种方法去度量比较这两种方式的拷贝效率和性能占用情况,方便以后读者能够灵活运用到自己的工程项目中。
2.DMA相关概念介绍
DMA(Direct Memory Access)是一种高效的数据传输方式,允许某些硬件子系统在不需要CPU介入的情况下直接访问系统内存。
DMA的主要作用是优化数据在内存和设备之间的传输,以下是它的一些关键特点:
- 高效性:通过DMA,数据可以直接在内存和I/O设备之间传输,而不需要经过CPU。这样可以减少CPU的负担,使其可以处理其他任务。
- 自主性:一旦CPU初始化了传输操作,DMA控制器就会接管总线控制权,自行完成数据传输任务。这意味着在传输过程中,CPU可以并行执行其他操作。
- 高速性:DMA通常用于高速数据传输,特别是在大量数据的移动时,能够显著提高系统的吞吐量。
- 硬件控制:DMA控制器负责生成内存地址、控制信号,并对传输的数据量进行计数。这些操作都是硬件控制的,不涉及软件层面的处理。
总的来说,DMA不仅提高了数据传输的效率,还释放了CPU资源,使得CPU可以专注于其他计算任务。这种机制在现代计算机系统中非常重要,尤其是在处理大量数据或需要快速响应的外设时。
一般情况下,在嵌入式设备环境中,DMA主要用来作为SOC片上外设(如SPI,UART,SDIO等等)到驱动层内存之间的数据传输,这一部分主要是SOC厂商给适配实现了,不需要下游终端产品商过多操心。
3.Linux内核DMA子系统框架
图1,DMA系统框架
如上图所示,站在provider和consumer的角度看,DMA系统框架主要分为3个部分。
1,DMA provider主要包括DMA硬件控制器和紧贴硬件的DMA控制器驱动程序,这一部分代码主要是SOC芯片厂商编写,主要是针对控制器层的寄存器级别的代码。
2,DMA核心层代码主要是Linux开源社区的维护者编写和维护,DMA核心层通过封装一些公共的函数接口和操作方法,屏蔽了底层的操作细节,向上提供了统一的操作接口和规范,方便了不同的SOC芯片厂商移植和适配自家的控制器驱动程序。(笔者认为核心层的思想正是软件设计的精妙绝美所在)
3,DMA consumer主要是用于申请(消费)DMA请求,使用DMA。对于SOC芯片厂商的SPI,SDIO等控制器驱动,SOC芯片厂商会在其驱动代码中申请DMA通道,使能DMA传输,释放DMA通道。应用态层面无直接有关DMA的操作,都是通过系统调用间接的使用DMA功能。
我们这篇文章也是需要实现DMA consumer侧的驱动程序,实现数据从内存到内存的拷贝。
4.DMA内存缓冲区设计
在设计使用DMA的方式进行内存拷贝,一个核心的问题是该怎么设计相关的内存缓冲区,毕竟需要使用DMA,那么肯定是需要涉及到数据从内核态到应用态的“流动”。如果单纯的从内核态使用kmalloc/vmalloc申请一段内存,然后再通过copy_to_user()的方式传递到应用层;或者从应用态使用malloc的方式申请一段内存,然后再通过copy_from_user()的方式传递到内核层,无论是那种方式都需要拷贝,标题开宗明义的就说明是要进行系统性能优化,如果这么设计,兜了一圈还是少不了数据拷贝,那么使用DMA拷贝数据就没有意义了。很显然使用mmap方式使内核层内存与应用层内存直接映射起来,就可以避免内核态到应用态的拷贝了。
另外需要考虑的一个问题是,如何保证内存缓冲区的物理地址是连续的?因为需要使用DMA拷贝数据,因此需要保证物理地址是连续的(先姑且这么认为,后文再稍微展开讲讲物理地址不连续的情况)。实际上在内核态使用kmalloc申请的内存是可以保证物理地址连续的,但是kmalloc申请的内存不能太大,那么究竟能有多大呢?
以目前手头上的硬件板子为例,page block oder为14,即2^14*4K=64MB,鉴于系统刚运行起来后存在的最高阶内存为12,也就是说有16MB的大内存空间是连续的,那么在此环境下使用kmalloc申请16M的内存块也是没问题的。本篇案例先以申请2M内存作为源数据缓冲区,申请2M内存作为目的数据缓冲区为例进行代码实现。
应用层部分代码实现如下:
/* 2M */
#define MMAP_SRC_SIZE (2*1024*1024)
#define MMAP_DEST_SIZE MMAP_SRC_SIZE
void mmap_pdata_init(mmap_t *ptr)
{
ptr->mmap_fd = -1;
ptr->mmap_ptr_src = NULL;
ptr->mmap_ptr_dest = NULL;
ptr->src_size = MMAP_SRC_SIZE;
ptr->dest_size = MMAP_DEST_SIZE;
}
int mmap_mem(mmap_t *ptr)
{
ptr->mmap_ptr_src = mmap(NULL, ptr->src_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, ptr->mmap_fd, 0);
if (ptr->mmap_ptr_src == MAP_FAILED)
{
debug_printf("mmap size is %d bytes, mmap failed!(%s)\n", ptr->src_size, strerror(errno));
return -1;
}
else
{
debug_printf("mmap virtual src addr: 0x%x, size: %d\n", ptr->mmap_ptr_src, ptr->src_size);
}
ptr->mmap_ptr_dest = mmap(NULL, ptr->dest_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, ptr->mmap_fd, 0);
if (ptr->mmap_ptr_dest == MAP_FAILED)
{
debug_printf("mmap size is %d bytes, mmap failed!(%s)\n", ptr->dest_size, strerror(errno));
munmap(ptr->mmap_ptr_src, ptr->src_size);
return -1;
}
else
{
debug_printf("mmap virtual src addr: 0x%x, size: %d\n", ptr->mmap_ptr_dest, ptr->dest_size);
}
return 0;
}
驱动层部分代码实现如下:
void *mmap_kmem_alloc(int size)
{
s
版权归原作者 fwx_ybd 所有, 如有侵权,请联系我们删除。