Linux内存管理:DMA

说起DMA我们并不陌生,但是实际编程中去用的人不多吧,最多就是网卡驱动里的环形buffer,再有就是设备的dma,下面我们就分析分析.
DMA用来在设备内存和内存之间直接数据交互。而无需cpu干预

内核为了方便驱动的开发,已经提供了几个dma 函数接口。

dma跟硬件架构相关,所以linux关于硬件部分已经给屏蔽了,有兴趣的可以深入跟踪学习.

按照linux内核对dma层的架构设计,各平台dma缓冲区映射之间的差异由内核定义的一个dma操作集

include/linux/dma-mapping.h:

来统一屏蔽实现的差异.
不同差异主要来来自cache的问题
Cache与dma同步问题,这里不深入讨论.

另外一个常用的函数是Dma_set_mask, 为了通知内核设备能够寻址的范围,很多时候设备能够寻址的范围有限。

Dma映射可以分为三类:

1. 一致性dma映射 dma_alloc_coherent (问题:驱动使用的buffer不是自身申请的,而是其他模块)
当驱动模块主动分配一个Dma缓冲区并且dma生存期和模块一样时

参数说明:

(1)这个函数的返回值是缓冲的一个内核虚拟地址, 它可被驱动使用
(2)第三个参数dma_handle:
其间相关的物理地址在 dma_handle 中返回

2. 流式dma映射 dma_map_single
通常用于把内核一段buffer映射,返回物理地址.
如果驱动模块需要使用从别的模块传进来的虚拟地址空间作为dma缓冲区,保证地址的线性 cache一致性
一致性api接口:sync_single_for_cpu

3.分散/聚集映射(scatter/gather map) Dma_map_sgs

有时候我们还需要
1. 回弹缓冲区 bounce buffer:当cpu侧物理地址不适合设备的dma操作的时候

2.
DmA内存池:一般dma映射都是单个page的整数倍,如果驱动程序需要更小的一致性映射的dma缓冲区,可以使用。类似于slab机制,
Dma_pool_create

下面我们就那网卡驱动的例子说说dma的具体应用,参考linux kernel e1000网卡
drivers/net/ethernet/intel/e1000/*
Ring buffer

Dma不能为高端内存,一般为32,默认低端内存,由于设备能够访问的地址范围有限。
设备使用物理地址,而代码使用虚拟地址。

就看看如何发送数据包:e1000_main.c:

e1000_xmit_frame: 关于帧的发送流程这里不多说.

经过上次,邻居子系统后,数据帧已经到达驱动,数据放在skb指定的内存里.
看代码
tx_ring = adapter->tx_ring; // 获取发送的ring buffer

接着我们看关键代码:
count = e1000_tx_map(adapter, tx_ring, skb, first, max_per_txd, nr_frags, mss);

它做了什么呢?

默认数据报文没有分片或者碎片什么的。
那么进入第一个while(len)

获取buffer_info = &tx_ring->buffer_info[i];
然后:调用dma_map_single进行流式映射. 即把skb->data(虚拟地址) 和buffer_info->dma(物理地址)对应起来.操作两个地址等于操作同一片区域。

回到主发送函数:

调用e1000_tx_queue把数据发送出去:

我们看到它把刚才dma_map_singe里的映射赋值了:
tx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
说明发送的时候是根据发送描述符来发送的。

然后操作寄存器:
writel(i, hw->hw_addr + tx_ring->tdt);
那么网卡就会自动读取tx desc 然后把数据发送出去。

总结下流程:
1. linux os会调用网卡的start_xmit()函数。在e1000里,对应的函数是 e1000_xmit_frame,
2. e1000_xmit_frame又会调用e1000_tx_queue(adapter, tx_ring, tx_flags, count)。
这里的tx_queue指的是发送Descriptor的queue。
3. e1000_tx_queue 在检查了一些参数后,最终调用 writel(i, hw->hw_addr + tx_ring->tdt)。
这里的tx_ring->tdt中的tdt全写为 tx_descriptor_tail。从网卡的开发手册中可以查到,如果写了descriptor tail,那么网卡就会自动读取 descriptor,然后把包发送出去。

descroptor的主要内容是addr pointer和length。前者是要发送的包的起始物理地址。后者是包的长度。有了这些,硬件就可以通过dma来读取包并发出去了。其他网卡也基本会用descriptor的结构。

虽然流程明白了,但是还有几个点,
1. tx_ring在哪初始化?
2. 网卡到底是如何操作映射的dma地址的,把数据发送出去的?

tx ring 在e1000_open 的时候:
调用:

我们看:它建立了一致性dma映射.

desc是结构指针:它的结构跟网卡寄存器结构有关,e1000_hw.h

我们稍微屡一下,

那么网卡又是如何和dma地址关联的呢?

很明显它把dma地址写入了网卡dma寄存器。所以dma还需要网卡硬件的支持才行.

当然e1000这个网卡驱动还是相当的复杂,不过它把一致性映射和流式映射都用上了。

1 5 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部