dma相关
ddatsh
直接内存访问(Direct Memory Access),内存访问技术,一种快速的数据传送方式,外设可独立直接读写系统内存,而不需CPU介入处理(取指令/取数/送数等;大量中断保护、恢复 CPU 现场,寄存器-内存复制)
实现 Zero Copy 目标,IO 设备跟用户程序空间传输数据的过程中,减少数据拷贝次数,减少系统调用
为什么 DMA 传输速度快
外设与计算机内存之间的信息交换,可通过程序查询方式和中断方式进行
这两种方式都是在 CPU 的控制下,通过 CPU 执行指令完成
数据传送方向为外设→CPU→内存。这两种方式每传送一个字节都需要耗用较长时间
程序查询方式时,CPU 要反复测试外设状态,外设未准备好时,CPU 处于等待状态,直到外设准备好,才进行数据传送
中断方式下,每实现一次数据传送,CPU 都要进行转入中断服务子程序、保护断点、保护现场、恢复现场、返回主程序等操作
DMA 传送期间,CPU 要让出对系统总线的控制权,交给 DMA 控制。总线在 DMA 的控制下,数据直接在存储器和外设之间传送,而不经过 CPU 干预,其传送速度大大提高,可接近于存储器的最快存取速度
工作流程
通常 CPU 总线由 CPU 管理,但在 DMA 工作时,CPU 总线由 DMA 控制器接管,控制传送的字节数,判断 DMA 是否结束以及发出 DMA 结束信号,故 DMA 控制器的工作流程如下:
- 当外设有 DMA 需求并准备就绪时,就向 DMA 控制器发送 DMA 请求
- DMA 控制器接收请求后,向 CPU 发送总线接管请求
- CPU 接到总线接管请求后,则会在当前总线周期结束后进行中断处理并返回响应信号,通知 DMA 控制器其已经放弃了对总线的控制权
- DMA 控制器获得总线的控制权,并对外设发送应答信号,通知外设可以进行 DMA 传输
- DMA 控制器向存储器发送地址信号和向存储器及外设发出读/写控制信号,控制数据按初始化设定的方向传送,实现外设与内存的数据传输
- 数据全部传输结束后,DMA 控制器向 CPU 发送结束信号,要求撤销总线请求信号,CPU 接收到信号后,收回对总线的控制权
scatter-gather DMA
传统 block DMA 一次只能传输物理上连续的一个块的数据, 完成传输后发起中断
scatter-gather DMA 允许一次传输多个物理上不连续的块,完成传输后只发起一次中断
使用一个链表描述物理上不连续的存储空间,然后把链表首地址告诉 DMA master。DMA master 在传输完一块物理连续的数据后,会直接根据链表继续传输下一块物理上连续的数据,直到传输完毕后才发起中断
scatter-gather DMA 方式比 block DMA 方式效率高,但需要硬件软件都实现。
scatter-gather DMA 的应用
dpdk 分片包采用链式管理,同一个数据包的数据,分散存储在不连续的块中(mbuf 结构)。要求 DMA 一次操作,需要从不连续的多个块中搬移数据。e1000 驱动发包部分代码:
uint16_t
eth_em_xmit_pkts(void *tx_queue, struct rte_mbuf **tx_pkts,
uint16_t nb_pkts)
{
//e1000驱动部分代码
...
m_seg = tx_pkt;
do {
txd = &txr[tx_id];
txn = &sw_ring[txe->next_id];
if (txe->mbuf != NULL)
rte_pktmbuf_free_seg(txe->mbuf);
txe->mbuf = m_seg;
/*
* Set up Transmit Data Descriptor.
*/
slen = m_seg->data_len;
buf_dma_addr = rte_mbuf_data_iova(m_seg);
txd->buffer_addr = rte_cpu_to_le_64(buf_dma_addr);
txd->lower.data = rte_cpu_to_le_32(cmd_type_len | slen);
txd->upper.data = rte_cpu_to_le_32(popts_spec);
txe->last_id = tx_last;
tx_id = txe->next_id;
txe = txn;
m_seg = m_seg->next;
} while (m_seg != NULL);
/*
* The last packet data descriptor needs End Of Packet (EOP)
*/
cmd_type_len |= E1000_TXD_CMD_EOP;
txq->nb_tx_used = (uint16_t)(txq->nb_tx_used + nb_used);
txq->nb_tx_free = (uint16_t)(txq->nb_tx_free - nb_used);
...
}
Linux 应用层 Vectored_I/O 写法(需驱动实现)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/uio.h>
int main(int argc, char *argv[])
{
const char buf1[] = "Hello, ";
const char buf2[] = "Wikipedia ";
const char buf3[] = "Community!\n";
struct iovec bufs[] = {
{ .iov_base = (void *)buf1, .iov_len = strlen(buf1) },
{ .iov_base = (void *)buf2, .iov_len = strlen(buf2) },
{ .iov_base = (void *)buf3, .iov_len = strlen(buf3) },
};
if (writev(STDOUT_FILENO, bufs, sizeof(bufs) / sizeof(bufs[0])) == -1)
{
perror("writev()");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}