描述符链表组织:单向链表与双向链表,循环链表在 SG-DMA 中的应用
描述符链表怎么组织,这事我得好好聊聊。
你想想看,SG-DMA 的核心就是靠一串描述符来搬运数据。这些描述符怎么串起来,直接决定了你系统的灵活性和可靠性。我个人习惯把链表分成三种来理解:单向、双向和循环。别看它们只是指针方向不同,用起来差别可大了。
单向链表:最简单,也最脆弱
单向链表的结构很直白。每个描述符里只有一个 Next Pointer,指向下一个描述符。DMA 引擎从头走到尾,一条路走到黑。
典型结构:
struct sg_desc {
uint32_t src_addr;
uint32_t dst_addr;
uint32_t length;
uint32_t cfg;
uint32_t next_ptr; // 指向下一个描述符
};
它的优点很明显:每个描述符省掉一个指针域,内存占用小。我在一个低成本的 IoT 项目里用过,当时内存只有 8KB,能省一点是一点。
但单向链表的缺点也很要命。我曾经遇到过一个情况,某个描述符的 next_ptr 因为总线干扰被写坏了,DMA 直接跳到一个非法地址,整个系统挂了。单向链表一旦断裂,后面的描述符全部丢失,连排查都很困难。
单向链表的致命伤: 没有反向路径。一旦一个节点损坏或丢失,后续所有传输都无法恢复。
双向链表:可进可退,代价翻倍
双向链表在每个描述符里加了 Prev Pointer。DMA 既能往前走,也能往后走。这听起来有点多余,但实际中有大用处。
我个人的建议: 如果你的系统对可靠性有要求,或者需要动态更新描述符,优先考虑双向链表。
struct sg_desc_double {
uint32_t src_addr;
uint32_t dst_addr;
uint32_t length;
uint32_t cfg;
uint32_t next_ptr;
uint32_t prev_ptr; // 多了这个
};
为什么说它强?我举个真实例子。在一个视频采集项目中,我们需要实时更新描述符链表,把新的内存缓冲区加进来。用双向链表,DMA 完成一个描述符后,我可以从当前节点往前找到上一个节点,安全地把新节点插在中间。整个过程不影响正在进行的传输。
代价呢?每个描述符多 4 字节(32 位系统)甚至 8 字节(64 位系统)。如果你有 1000 个描述符,多占 4KB 到 8KB 内存。值不值?看你系统的可靠性需求。
循环链表:无头无尾,永不停止
循环链表其实是单向链表的变种,但最后一个描述符的 Next Pointer 指向第一个描述符。这样链表就形成了一个环。
循环链表的核心机制:
- DMA 完成最后一个描述符后,自动跳回第一个描述符
- 适用于连续不间断的数据流,比如音频、视频采集
- 配合
Wrap或Interrupt on Completion标志位使用
我记得做一个音频环路测试时,用循环链表配合乒乓缓冲,DMA 在两个缓冲区之间来回切换,CPU 完全不用干预。那感觉,舒服。
用循环链表要小心:
- 必须保证链表里的描述符是有效的,否则会无限循环在无效描述符上
- 每次传输完成后,记得更新描述符的数据缓冲区地址
- 我曾经在更新地址时忘了同步,DMA 把旧数据又传了一遍,找 bug 找了大半天
三种链表的对比
我把它们的核心差异整理成了一张表,方便你对比选择。
| 特性 | 单向链表 | 双向链表 | 循环链表 |
|---|---|---|---|
| 内存占用 | 低(每节点 1 指针) | 高(每节点 2 指针) | 低(每节点 1 指针) |
| 插入/删除效率 | 低(需遍历前驱) | 高(直接获取前驱) | 低(需遍历前驱) |
| 错误恢复能力 | 弱(断裂即丢失) | 强(可反向追踪) | 弱(需额外保护) |
| 应用场景 | 简单、一次性批量传输 | 动态更新、高可靠性系统 | 连续数据流、乒乓缓冲 |
如何选择?给你几个场景
- 场景一:启动时一次性传输一批数据,完了就结束。 选单向链表。省内存,配置简单。
- 场景二:系统需要动态添加或移除缓冲区。 选双向链表。灵活,安全。我之前做一个网络数据包处理模块,就是双向链表,运行时可以动态添加 DMA 描述符。
- 场景三:音频、视频等连续数据流。 选循环链表。配上两个或三个描述符,形成环形缓冲,DMA 自己跑就行了。
来看一张结构对比图,直观感受一下三种链表的差异。
关于链表初始化的一个技巧:
无论你选哪种链表,建议在初始化时就把所有描述符的指针域填好,不用的节点指向一个固定的无效地址(比如 0xFFFFFFFF)。这样万一 DMA 跑飞了,也能快速定位问题。我常用的做法是:先用 memset 把所有描述符清零,再逐一正确配置。
单向、双向、循环,三种链表各有取舍。没有的好坏,只有合不合适的场景。你想想看,你的系统是需要省内存,还是需要高可靠性,或者是需要连续数据流?想清楚了,选起来就不难。