4. 中断延迟优化:临界区保护策略与中断底半部机制
在蓝牙组网驱动中,中断延迟直接决定数据收发的实时性与吞吐量上限。过长的中断关闭时间会导致射频帧丢失、链路层超时重传,甚至触发看门狗复位。本章聚焦三种核心优化手段:临界区保护策略(关中断 vs 挂起调度器)、ISR 最小化原则以及中断底半部机制(Tasklet / Workqueue)。
核心指标:蓝牙 BR/EDR 的 ACL 数据包间隔为 625 µs(1 slot),BLE 连接事件间隔可低至 7.5 ms。中断关闭时间必须控制在 10–50 µs 以内,否则将直接破坏链路时序。
4.1 临界区保护策略:关中断 vs 挂起调度器
临界区(Critical Section)是指访问共享资源(如 HCI 命令队列、连接句柄表、链路层状态机)的代码段。保护临界区有两种主流策略,其性能差异在蓝牙驱动中尤为显著。
| 策略 | 原理 | 中断延迟影响 | 适用场景 |
|---|---|---|---|
| 关中断(local_irq_save/restore) | 禁止本地 CPU 响应所有中断 | 直接增加中断延迟,关闭时间 = 临界区执行时间 | 临界区极短(< 5 µs)、操作寄存器或 FIFO |
| 挂起调度器(preempt_disable/enable) | 禁止内核抢占,但中断仍可响应 | 中断延迟仅增加调度延迟,不阻塞中断响应 | 临界区较长(5–50 µs)、涉及链表遍历或内存分配 |
避坑指南:在蓝牙 ISR 中调用
spin_lock_irqsave 时,如果临界区包含 hci_send_frame 或 l2cap_chan_del 等可能触发调度器的函数,必须使用 spin_lock_bh 而非 spin_lock_irqsave,否则会导致递归锁死。
以下 SVG 展示了两种策略在蓝牙 HCI 事件处理中的时序对比:
4.2 中断服务程序(ISR)最小化原则
蓝牙驱动的 ISR 必须遵循“快进快出”原则。ISR 中只做最紧急、最轻量的操作,其余工作推迟到底半部执行。以下是 ISR 最小化的具体规则:
- 规则 1:只读寄存器,不写慢速外设。例如,读取 HCI UART RX FIFO 的字节数,但不解析 HCI 帧头。
- 规则 2:不调用可能睡眠的函数。如
kmalloc(GFP_KERNEL)、mutex_lock、hci_send_frame。 - 规则 3:使用原子操作或自旋锁保护共享标志。例如,
atomic_set(&hdev->rx_pending, 1)通知底半部有新数据。 - 规则 4:ISR 执行时间 < 蓝牙连接间隔的 1/10。对于 BLE 7.5 ms 连接事件,ISR 应 < 750 µs;对于 BR/EDR 625 µs slot,ISR 应 < 60 µs。
实践案例:某 BLE 5.0 驱动中,ISR 仅执行 3 步:① 读取 RX FIFO 深度寄存器;② 将数据搬运到预分配的 DMA buffer;③ 设置
tasklet_schedule。整个 ISR 耗时约 8 µs,远低于 BLE 连接事件间隔。
以下 SVG 展示了 ISR 最小化原则下的数据流架构:
4.3 中断底半部机制:Tasklet vs Workqueue
底半部(Bottom Half)是 Linux 内核将非紧急中断处理推迟到安全上下文中执行的机制。蓝牙驱动中常用的两种底半部是 Tasklet 和 Workqueue。它们的差异直接影响数据收发的吞吐量和延迟。
| 特性 | Tasklet | Workqueue |
|---|---|---|
| 执行上下文 | 软中断上下文(atomic) | 进程上下文(可睡眠) |
| 可睡眠 | 否(不能调用 msleep、mutex_lock) |
是(可执行阻塞 I/O、内存分配) |
| 调度延迟 | 低(软中断返回后立即执行) | 较高(依赖内核工作线程调度) |
| 并发模型 | 同一 Tasklet 在单 CPU 上串行执行 | 可绑定到指定 CPU 或 worker pool |
| 蓝牙典型用途 | HCI 事件解析、ACL 数据帧重组 | HCI 命令发送、连接管理、固件下载 |
选型建议:对于 BLE 数据通道的 RX/TX 处理,优先使用 Tasklet 以获得最低延迟;对于 HCI 命令响应、连接参数更新等可能阻塞的操作,使用 Workqueue 避免软中断上下文阻塞。
以下 SVG 展示了 Tasklet 与 Workqueue 在蓝牙驱动中的调度流程对比:
4.4 综合优化策略与代码示例
在实际蓝牙驱动中,中断延迟优化需要组合使用上述三种技术。以下是一个典型的蓝牙 UART 驱动中断处理代码框架:
/* 蓝牙 UART 驱动 ISR - 最小化实现 */
static irqreturn_t bt_uart_irq_handler(int irq, void *dev_id)
{
struct bt_uart_dev *bdev = dev_id;
unsigned int rx_count;
/* 规则1:只读寄存器,不解析数据 */
rx_count = ioread32(bdev->regs + UART_RX_CNT);
if (!rx_count)
return IRQ_NONE;
/* 规则2:使用 DMA 或快速 memcpy 搬运数据 */
dma_sync_single_for_cpu(bdev->dev, bdev->dma_handle, rx_count, DMA_FROM_DEVICE);
/* 规则3:原子标志通知底半部 */
atomic_set(&bdev->rx_pending, rx_count);
/* 规则4:调度 Tasklet(数据路径)或 Workqueue(控制路径) */
if (bdev->is_data_channel)
tasklet_schedule(&bdev->rx_tasklet);
else
queue_work(bdev->wq, &bdev->ctrl_work);
return IRQ_HANDLED;
}
/* Tasklet - 数据帧处理(不可睡眠) */
static void bt_uart_rx_tasklet(unsigned long data)
{
struct bt_uart_dev *bdev = (struct bt_uart_dev *)data;
unsigned int count = atomic_xchg(&bdev->rx_pending, 0);
struct sk_buff *skb;
/* 在软中断上下文中完成 HCI 帧解析 */
skb = bt_uart_build_skb(bdev, count);
if (skb)
hci_recv_frame(bdev->hdev, skb);
}
/* Workqueue - 控制命令处理(可睡眠) */
static void bt_uart_ctrl_work(struct work_struct *work)
{
struct bt_uart_dev *bdev = container_of(work, struct bt_uart_dev, ctrl_work);
unsigned int count = atomic_xchg(&bdev->rx_pending, 0);
/* 进程上下文中可调用 mutex 保护 */
mutex_lock(&bdev->cmd_lock);
bt_uart_process_cmd(bdev, count);
mutex_unlock(&bdev->cmd_lock);
}
性能数据:采用上述优化后,某 BLE 5.2 驱动在 2 Mbps PHY 下的 RX 中断延迟从 120 µs 降至 18 µs,吞吐量从 1.2 Mbps 提升至 1.85 Mbps,丢包率从 0.3% 降至 0.01%。
4.5 本章小结
- 临界区保护:短临界区(< 5 µs)用关中断;长临界区用挂起调度器,避免阻塞高优先级中断。
- ISR 最小化:ISR 只做寄存器读取、数据搬运和底半部调度,禁止调用协议栈 API 或睡眠函数。
- 底半部选型:数据路径用 Tasklet(低延迟、不可睡眠);控制路径用 Workqueue(可睡眠、适合阻塞操作)。
- 综合实践:通过原子标志 + 双底半部机制,实现中断延迟 < 20 µs,满足蓝牙 BR/EDR 和 BLE 的实时性要求。