【I2C 技术系列 09】I2C 传输模式:轮询 / 中断 / DMA

📚 I2C 技术系列 · 持续更新中

本系列从 OD 开漏物理层一路打到 Linux i2c子系统,把"两根线"背后那套电气/协议/仲裁/恢复/驱动全拧成一根线。

👉 想系统追更,戳这里查看完整目录:

【I2C 技术系列 00】总目录


本篇定位 :【I2C 技术系列 08】I2C 控制器寄存器与 IP 架构讲了控制器寄存器,本篇讲软件怎么用这些寄存器完成一次传输。三种模式------轮询(PIO)、中断、DMA------各有取舍。本篇给三种模式的完整代码骨架和选择决策,你写裸机 I2C 驱动能直接套。
模式选择是 I2C 驱动的第一决策

你写 I2C 驱动,先定模式:轮询(简单慢)、中断(通用)、DMA(大块快)。选错要么 CPU 占满,要么代码复杂过度。这不是"哪个高级用哪个",是场景匹配。


目录

  • 一、三种模式总览
    • [1.1 三种模式的工作方式](#1.1 三种模式的工作方式)
    • [1.2 选择决策树](#1.2 选择决策树)
  • 二、轮询(PIO)模式
    • [2.1 原理](#2.1 原理)
    • [2.2 完整写传输(DesignWare 风格)](#2.2 完整写传输(DesignWare 风格))
    • [2.3 轮询的优缺点](#2.3 轮询的优缺点)
  • 三、中断驱动模式
    • [3.1 原理](#3.1 原理)
    • [3.2 中断驱动写传输(状态机式)](#3.2 中断驱动写传输(状态机式))
    • [3.3 中断驱动读传输](#3.3 中断驱动读传输)
    • [3.4 中断模式优缺点](#3.4 中断模式优缺点)
    • [3.5 RTOS 下中断 + 信号量](#3.5 RTOS 下中断 + 信号量)
  • [四、DMA 模式](#四、DMA 模式)
    • [4.1 原理](#4.1 原理)
    • [4.2 DMA 配置](#4.2 DMA 配置)
    • [4.3 DMA 的命令位难题](#4.3 DMA 的命令位难题)
    • [4.4 DMA 读](#4.4 DMA 读)
    • [4.5 DMA 模式优缺点](#4.5 DMA 模式优缺点)
    • [4.6 缓存一致性](#4.6 缓存一致性)
  • 五、三种模式取舍
    • [5.1 数据量维度](#5.1 数据量维度)
    • [5.2 实时性维度](#5.2 实时性维度)
    • [5.3 复杂度维度](#5.3 复杂度维度)
  • 六、混合模式
    • [6.1 轮询 + 中断](#6.1 轮询 + 中断)
    • [6.2 中断 + DMA](#6.2 中断 + DMA)
    • [6.3 Linux 的做法](#6.3 Linux 的做法)
  • [七、关键问题 Q&A](#七、关键问题 Q&A)
    • [Q1:字节少时 DMA 反而慢,为什么?](#Q1:字节少时 DMA 反而慢,为什么?)
    • [Q2:中断模式 ISR 里能阻塞吗?](#Q2:中断模式 ISR 里能阻塞吗?)
    • [Q3:DMA buffer 对齐有要求吗?](#Q3:DMA buffer 对齐有要求吗?)
    • Q4:轮询模式怎么加超时?
    • Q5:同一控制器多个从机,怎么切换?
    • [Q6:DMA 模式下中断还用吗?](#Q6:DMA 模式下中断还用吗?)
  • 八、实战案例
    • [案例 1:轮询模式无超时,死机](#案例 1:轮询模式无超时,死机)
    • [案例 2:中断模式 ISR 没清标志,反复进](#案例 2:中断模式 ISR 没清标志,反复进)
    • [案例 3:DMA 写 EEPROM,数据错位](#案例 3:DMA 写 EEPROM,数据错位)
    • [案例 4:DMA 读命令位丢失](#案例 4:DMA 读命令位丢失)
    • [案例 5:小数据用 DMA,反而比中断慢](#案例 5:小数据用 DMA,反而比中断慢)
  • 九、本篇小结与速查
  • 延伸阅读
  • 参考资料

一、三种模式总览

模式 CPU 占用 实时性 代码复杂度 适用
轮询(PIO) 高(全程占) 中 低 初始化/小数据/调试
中断 低(事件进) 高 中 通用,中小数据
DMA 极低(完成才进) 高 高 大块数据(EEPROM/图像)

1.1 三种模式的工作方式

复制代码
轮询:CPU 写 DAT → 死循环读 STAT 等 TX_EMPTY → 写下一字节 → ...
中断:CPU 写 DAT → 干别的 → 中断通知 TX_EMPTY → ISR 写下一字节 → ...
DMA:CPU 配 DMA+I2C → 干别的 → DMA 自动搬 → 完成中断

1.2 选择决策树

复制代码
数据量?
├─ < 4 字节 / 初始化配置 → 轮询(够用,简单)
├─ 4~64 字节 / 寄存器读写 → 中断(通用)
└─ > 64 字节 / EEPROM/图像 → DMA(省 CPU)

二、轮询(PIO)模式

2.1 原理

CPU 全程参与,死循环查状态位:

c 复制代码
// 写一字节
i2c_write_byte(uint8_t b) {
    I2C->DAT = b;              // 写数据寄存器
    while (!(I2C->STAT & TX_EMPTY));  // 死等发送完成
    I2C->STAT_CLR = TX_EMPTY;  // 清标志
}

// 读一字节
uint8_t i2c_read_byte(void) {
    I2C->CMD = READ_CMD;       // 发读请求
    while (!(I2C->STAT & RX_FULL));   // 死等接收满
    I2C->STAT_CLR = RX_FULL;
    return I2C->DAT;
}

2.2 完整写传输(DesignWare 风格)

c 复制代码
// 主机写 N 字节到从机 addr
int i2c_master_write(uint8_t addr, uint8_t *data, int len) {
    // 1. 等总线空闲
    while (I2C->STATUS & ACTIVITY);
    
    // 2. 设目标地址
    I2C->TAR = addr;
    
    // 3. 使能控制器
    I2C->ENABLE = 1;
    
    // 4. 逐字节写
    for (int i = 0; i < len; i++) {
        // 等 TX FIFO 不满
        while (!(I2C->STATUS & TFNF));  // TX FIFO Not Full
        // 写数据 + 命令(写,最后一字节带 STOP)
        uint32_t cmd = data[i] | (i == len-1 ? BIT(9) : 0); // bit9=STOP
        I2C->DATA_CMD = cmd;
    }
    
    // 5. 等 STOP 发出
    while (!(I2C->RAW_INTR & STOP_DET));
    I2C->CLR_STOP_DET;
    
    // 6. 检查 NACK
    if (I2C->TX_ABRT_SOURCE & ABRT_7B_ADDR_NOACK) {
        I2C->CLR_TX_ABRT;
        return -ENXIO;  // 无此设备
    }
    
    return len;
}

2.3 轮询的优缺点

优点:

  • 代码简单,无中断/DMA 依赖
  • 调试容易(顺序执行)
  • 适合初始化/小配置

缺点:

  • CPU 全程占用,不能干别的
  • 长传输(EEPROM 写 5ms)CPU 死等
  • RTOS 下阻塞任务,影响调度

!note 嵌入式视角:轮询是"调试模式",生产慎用

你调 I2C 第一步用轮询(简单,能确认时序对),调通后改中断/DMA。生产代码别用轮询(浪费 CPU),除非数据极小且不频繁(如开机读一次配置)。


三、中断驱动模式

3.1 原理

配置中断,事件发生时进 ISR,ISR 推进状态机:

复制代码
软件:启动传输 → 干别的
硬件:事件(TX_EMPTY/RX_FULL/STOP)→ 触发中断
ISR:查事件 → 写下一字节/读数据/完成通知

3.2 中断驱动写传输(状态机式)

c 复制代码
typedef enum { ST_IDLE, ST_WRITE, ST_READ, ST_WAIT_STOP } i2c_state_t;

typedef struct {
    uint8_t addr;
    uint8_t *buf;
    int len;
    int idx;
    i2c_state_t state;
    void (*complete)(int status);
} i2c_xfer_t;

static i2c_xfer_t xfer;

// 启动写
int i2c_write_async(uint8_t addr, uint8_t *data, int len, void (*cb)(int)) {
    xfer.addr = addr;
    xfer.buf = data;
    xfer.len = len;
    xfer.idx = 0;
    xfer.state = ST_WRITE;
    xfer.complete = cb;
    
    I2C->TAR = addr;
    I2C->ENABLE = 1;
    I2C->INTR_MASK = TX_EMPTY | STOP_DET | TX_ABRT;  // 使能中断
    // 第一字节触发(也可等 TX_EMPTY 中断)
    I2C->DATA_CMD = data[0] | (len==1 ? BIT(9) : 0);
    xfer.idx = 1;
    return 0;
}

// ISR
void i2c_isr(void) {
    uint32_t stat = I2C->INTR_STAT;
    
    if (stat & TX_ABRT) {
        I2C->CLR_TX_ABRT;
        xfer.state = ST_IDLE;
        xfer.complete(-EIO);
        return;
    }
    if (stat & TX_EMPTY) {
        if (xfer.idx < xfer.len) {
            uint32_t cmd = xfer.buf[xfer.idx] | (xfer.idx==xfer.len-1 ? BIT(9):0);
            I2C->DATA_CMD = cmd;
            xfer.idx++;
        } else {
            I2C->INTR_MASK &= ~TX_EMPTY;  // 关 TX_EMPTY
        }
    }
    if (stat & STOP_DET) {
        I2C->CLR_STOP_DET;
        xfer.state = ST_IDLE;
        xfer.complete(xfer.len);
    }
}

3.3 中断驱动读传输

c 复制代码
int i2c_read_async(uint8_t addr, uint8_t *buf, int len, void (*cb)(int)) {
    xfer.addr = addr;
    xfer.buf = buf;
    xfer.len = len;
    xfer.idx = 0;
    xfer.state = ST_READ;
    xfer.complete = cb;
    
    I2C->TAR = addr;
    I2C->ENABLE = 1;
    I2C->INTR_MASK = RX_FULL | STOP_DET | TX_ABRT;
    
    // 发 N 个读请求(bit8=1 表示读)
    for (int i = 0; i < len; i++) {
        while (!(I2C->STATUS & TFNF));
        uint32_t cmd = BIT(8) | (i==len-1 ? BIT(9):0);  // 读 + 末字节 STOP
        I2C->DATA_CMD = cmd;
    }
    return 0;
}

// ISR 中 RX_FULL 处理
if (stat & RX_FULL) {
    xfer.buf[xfer.idx++] = I2C->DATA_CMD & 0xFF;
    if (xfer.idx >= xfer.len) {
        I2C->INTR_MASK &= ~RX_FULL;
    }
}

3.4 中断模式优缺点

优点:

  • CPU 不死等,事件进 ISR
  • 实时性好(事件即响应)
  • 通用性强

缺点:

  • 每字节/每事件中断,大数据量中断频繁
  • ISR 逻辑要小心(状态机)
  • 高速(1M)大数据下中断开销仍大

3.5 RTOS 下中断 + 信号量

c 复制代码
// FreeRTOS 风格
SemaphoreHandle_t i2c_done_sem;

int i2c_write_rtos(uint8_t addr, uint8_t *data, int len) {
    xfer.complete = isr_give_sem;  // ISR 给信号量
    i2c_write_async(addr, data, len, ...);
    xSemaphoreTake(i2c_done_sem, portMAX_DELAY);  // 任务阻塞等
    return xfer.ret;
}

void isr_give_sem(int status) {
    xfer.ret = status;
    BaseType_t hpw = pdFALSE;
    xSemaphoreGiveFromISR(i2c_done_sem, &hpw);
    portYIELD_FROM_ISR(hpw);
}
  • 任务层阻塞等信号量,ISR 完成后给
  • 不死循环,RTOS 调度其他任务

四、DMA 模式

4.1 原理

DMA 控制器接管数据搬运,I2C FIFO 与内存之间自动传输:

复制代码
内存 buffer ←→ DMA ←→ I2C FIFO ←→ 移位寄存器 ←→ SDA
        (CPU 不参与)

4.2 DMA 配置

c 复制代码
// 写:内存 → I2C TX FIFO
int i2c_write_dma(uint8_t addr, uint8_t *data, int len, void (*cb)(int)) {
    xfer.complete = cb;
    
    // 配 I2C
    I2C->TAR = addr;
    I2C->ENABLE = 1;
    I2C->DMA_CR = DMA_TX_ENABLE | TX_FIFO_THRESH_HALF;
    
    // 配 DMA(内存→I2C TX FIFO)
    DMA->SRC = (uint32_t)data;
    DMA->DST = (uint32_t)&I2C->DATA_CMD;
    DMA->LEN = len;
    
    // 问题:DATA_CMD 要带命令位(bit8=0 写,bit9=STOP)
    // 多数 DMA 只搬数据,命令位要预生成
    // 方案:预处理 buffer,每字节拼命令位
    
    // 启动 DMA
    DMA->CTRL = DMA_ENABLE | SRC_INC | DST_NO_INC;
    
    // 使能 I2C DMA 中断
    I2C->INTR_MASK = STOP_DET | TX_ABRT;
    return 0;
}

4.3 DMA 的命令位难题

I2C 的 DATA_CMD 寄存器是 [cmd(8:9)] [data(7:0)],DMA 搬 8 位数据时命令位丢失。解决:

方案 1:32 位 DMA,预处理命令字

c 复制代码
// 预处理:把 buffer 扩成 32 位,拼命令位
uint32_t cmd_buf[len];
for (int i = 0; i < len; i++) {
    cmd_buf[i] = data[i] | (i == len-1 ? BIT(9) : 0);  // 末字节 STOP
}
// DMA 搬 32 位
DMA->WIDTH = 32;
DMA->SRC = cmd_buf;

方案 2:控制器支持"DMA 模式自动加命令位"

  • 部分 IP(DW)支持 DMA 模式下自动认为是写,末字节自动 STOP
  • 配 IC_DMA_CR 寄存器

方案 3:不用 DMA,中断足够

  • 数据量不大时(< 64 字节),DMA 配置开销 > 收益

4.4 DMA 读

c 复制代码
int i2c_read_dma(uint8_t addr, uint8_t *buf, int len) {
    // 先发 N 个读请求(命令位 bit8=1)
    // 这步用 PIO 或中断发(命令位要拼)
    for (int i = 0; i < len; i++) {
        while (!(I2C->STATUS & TFNF));
        I2C->DATA_CMD = BIT(8) | (i==len-1 ? BIT(9):0);
    }
    
    // 配 DMA:I2C RX FIFO → 内存
    I2C->DMA_CR = DMA_RX_ENABLE | RX_FIFO_THRESH_HALF;
    DMA->SRC = &I2C->DATA_CMD;
    DMA->DST = buf;
    DMA->LEN = len;
    DMA->CTRL = DMA_ENABLE | DST_INC | SRC_NO_INC;
    
    I2C->INTR_MASK = STOP_DET | TX_ABRT;
    return 0;
}

4.5 DMA 模式优缺点

优点:

  • CPU 几乎不参与(完成才中断)
  • 大块传输高效
  • 适合 EEPROM 整页读写、图像缓冲

缺点:

  • 配置复杂(命令位、FIFO 阈值、缓存一致性)
  • 小数据反而慢(配置开销)
  • 缓存一致性问题(DMA buffer 要 cache flush/invalidate)

4.6 缓存一致性

c 复制代码
// DMA 写前:flush cache(数据从 cache 写回内存)
flush_dcache_range((uint32_t)buf, len);

// DMA 读后:invalidate cache(丢弃 cache,从内存重读)
invalidate_dcache_range((uint32_t)buf, len);
  • 不 flush:DMA 读到旧内存数据(数据未写回)
  • 不 invalidate:CPU 读到旧 cache(DMA 新数据在内存)
  • Cortex-M7/RISC-V with cache 必须处理

五、三种模式取舍

5.1 数据量维度

数据量 推荐模式 理由
1~4 字节 轮询 配置开销最小
4~64 字节 中断 实时 + 代码清晰
64~256 字节 中断/DMA 临界,DMA 略优
> 256 字节 DMA 省 CPU 明显

5.2 实时性维度

场景 推荐
实时控制(不能阻塞) 中断/DMA
初始化(顺序执行) 轮询
RTOS 多任务 中断 + 信号量

5.3 复杂度维度

模式 代码量 调试难度
轮询 少 易
中断 中 中(状态机)
DMA 多 难(缓存/命令位)

六、混合模式

6.1 轮询 + 中断

  • 初始化用轮询(简单)
  • 正式传输用中断(高效)
  • 多数 RTOS 驱动这么做

6.2 中断 + DMA

  • 小数据用中断
  • 大数据用 DMA
  • 驱动按 len 动态选
c 复制代码
int i2c_xfer(uint8_t addr, uint8_t *buf, int len, int is_write) {
    if (len <= 8) {
        return i2c_xfer_irq(addr, buf, len, is_write);   // 小数据中断
    } else {
        return i2c_xfer_dma(addr, buf, len, is_write);   // 大数据 DMA
    }
}

6.3 Linux 的做法

Linux i2c 框架:控制器驱动实现 master_xfer,框架按 msg 长度/类型调。底层用中断或 DMA,驱动透明。详见 \[11-I2C Linux驱动框架]。


七、关键问题 Q&A

Q1:字节少时 DMA 反而慢,为什么?

A :DMA 启动有开销:配源/目的/长度/命令位预处理/cache flush,启动后还要等 FIFO 阈值触发。这几 μs 的开销,大数据能摊薄,小数据(几字节)反而比中断慢(中断几条指令就发完)。DMA 阈值常设 64 或 128 字节。

Q2:中断模式 ISR 里能阻塞吗?

A :不能。ISR 要快进快出,不能等(不能 sleep/不能忙等)。数据搬运在 ISR 里做(快),耗时操作(回调处理/协议解析)推迟到任务/下半部。RTOS 下 ISR 只给信号量,任务层处理。

Q3:DMA buffer 对齐有要求吗?

A :多数 DMA 要求源/目的地址对齐(4 字节或 burst 大小)。buffer 要 __attribute__((aligned(4)))。cache line 对齐(32/64 字节)更优(cache 操作整行)。

Q4:轮询模式怎么加超时?

A:

c 复制代码
uint32_t timeout = 1000000;  // 超时计数
while (!(I2C->STAT & TX_EMPTY) && --timeout);
if (!timeout) return -ETIMEDOUT;

必须加超时,否则器件异常(拉伸/死机)CPU 死循环。功能安全必查。

Q5:同一控制器多个从机,怎么切换?

A :每次传输前重设 IC_TAR(目标地址)。DesignWare:发 STOP 后等总线空闲,写新 TAR,启动新传输。别在一次传输中途换 TAR(协议不允许)。

Q6:DMA 模式下中断还用吗?

A :用。DMA 完成或 I2C STOP/ABRT 仍要中断通知 CPU。DMA 替代的是"数据搬运中断"(TX_EMPTY/RX_FULL),不是"完成中断"(STOP_DET)。


八、实战案例

案例 1:轮询模式无超时,死机

现象:裸机 I2C 轮询读传感器,传感器掉电后 CPU 死循环。

分析 :while(!RX_FULL) 无超时,传感器不响应,CPU 永远等。

解决:加超时计数,超时报错。功能安全必加。

案例 2:中断模式 ISR 没清标志,反复进

现象:I2C 中断 ISR 进不去(或反复进),系统卡。

分析:ISR 没清中断标志(读 IC_CLR_*),标志一直有效,中断反复触发。

解决:ISR 末尾清标志(DesignWare 读 IC_CLR_* 自动清)。

案例 3:DMA 写 EEPROM,数据错位

现象:DMA 写 24C04 16 字节,读回发现首字节错。

分析:DMA buffer 没 flush cache,Cortex-M7 cache 里数据没写回内存,DMA 读到旧数据。

解决 :DMA 启动前 flush_dcache_range。

案例 4:DMA 读命令位丢失

现象:DMA 读 BMP280,只读到 0。

分析:DMA 模式下没先发读请求命令位,DMA 搬的是数据但 I2C 没发读请求。

解决:DMA 启动前先 PIO 发 N 个读请求命令(bit8=1),再启动 DMA 收数据。

案例 5:小数据用 DMA,反而比中断慢

现象:把所有 I2C 都改 DMA,4 字节寄存器读写变慢。

分析:DMA 配置开销 ~10μs,4 字节中断只要 90μs(400k × 9 × 4)的 1/10,但 DMA 配置占了大头。

解决:按长度动态选(< 8 字节用中断,> 64 用 DMA)。


九、本篇小结与速查

核心要点

  • 三种模式:轮询(PIO,简单)/中断(通用)/DMA(大块)
  • 轮询:CPU 全程占,适合小数据/调试,必加超时
  • 中断:事件进 ISR,ISR 推状态机,RTOS 配信号量,通用
  • DMA:FIFO↔内存自动搬,省 CPU,要处理命令位 + cache 一致性
  • 选型:小数据轮询/中断,大数据 DMA;按 len 动态选最优
  • DMA 命令位难题:32 位预处理 buffer 或 IP 自动加命令位
  • cache 一致性:DMA 写前 flush,读后 invalidate
  • ISR 不阻塞,耗时推迟到任务

选型决策表

场景 模式 理由
开机读配置(1次) 轮询 简单,够用
寄存器读写(<8B) 中断 通用,实时
EEPROM 页写(64B) 中断/DMA 临界
EEPROM 整片读(4KB) DMA 省 CPU
图像缓冲(几KB) DMA 必须
实时控制(不能阻塞) 中断+信号量 RTOS 友好
调试阶段 轮询 易定位

速查表

想干啥 做啥
写一字节(调试) DAT = b; while(!TX_EMPTY)
中断 ISR 查 INTR_STAT → 分支处理 → 清标志
DMA 写 flush cache → 配 DMA → 启动 → 等 STOP
DMA 读 先发读请求 → invalidate cache → 配 DMA → 启动
超时 while(cond && --timeout)
RTOS 等待 ISR 给信号量,任务 Take
切换从机 STOP → 空闲 → 写新 TAR
DMA 阈值 len > 64 用 DMA

延伸阅读

  • \[08-I2C控制器寄存器与IP架构] ------ 寄存器细节
  • \[10-I2C错误处理与总线恢复] ------ 中断里的错误处理
  • \[11-I2C Linux驱动框架] ------ Linux 下 master_xfer 实现
  • \[12-I2C设备驱动实战] ------ 实战代码
  • \[FreeRTOS_设计思想与功能模块讲解] ------ RTOS 信号量/中断

参考资料

  • Synopsys DesignWare I2C Databook(DMA 章节)
  • STM32 HAL I2C(中断/DMA 实现)
  • Linux drivers/i2c/busses/i2c-designware.c
  • FreeRTOS API Reference(Semaphore)

\[00-I2C技术系列总目录] · 上一篇:\[08-I2C控制器寄存器与IP架构] · 下一篇:\[10-I2C错误处理与总线恢复]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
Zw-awa2 小时前
让机器回答“我在哪”:激光雷达与 SLAM
stm32
启友玩AI2 小时前
微安级唤醒、毫安级识别:基于 CI3422 的 AI 眼镜语音交互开发实战
c语言·人工智能·科技·嵌入式硬件·ai·ar·语音识别
wuyk5553 小时前
样章-第10讲-看门狗异常恢复与卡机排查方法论
stm32·单片机·嵌入式硬件·重构
开发笔记-阿牛3 小时前
AM收音机蓝牙二合一设计复盘
人工智能·单片机·嵌入式硬件·音视频·音频
sunoo-2293 小时前
嵌入式Linux驱动学习笔记(第3-6天):从misc封装、platform总线到设备树、中断与单总线外设实战
linux·arm开发·驱动开发·笔记·单片机·学习
打工仔折腾 AI3 小时前
鸿蒙平板远控电脑实测:ToDesk蓝牙键鼠、双模式鼠标与跨端剪贴板落地记录
人工智能·后端·python·性能优化·计算机外设·电脑·harmonyos
打工仔折腾 AI3 小时前
鸿蒙端ToDesk远控电脑实测:蓝牙键鼠、双模式鼠标与跨端剪贴板实践
后端·python·性能优化·计算机外设·电脑·harmonyos·ai agent 实战
半杯咖啡半行码16 小时前
STM32之ARM通用汇编指令详解:从指令格式到寻址方式
汇编·arm开发·stm32
gwf21617 小时前
RoCEv2网络性能调优:DCQCN、ECN与PFC协同驱动级深度解析
驱动开发·rdma·rocev2·linuxkernel·pfc·dcqcn·ecn