系统性能优化——Linux系统上运用DMA实现memcpy

本文探讨了在Linux系统中如何利用DMA技术优化内存拷贝,以提高系统性能。通过DMA,数据可以直接在内存和硬件之间传输,减少了CPU负载。文章详细介绍了DMA的相关概念、Linux内核的DMA子系统框架、内存缓冲区设计以及DMA驱动和应用层代码的实现。测试结果显示,虽然DMA拷贝效率低于标准memcpy,但其异步特性在释放CPU资源方面表现出色,适用于需要高效利用CPU的任务场景。
摘要由CSDN通过智能技术生成

目录

1.背景

2.DMA相关概念介绍

3.Linux内核DMA子系统框架

 4.DMA内存缓冲区设计

5.DMA内存拷贝驱动实现

 6.DMA应用层代码实现

7.测试DMA内存拷贝效率和性能

7.1效率评测

7.2性能评测

8,总结


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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值