1. 软中断简介
在linux内核中,硬件中断主要来自于CPU外部。softirq则是内核定义的需要CPU快速处理的程序。软中断在中断服务处理之后处理,如果在规定的时间内无法完成软中断的处理,则在softirqd内核进程处理。
2. 软中断模块的初始化
kernel/softirq.c
751 void __init softirq_init(void)
752 {
753 int cpu;
754
755 for_each_possible_cpu(cpu) {
756 int i;
757
758 per_cpu(tasklet_vec, cpu).tail =
759 &per_cpu(tasklet_vec, cpu).head;
760 per_cpu(tasklet_hi_vec, cpu).tail =
761 &per_cpu(tasklet_hi_vec, cpu).head;
762 for (i = 0; i < NR_SOFTIRQS; i++)
763 INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));
764 }
765
766 register_hotcpu_notifier(&remote_softirq_cpu_notifier);
767
768 open_softirq(TASKLET_SOFTIRQ, tasklet_action);
769 open_softirq(HI_SOFTIRQ, tasklet_hi_action);
770 }
755-764 初始化tasklet_vec, tasklet_hi_vec数据结构,初始化softirq_work_list。
768-769 打开TASKLET_SOFTIRQ,HI_SOFTIRQ。
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
设置 软中断向量。
3. 触发软中断
385 /*
386 * This function must run with irqs disabled!
387 */
388 inline void raise_softirq_irqoff(unsigned int nr)
389 {
390 __raise_softirq_irqoff(nr);
391
392 /*
393 * If we're in an interrupt or softirq, we're done
394 * (this also catches softirq-disabled code). We will
395 * actually run the softirq once we return from
396 * the irq or softirq.
397 *
398 * Otherwise we wake up ksoftirqd to make sure we
399 * schedule the softirq soon.
400 */
401 if (!in_interrupt())
402 wakeup_softirqd();
403 }
405 void raise_softirq(unsigned int nr)
406 {
407 unsigned long flags;
408
409 local_irq_save(flags);
410 raise_softirq_irqoff(nr);
411 local_irq_restore(flags);
412 }
413
414 void __raise_softirq_irqoff(unsigned int nr)
415 {
416 trace_softirq_raise(nr);
417 or_softirq_pending(1UL << nr);
418 }
include/linux/interrupt.h
#define or_softirq_pending(x) (local_softirq_pending() |= (x))
raise_softirq主要是,设置pending标志,如果可能唤醒softirqd内核进程。
4. 软中断的处理
kernel/softirq.c
367 void irq_exit(void)
368 {
369 #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
370 local_irq_disable();
371 #else
372 WARN_ON_ONCE(!irqs_disabled());
373 #endif
374
375 account_irq_exit_time(current);
376 trace_hardirq_exit();
377 sub_preempt_count(HARDIRQ_OFFSET);
378 if (!in_interrupt() && local_softirq_pending())
379 invoke_softirq();
380
381 tick_irq_exit();
382 rcu_irq_exit();
383 }
378行,如果不在中断中且有等待处理的软中断,调用invoke_softirq。
334 static inline void invoke_softirq(void)
335 {
336 if (!force_irqthreads) {
337 /*
338 * We can safely execute softirq on the current stack if
339 * it is the irq stack, because it should be near empty
340 * at this stage. But we have no way to know if the arch
341 * calls irq_exit() on the irq stack. So call softirq
342 * in its own stack to prevent from any overrun on top
343 * of a potentially deep task stack.
344 */
345 do_softirq();
346 } else {
347 wakeup_softirqd();
348 }
349 }
在内核的配置中,force_irqthreads是0,调用do_softirq。
293 asmlinkage void do_softirq(void)
294 {
295 __u32 pending;
296 unsigned long flags;
297
298 if (in_interrupt())
299 return;
300
301 local_irq_save(flags);
302
303 pending = local_softirq_pending();
304
305 if (pending)
306 __do_softirq();
307
308 local_irq_restore(flags);
309 }
214 asmlinkage void __do_softirq(void)
215 {
216 struct softirq_action *h;
217 __u32 pending;
218 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
219 int cpu;
220 unsigned long old_flags = current->flags;
221 int max_restart = MAX_SOFTIRQ_RESTART;
222
223 /*
224 * Mask out PF_MEMALLOC s current task context is borrowed for the
225 * softirq. A softirq handled such as network RX might set PF_MEMALLOC
226 * again if the socket is related to swap
227 */
228 current->flags &= ~PF_MEMALLOC;
229
230 pending = local_softirq_pending();
231 account_irq_enter_time(current);
232
233 __local_bh_disable((unsigned long)__builtin_return_address(0),
234 SOFTIRQ_OFFSET);
235 lockdep_softirq_enter();
236
237 cpu = smp_processor_id();
238 restart:
239 /* Reset the pending bitmask before enabling irqs */
240 set_softirq_pending(0);
241
242 local_irq_enable();
243
244 h = softirq_vec;
245
246 do {
247 if (pending & 1) {
248 unsigned int vec_nr = h - softirq_vec;
249 int prev_count = preempt_count();
250
251 kstat_incr_softirqs_this_cpu(vec_nr);
252
253 trace_softirq_entry(vec_nr);
254 mt_trace_SoftIRQ_start(vec_nr);
255 h->action(h);
256 mt_trace_SoftIRQ_end(vec_nr);
257 trace_softirq_exit(vec_nr);
258 if (unlikely(prev_count != preempt_count())) {
259 printk(KERN_ERR "huh, entered softirq %u %s %p"
260 "with preempt_count %08x,"
261 " exited with %08x?\n", vec_nr,
262 softirq_to_name[vec_nr], h->action,
263 prev_count, preempt_count());
264 preempt_count() = prev_count;
265 }
266
267 rcu_bh_qs(cpu);
268 }
269 h++;
270 pending >>= 1;
271 } while (pending);
272
273 local_irq_disable();
274
275 pending = local_softirq_pending();
276 if (pending) {
277 if (time_before(jiffies, end) && !need_resched() &&
278 --max_restart)
279 goto restart;
280
281 wakeup_softirqd();
282 }
283
284 lockdep_softirq_exit();
285
286 account_irq_exit_time(current);
287 __local_bh_enable(SOFTIRQ_OFFSET);
288 tsk_restore_flags(current, old_flags, PF_MEMALLOC);
289 }
255调用open_softirq注册的处理函数,如TASKLET_SOFTIRQ的处理函数tasklet_action。
如果277满足,继续处理软中断。否则唤醒内核软中断处理进程。
以上讲述了软中断处理的整个流程,注册/触发/处理。更详细的内容可以读kernel/softirq.c,代码是最好的老师。