ソフト割り込み
Rev.1を表示中。最新版はこちら。
ソフト割り込み=アセンブリのint命令という事で、ソフト割り込みが負荷の大きい割り込み処理の遅延に使われる。と説明があるのですが、「なぜint命令でそのような事ができるのか。」と疑問に思っていました。そして調べてみると、linuxのソフト割り込みとはアセンブリのint命令と全く関係ないようです。x86では割り込みが発生すると、その割り込みディスクリプタで設定されるアドレスにジャンプするわけですが、その時その割り込み処理から抜けるまで(たぶんiret)その割り込みは禁止されるそうです。そうなると、ネットワークとかタイマーのような頻度多い割り込みハンドラーでは、割り込みの取りこぼしが発生してしまいます。その回避策として一旦割り込みハンドラーが起動すると、その主たる処理をソフト割り込みとして起動するように設定して、割り込みハンドラを終了するという事らしいです。そしてソフト割り込みとする主たる処理は、カーネルタスク(ksoftirqd)として起動されます。要は割り込みハンドラと関係ないスケージューリング対象のタスクとして処理されるというわけです。
linuxでは下記の9のソフト割込みが固定的に定義されてあって、ユーザが新規に設定することはできないようです。なおソフト割込みにタスクレットをハンドリングすることで、ユーザが遅延処理としてソフト割り込みを間接的に使うことができるようになっています。enumで定義されている順序が優先順位となるようです。
include/linux/interrupt.h enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, #ifdef CONFIG_HIGH_RES_TIMERS HRTIMER_SOFTIRQ, #endif RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ }; static struct softirq_action softirq_vec[32]
open_softirq関数は各ソフト割り込みをsoftirq_vecテーブルに設定します。(設定からソフト割り込みとして定義できるのは32個)この関数はタイマーモジュールの初期化処理(kernel/timer.c:init_timers)とか、ネットワークモジュールの初期化処理(net/core/dev.c:net_dev_init)とかで、所定のenum定数と処理ルーチンを引数として呼ばれています。
kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action *)) { softirq_vec[nr].action = action; }ソフト割り込みに起動要求するのがraise_softirq関数です。これは割り込みハンドラーから呼ばれるはずです。(たぶん)。raise_softirq関数はローカルの割り込みを禁止してraise_softirq_irqoff関数を呼んでいます。raise_softirq_irqoff関数はCPU毎にある静的変数のsoftirq_pendingの所定のビットをセットしているだけです。イメージは通常のスケージューリングでランキューのタスク状態をTASK_RUNNINGに設定するような感じです。
in_interrupt関数はソフト割り込みが禁止されていないか、また割り込みハンドラ内かをチェックしていて、そうでないならwakeup_softirqd関数でカーネルタスクのksofutoirqdを起動し、そこからソフト割り込みがスケージューリングされるようにします。そしてスケージューリングされた適当な所でソフト割り込みが起動されるということです。
kernel/softirq.c inline void raise_softirq_irqoff(unsigned int nr) { __raise_softirq_irqoff(nr); if (!in_interrupt()) wakeup_softirqd(); }wakeup_softirqd関数はCPU変数からksoftirqdのタスク構造体を取得し、まだ動作していないならwake_up_process関数でksoftirqdをタスクとして起動します。
kernel/softirq.c static inline void wakeup_softirqd(void) { /* Interrupts are disabled: no need to stop preemption */ struct task_struct *tsk = __get_cpu_var(ksoftirqd); if (tsk && tsk->state != TASK_RUNNING) wake_up_process(tsk); }ksoftirqdタスクが起動すると、local_softirq_pending関数でソフト割り込みがあるかどうかチェックしています。なければschedule関数がよばれます。もしなければschedule関数をはさんでループし続ける事になるわけですが、しかしksoftirqd関数のプライオリチィは低く設定されていて、基本的にはアイドル状態のとき動くようになっています。
ソフト割り込みがあると、なんやら設定し、do_softirq関数を呼んでいます。この関数で実際のソフト割り込みの処理を行います。
kernel/softirq.c static int ksoftirqd(void * __bind_cpu) { set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) { preempt_disable(); if (!local_softirq_pending()) { preempt_enable_no_resched(); schedule(); preempt_disable(); } __set_current_state(TASK_RUNNING); while (local_softirq_pending()) { /* Preempt disable stops cpu going offline. If already offline, we'll be on wrong CPU: don't process */ if (cpu_is_offline((long)__bind_cpu)) goto wait_to_die; do_softirq(); preempt_enable_no_resched(); cond_resched(); preempt_disable(); } preempt_enable(); set_current_state(TASK_INTERRUPTIBLE); } __set_current_state(TASK_RUNNING); return 0; }do_softirq関数ではソフト割り込みがあるかどうかをチェックして有れば__do_softirq関数を呼んでいます。
__do_softirq関数ではソフト割り込み処理が設定されているテーブルsoftirq_vecをhに設定し、順番にそのコールバック処理h->action(h)を呼んでいます。pending = local_softirq_pending()のpendingのすべてのビットに対して処理が行われるようです。言い換えると本関数ではペンヂングされているすべてのソフト割り込みを処理するようです。
注目すべき点は、コールバック呼び出す前にlocal_irq_enable関数で割り込みを許可しています。ソフト割り込み処理中に割り込みが発生します。そうなるとすべてのソフト割り込みを処理してループを抜けてもまた新たにソフト割り込みが発生しているかもしれません。そこでループを抜けた時点で再度pending = local_softirq_pending()で、ソフト割り込みの有り無しをチェックし、有ればgoto restartで再度同じ処理を繰り返します。しかしそうなると、割り込み頻度多い処理では、ソフト割り込みの処理に対する負荷が大きくなります。それをさけるためにnt max_restart = MAX_SOFTIRQ_RESTARTでMAX_SOFTIRQ_RESTART回ループしたらその処理を終了するようになっています。なおMAX_SOFTIRQ_RESTARTは10となっています。
kernel/softirq.c asmlinkage void __do_softirq(void) { struct softirq_action *h; __u32 pending; int max_restart = MAX_SOFTIRQ_RESTART; int cpu; pending = local_softirq_pending(); account_system_vtime(current); __local_bh_disable((unsigned long)__builtin_return_address(0)); trace_softirq_enter(); cpu = smp_processor_id(); restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); local_irq_enable(); h = softirq_vec; do { if (pending & 1) { h->action(h); rcu_bh_qsctr_inc(cpu); } h++; pending >>= 1; } while (pending); local_irq_disable(); pending = local_softirq_pending(); if (pending && --max_restart) goto restart; if (pending) wakeup_softirqd(); trace_softirq_exit(); account_system_vtime(current); _local_bh_enable(); }