软中断和实时性
软中断和实时性
翻译自:Software interrupts and realtime
Linux内核的软中断("softirq")机制有些奇怪,在早期的Linux和处理机制下比较晦涩,且仅有极少的内核开发人员会直接接触软中断。然而它是内核的大多数重要处理的核心。在某些场景下,软中断会以一种不合时宜的方式出现。特别是内核的实时抢占补丁集经常会与软中断产生冲突,该补丁集的最新版本提供了一种解决产生软中断问题的方法,值得一看。
软中断介绍
在3.6.1-rt1补丁集的声明中,Thomas Gleixner使用如下方式描述了软中断:
首先,它是大部分不相关任务聚合的产物,运行在随机对任务施加/解除控制的上下文中
First of all, it's a conglomerate of mostly unrelated jobs, which run in the context of a randomly chosen victim w/o the ability to put any control on them.
软中断处理几乎(但不等同)与硬中断一样重要。软中断的优先级比较高(但也有例外,见下文),但低于硬中断,因此会抢占除硬中断外的任何任务。
在很早以前,Linux存在32个硬中断向量,并为每个向量分配一个设备驱动或相关的任务。大部分驱动在很早以前就已经跟软中断分进行了分离(驱动仍然会使用软中断,但需要通过中间APIs,如tasklets和timers)。当前内核中有10种软中断向量:2种用于微线程(tasklet)处理,2种用于网络,2种用于块层(block layer,块设备使用的),2种用于定时器,调度器和read-copy-update(RCU)处理各使用了一种。内核通过CPU位掩码来指定需要处理(任意时间可能发生的)软中断的CPU。例如,当一个内核子系统调用tasklet_schedule()
时,会在对应的CPU上设置TASKLET_SOFTIRQ
比特位,当软中断处理完毕后(开中断),会运行微线程 (tasklet基于软中断)。
# cat /proc/softirqs
CPU0 CPU1
HI: 1 0 //高优先级的tasklet
TIMER: 104838818 108267618 //基于系统tick的定时器
NET_TX: 2 1 //数据发送
NET_RX: 11622033 2698 //数据接收
BLOCK: 37 6833945 //块设备访问
BLOCK_IOPOLL: 0 0 //
TASKLET: 9 46 //普通优先级的tasklet
SCHED: 61485884 65788587 //多CPU调度
HRTIMER: 0 0 //高精度定时器
RCU: 48876416 46889277
有两种情况会引发软中断并抢占当前线程:一种是在处理完一个硬中断时,中断处理程序会触发软中断(硬中断之后会触发软中断,用于处理硬中断的信号或数据,如网卡报文等),为了某些目的(如减小延迟,优化缓存等) 需要尽快处理该软中断,这样就能够重新启用硬中断;另一种是内核代码(在任何时候)可能会(通过调用如local_bh_enable()
或spin_unlock_bh()
函数,这两个函数用于中断保护,防止其他中断混入处理,类似锁机制)重新启用软中断,这样会导致积累的软中断在任意一个进程的上下文中运行,该进程也就是Thomas 所说的"随机挑选的牺牲品"("randomly chosen victim")。
读者可能会对系统上运行的ksoftirqd
感到疑惑,该进程主要用于在系统的软中断负载过高时降低软中断的处理。正规处理中,如果内联的软中断进程代码在循环处理10次之后,发现还需要处理更多的软中断(由于不断产生中断),此时中断进程会唤醒合适的ksoftirqd
(每个CPU都有一个ksoftirqd
进程)进程并退出,后续由ksoftirqd
进程处理软中断。Ksoftirqd
可以被(硬件或软件)中断上下文之外的软中断打断,这种处理是必要的,否则Ksoftirqd
在处理下一个软中断前可以运行任意时间。在老的内核中,Ksoftirqd
进程以最低的优先级运行,即对软中断的处理取决于该进程是系统上的最高优先级还是最低优先级。从2.6.23开始,Ksoftirqd
默认使用普通用户优先级运行。
实时设置中的软中断
在一般的系统上,软中断机制已经足够处理大部分情况,也不需要做过多改进。然而如The new visibility of RCU processing中描述,在3.7内核中, read-copy-update的任务已经移到其辅助线程中。在实时处理中,强制任意的进程做一些随机工作的方式并不受欢迎,传统的实时补丁会将所有的软中断隔离到独立的线程中,每个线程都有各自的优先级。在这样的处理下,如,当网络需要实时响应时,该中断处理的线程的优先级会提高;相反地,当网络事件不那么紧急时,线程的优先级会降低。
从3.0实时补丁集开始,上面的处理方式无法继续工作。如它无法与Per-CPU variables and the realtime tree很好地配合使用,正如Thomas所说, 采用软中断线程的方式会导致配置问题:
通常很难从一个实时系统中获得合适的参数。将一些像软中断一样晦涩的工作添加到系统设计人员的待做事项中并不是个好主意。
It's extremely hard to get the parameters right for a RT system in general. Adding something which is obscure as soft interrupts to the system designers todo list is a bad idea.
因此,从3.0开始,软中断的处理与主线内核的处理非常类似。使用这种方式改进了代码质量并提升了非协调系统(通过消除切换到软中断线程的上下文)的性能,但也剥夺了倾向于对这种方式进行细微调整的能力(一些专注实时的开发者非常倾向于使用这种方式)。这样也是一些用户对这种改变抱怨的地方。
作为回应,3.6.1-rt1对软中断的处理又作了改动。现在,当一个线程触发一个软中断时,内核会保存特定的中断(如处理接收到的网络报文时)。一旦线程退出,内核会禁用该软中断的上下文,并运行下一个软中断,使用这种方式可以减小处理软中断的延迟(由于会立即运行下一个软中断)。同样重要的是,这种方式将软中断和产生该软中断的进程绑定到了一起。这样产生网络软中断的进程不会陷入处理其他进程的定时器的困境中,使得软中断处理本地化,消除由于处理其他进程的软中断造成的不确定性,并使得软中断能够以一开始创建任务的进程的优先级运行。
但有一种例外:由硬中断引发的软中断不能使用这种方式处理。由于无法将硬中断与一个特定的线程进行关联,因此不能使用对应的线程做必要的处理。这种情况下会将这些软中断交给ksoftirqd
进程处理。
Thomas暗示的下一步处理逻辑为,将一个禁用所有软中断的环境转变为仅禁用特定软中断的环境。大多数禁用软中断的代码仅关心某一特定的软中断处理,其他都允许正常运行。更进一步,Thomas补充到"最好的方式是完全摆脱软中断"。移除软中断机制已经在"待做"列表中存在了很长时间,但没有人实际做这些工作。
实时补丁集的性质使得用户对主线内核的缺陷感到痛苦,这导致来自实时社区的大量主线代码修改和提升。目前,实时用户已经有了一个改进的软中断机制,使其不必再进行底层调优。
TIPS:
- linux把中断按照等级分为了top half和buttom half,在执行top half的时候是关中断的,而在执行buttom half的时候是开中断的(此时可以再次处理中断)
- softirq在一个CPU上是串行的,一个tasklet本身就是串行的.softirq性能好,而一个tasklet不考虑在不同CPU上的并行场景,因此其在开发上比较便利。
参考:
本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/12169220.html