タスクレット
Rev.5を表示中。最新版はこちら。
タスクレットは割り込み処理を遅延するものです。ソフト割り込みと何が違うかと言えば、タスクレットはソフト割り込みから呼ばれるもので、そもそもソフト割り込みの上位アプリケーションと言う感じでしょうか。ソフト割り込みは静的に割り込みIDが決められて、ユーザが新規に登録することはできません。しかしタスクレットはリスト構造をしていて、ユーザが新規に追加することが可能です。ユーザがタスクレットを使う場合DECLARE_TASKLETを使ってtasklet_struct構造体を初期化します。
include/linux/interrupt.h struct tasklet_struct { struct tasklet_struct *next; <-次にタスクレット構造体 unsigned long state; <-タスクレットの状態 atomic_t count; <-0なら許可、1なら禁止 void (*func)(unsigned long); <-処理関数 unsigned long data; <-引数 };そしてtasklet_hi_schedule/tasklet_schedule関数を呼ぶことで、タスクレットを起動することができます。ユーザはこれだけでタスクレット処理を記述することができるわけです。なおtasklet_hi_schedule関数はHI_SOFTIRQのソフト割り込みを、tasklet_schedule関数はTASKLET_SOFTIRQのソフト割り込みを呼ぶだけの違いです。
tasklet_hi_schedule/tasklet_schedule関数では、まずそのタスクレットすでにスケージューリングされているかチェックしています。二重登録防止です。タスクレットがスケジューリングされてということは、CPU変数のtasklet_vecにtasklet_struct構造体がリストされているかと言うことです。そうでないなら__tasklet_schedule/__tasklet_hi_schedule関数が呼ばれます。
include/linux/interrupt.h static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); } static inline void tasklet_hi_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_hi_schedule(t); }__tasklet_schedule/__tasklet_hi_schedule関数では、tasklet_struct 構造体をCPU変数のtasklet_vecにリスト追加して、TASKLET_SOFTIRQ/HI_SOFTIRQでraise_softirq_irqoff関数が呼んでいます。
kernel/softirq.c void __tasklet_schedule(struct tasklet_struct *t) { unsigned long flags; local_irq_save(flags); t->next = NULL; *__get_cpu_var(tasklet_vec).tail = t; __get_cpu_var(tasklet_vec).tail = &(t->next); raise_softirq_irqoff(TASKLET_SOFTIRQ); local_irq_restore(flags); } kernel/softirq.c void __tasklet_hi_schedule(struct tasklet_struct *t) { unsigned long flags; local_irq_save(flags); t->next = NULL; *__get_cpu_var(tasklet_hi_vec).tail = t; __get_cpu_var(tasklet_hi_vec).tail = &(t->next); raise_softirq_irqoff(HI_SOFTIRQ); local_irq_restore(flags); }TASKLET_SOFTIRQ/HI_SOFTIRQのソフト割り込みのコールバック処理はtasklet_action/tasklet_hi_action関数となっています。違いは__raise_softirq_irqoff関数でソフト割り込みに起動要求する時、TASKLET_SOFTIRQにするかHI_SOFTIRQにするかだけで、中身は全く同じです。
tasklet_action関数では、CPU変数のtasklet_vecからタスクレットリストを取得し、すべてのリストを走査して各タスクレットをt->func(t->data)として起動しています。なお、タスクレットを起動する前にtasklet_trylock関数でタスクレット状態がTASKLET_STATE_RUNであるかチェックしています。TASKLET_STATE_RUNでないならタスクレット状態をTASKLET_STATE_RUNにして0以外を返し、タスクレットが起動されます。TASKLET_STATE_RUNならそのタスクレットは起動中ということで、そのタスクレットをリストの末尾に繋ぎ変え再度__raise_softirq_irqoff関数を呼ぶことで、次にタスクレットの起動処理となるわけです。tasklet_trylock関数でTASKLET_STATE_RUNがどうかをチェックすることで、タスクレットを再入不可能な処理として作成することができるわけですが、
確かにwakeup_softirqd関数ではCPU変数からksoftirqdのtask_struct構造体を取得し、そのタスク状態でksoftirqdを起動するかしないかを判定していました。このことからksoftirqdは各CPUで1つづつ起動され、ほかのwakeup_softirqd関数よりこのタスクレットが起動されているケースだと思ったのですが・・・。しかし、__tasklet_schedule関数でタスクレットをリストするとき、CPU変数のtasklet_vecに挿入していました。1つのtaskletリストを全CPUで共有しているようでもありません。しかもタスクレットstateメンバーでそのタスクレットがスケジューリングされていたらリスト挿入しないようになっていました。そうなるとここであえてtasklet_trylock関数で二重起動回避チェックを入れる必要があるのか???なんですが。
kernel/softirq.c static void tasklet_action(struct softirq_action *a) { struct tasklet_struct *list; local_irq_disable(); list = __get_cpu_var(tasklet_vec).head; __get_cpu_var(tasklet_vec).head = NULL; __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head; local_irq_enable(); while (list) { struct tasklet_struct *t = list; list = list->next; if (tasklet_trylock(t)) { if (!atomic_read(&t->count)) { if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) BUG(); t->func(t->data); tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t->next = NULL; *__get_cpu_var(tasklet_vec).tail = t; __get_cpu_var(tasklet_vec).tail = &(t->next); __raise_softirq_irqoff(TASKLET_SOFTIRQ); local_irq_enable(); } }
include/linux/interrupt.h static inline int tasklet_trylock(struct tasklet_struct *t) { return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state); }タスクレットはスリープできない。ということですが、上で見てきたようにソフト割り込みが発生すると、ペンディングされているすぺでのタスクレットを処理しようとします。もし1つタスクレットがスリープさえたからといって、次のタスクレットに処理が移るわけでありません。ksoftiqrdタスクからみると、これらのタスクレット処理は単にksoftiqrdの延長線上の関数でしかないからです。しかもそれ故ほかのタスクレットだけでなく他のソフト割り込み(タイマー、通信とか)の処理さえ移行しなくなります。ひょっとしたら他のCPUのksoftirqdから起動かかるのかもしれませんが・・・。この辺りがスリープできない要因では。と勝手に解釈しています。そしてソフト割り込みは処理はシステムの多大な影響をおよぼすため、ユーザに解放しないで、タスクレットと言う形(と言ってもその危険性が回避されるわけでないのですが。)ユーザに提供されているのかな。と。なお、ver6からワークキューといのが導入されています。これはユーザがタスクレットに相当する(ワーク)を起動する独自のksoftirqd相当するカーネルスレッドを割り当てて、従ってその責任で自由にスリープしてもいいですよ。ってものが導入されています。