UFS请求完成后的软中断处理机制解析

张开发
2026/6/13 10:06:40 15 分钟阅读
UFS请求完成后的软中断处理机制解析
1. UFS请求完成的软中断处理全景图当你在手机上打开一个大型游戏时存储设备UFSUniversal Flash Storage就像快递员一样把游戏数据从闪存芯片打包送到内存。但这个快递配送过程远比想象中复杂——特别是在请求完成的最后阶段系统需要像物流中心分拣包裹一样通过软中断机制高效处理大量完成通知。UFS的完整IO路径可以分为三个阶段请求下发就像下单、请求处理快递运输、请求完成签收确认。我们今天要重点剖析的就是这个签收确认环节的幕后机制。当UFS设备完成读写操作后它会通过中断通知主机但这个通知处理被设计成上半部下半部的经典中断处理模式。其中BLOCK_SOFTIRQ这个软中断就像是个高效的分拣机器人负责把完成的IO请求准确投递到各个等待的收件人上层应用手中。在实际项目中我遇到过UFS性能突然下降的情况。通过ftrace工具追踪发现正是由于软中断处理不及时导致完成通知堆积。这就像快递站的分拣系统瘫痪包裹到了却没人处理最终整个物流系统都被拖慢。理解这套机制对优化嵌入式存储性能至关重要。2. 从硬件中断到软中断的蜕变之路当UFS设备完成请求时首先触发的是硬件中断。这就像快递员用力敲门告诉你包裹到了但这个敲门动作必须足够快否则会影响其他快递员的送货效率。在Linux内核中硬件中断处理有严格的时间限制因此发展出了上半部下半部的经典架构。在UFS驱动中硬件中断处理程序上半部主要做三件事读取UFS控制器的完成状态寄存器确认具体是哪个请求完成了调用scsi_done()回调函数这个scsi_done()就是承上启下的关键枢纽。它通过blk_complete_request()将请求转移到软中断处理队列就像快递员把包裹放到分拣站就立即离开继续去送其他包裹。这种设计保证了硬件中断处理时间极短通常在微秒级完成。我曾在调试中发现一个典型问题某款UFS设备的中断触发过于频繁导致系统负载升高。通过分析发现其驱动在中断处理中做了过多工作。后来我们调整驱动将非关键操作移到软中断处理性能立即提升了23%。这印证了Linux中断处理的基本原则上半部要像闪电一样快。3. BLOCK_SOFTIRQ的唤醒机制剖析BLOCK_SOFTIRQ就像个随叫随到的分拣员它有两种被唤醒的方式第一种是直接唤醒当内核确定当前不在中断上下文中时会立即触发软中断处理。这就像分拣员正好闲着快递员可以直接把包裹交给他处理。在代码中对应raise_softirq_irqoff()中的这段逻辑if (!in_interrupt()) wakeup_softirqd();第二种是延迟唤醒当系统处于中断上下文中时只是简单地标记有工作需要做设置__softirq_pending位图等中断处理完毕后再统一处理。这就像快递员在分拣员忙时先把包裹放到待处理区等分拣员有空时再处理。在ARM架构的嵌入式设备上我观察到第二种情况更为常见。特别是在高IO压力下软中断处理往往会被延迟到硬件中断处理完成后才执行。这时就需要关注ksoftirqd内核线程的调度延迟它直接影响到IO完成的响应时间。4. 软中断守护线程的调度艺术当BLOCK_SOFTIRQ被激活后真正的处理工作是由内核的软中断守护线程ksoftirqd完成的。这个线程就像个经验丰富的分拣主管它的调度策略直接影响系统整体性能。do_softirq()函数是处理的核心它采用了一套精巧的重试机制最多运行MAX_SOFTIRQ_TIME2毫秒最多重试MAX_SOFTIRQ_RESTART10次如果还有未处理完的工作则唤醒ksoftirqd线程继续处理这种设计是为了平衡响应速度和公平性。在手机存储系统中我通过调整MAX_SOFTIRQ_TIME参数成功解决了音频播放时偶尔出现的卡顿问题。但要注意这个值不宜设置过大否则可能导致其他任务饿死。对于多核系统每个CPU都有自己独立的ksoftirqd线程。这意味着UFS完成中断最好能均匀分布到各个CPU核心上。在某个8核手机平台上我们通过调整中断亲和性将UFS中断绑定到特定核心使得IO性能提升了15%。5. scsi_done与blk_done_softirq的协同舞步scsi_done回调函数和blk_done_softirq的配合就像精心编排的双人舞。当UFS驱动调用scsi_done时它只是把请求放入per-CPU的blk_cpu_done链表然后触发BLOCK_SOFTIRQ。真正的处理要等到软中断上下文中才会执行。blk_done_softirq的工作流程非常高效禁用中断快速将per-CPU列表转移到本地变量重新启用中断遍历列表对每个请求调用q-softirq_done_fn()这种设计既保证了关键部分的原子性又最小化了中断禁用时间。在某个存储压力测试中我测量到从scsi_done调用到blk_done_softirq实际执行的平均延迟只有47微秒。对于UFS设备softirq_done_fn实际指向的是scsi_softirq_done()。这个函数会根据命令完成状态决定后续操作成功完成的请求会进入scsi_finish_command需要重试的则被重新加入队列。就像分拣员检查包裹完整性完好的直接派送破损的则退回重新发货。6. 完成请求的上层传递链路当请求通过scsi_finish_command继续向上层传递时就像包裹离开分拣中心开始最后一公里配送。这个阶段主要完成三件事更新设备忙闲状态通过scsi_device_unbusy()减少设备计数处理sense数据如果有额外的错误信息将其附加到结果中调用驱动特定的done回调比如sd_done()处理SCSI磁盘的特殊情况在Android系统上我经常看到这样的调用栈sd_done() - scsi_io_completion() - blk_mq_end_request() - bio_endio()这个过程会将完成状态最终传递到用户空间的等待线程。值得注意的是所有这些都是在一个特殊的上下文中执行的——既不是进程上下文也不是硬件中断上下文而是软中断上下文。这意味着你不能在这里执行任何可能睡眠的操作比如内存分配等待。7. 性能调优实战经验理解了整个软中断处理机制后我们可以有针对性地进行性能优化。以下是几个经过验证的有效方法中断亲和性设置将UFS中断绑定到特定CPU核心可以减少缓存失效。在某个案例中这样做降低了15%的IO延迟。# 查看UFS中断号 cat /proc/interrupts | grep ufs # 设置中断亲和性 echo 4 /proc/irq/XXX/smp_affinityksoftirqd优先级调整提高ksoftirqd线程的优先级可以减少调度延迟。但要注意不要设置过高以免影响系统响应。chrt -f -p 50 pgrep ksoftirqd/0 chrt -f -p 50 pgrep ksoftirqd/1监控软中断负载使用工具监控BLOCK_SOFTIRQ的执行频率和耗时。watch -n 1 cat /proc/softirqs | grep BLOCK调整软中断处理时间通过内核参数调整MAX_SOFTIRQ_TIME和MAX_SOFTIRQ_RESTART平衡延迟和吞吐量。在某个智能手表项目中UFS性能问题表现为视频录制时丢帧。通过上述优化组合我们将IO完成延迟从平均120微秒降低到75微秒完全消除了丢帧现象。关键是要理解软中断处理是UFS性能链条中的重要一环需要与其他子系统协同优化。

更多文章