2013年02月05日
情報科学類 オペレーティングシステム II
筑波大学 システム情報工学研究科
コンピュータサイエンス専攻, 電子・情報工学系
新城 靖
<yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2012/2013-02-05
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

図? 割り込み処理の前半部分と後半部分
割り込みハンドラ(前半部)と後半部の役割分担の目安。
注意1: Tasklet は、task 構造体とはまったく関係ない。名前がよくない。
注意2: Softirq という用語を、割り込み処理の後半部という意味で使う人もい る。
注意3: 伝統的なUnixでは、top half は、システム・コールから派生する上位 層の処理、bottom half は、割り込みから派生する下位層の処理の意味で使わ れることがある。Linux では、top half, bottom half は、割り込み処理の前 半部分と後半部分の意味に使う。

図? ハードウェアの割り込みにおけるハンドラの実行

図? Softirqでのハンドラの実行
ハードウェアの割り込み
linux-3.6.8/include/linux/interrupt.h
442: struct softirq_action
443: {
444: void (*action)(struct softirq_action *);
445: };
linux-3.6.8/kernel/softirq.c
55: static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
void softirq_handler(struct softirq_action *a) {
...
}
この変数のビットnを1にする(raise)には、次の関数を使う。
void raise_softirq(unsigned int n)
まず、割り込みが禁止し、
n で指定された Softirq を実行可能にし、
再び割り込みを許可する。
void raise_softirq_irqoff(unsigned int n)
n で指定された Softirq を実行可能にする。
割り込みハンドラによる実行を想定。
割り込みハンドラは、割り込み禁止で実行されている。
概念的には、次のようになっている。
unsigned int softirq_pending;
raise_softirq(int n)
{
softirq_pending |= (1 << n);
}
実際には、変数softirq_pendingは、プロセッサごとに固有(local)の変数になっ
ている。
次の関数でアクセスする。
linux-3.6.8/kernel/softirq.c
207: asmlinkage void __do_softirq(void)
208: {
209: struct softirq_action *h;
210: __u32 pending;
...
222: pending = local_softirq_pending();
...
232: set_softirq_pending(0);
233:
234: local_irq_enable();
235:
236: h = softirq_vec;
237:
238: do {
239: if (pending & 1) {
...
246: h->action(h);
...
258: }
259: h++;
260: pending >>= 1;
261: } while (pending);
...
277: }
linux-3.6.8/include/linux/interrupt.h
417: enum
418: {
419: HI_SOFTIRQ=0,
420: TIMER_SOFTIRQ,
421: NET_TX_SOFTIRQ,
422: NET_RX_SOFTIRQ,
423: BLOCK_SOFTIRQ,
424: BLOCK_IOPOLL_SOFTIRQ,
425: TASKLET_SOFTIRQ,
426: SCHED_SOFTIRQ,
427: HRTIMER_SOFTIRQ,
428: RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
429:
430: NR_SOFTIRQS
431: };
Softirq の主な利用場所
linux-3.6.8/net/core/dev.c
6579: static int __init net_dev_init(void)
6580: {
...
6642: open_softirq(NET_TX_SOFTIRQ, net_tx_action);
6643: open_softirq(NET_RX_SOFTIRQ, net_rx_action);
...
6651: }
3005: static void net_tx_action(struct softirq_action *h)
3006: {
...
3061: }
3903: static void net_rx_action(struct softirq_action *h)
3904: {
...
3987: }
$ ps alx
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
...
1 0 3 2 20 0 0 0 run_ks S ? 0:00 [ksoftirqd/0]
$
問題: Softirq を優先すべきか、普通のユーザ・プロセスを優先すべきか。
Tasklet で1つの仕事は次のような、struct tasklet_struct で表現される。
linux-3.6.8/include/linux/interrupt.h
503: struct tasklet_struct
504: {
505: struct tasklet_struct *next;
506: unsigned long state;
507: atomic_t count;
508: void (*func)(unsigned long);
509: unsigned long data;
510: };

図? Taskletにおける仕事のキュー
DECLARE_TASKLET(name, func, data)
有効な(count==0) の struct tasklet_struct を宣言する
DECLARE_TASKLET_DISABLED(name, func, data)
無効な(count==1) の struct tasklet_struct を宣言する
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
その他に、生成消滅有効無効に関して次のような操作がある。
void tasklet_handler(unsigned long data) {
....
}
void tasklet_schedule(struct tasklet_struct *t)
Tasklet t を通常の優先度でスケジュールする
void tasklet_hi_schedule(struct tasklet_struct *t)
Tasklet t を高優先度でスケジュールする
すると、それは「そのうちに」1度だけ実行される。
linux-3.6.8/drivers/net/wireless/ath/ath9k/ath9k.h
657: struct ath_softc {
...
664: struct tasklet_struct intr_tq;
665: struct tasklet_struct bcon_tasklet;
...
734: };
linux-3.6.8/drivers/net/wireless/ath/ath9k/init.c
561: tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc);
562: tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet,
563: (unsigned long)sc);
linux-3.6.8/drivers/net/wireless/ath/ath9k/main.c
429: irqreturn_t ath_isr(int irq, void *dev)
430: {
...
476: ath9k_hw_getisr(ah, &status); /* NB: clears ISR too */
...
489: if (status & SCHED_INTR)
490: sched = true;
...
522: if (status & ATH9K_INT_SWBA)
523: tasklet_schedule(&sc->bcon_tasklet);
...
550: if (sched) {
...
553: tasklet_schedule(&sc->intr_tq);
554: }
555:
556: return IRQ_HANDLED;
...
559: }
361: void ath9k_tasklet(unsigned long data)
362: {
...
427: }
linux-3.6.8/drivers/net/wireless/ath/ath9k/beacon.c
310: void ath9k_beacon_tasklet(unsigned long data)
311: {
...
408: }
キューにつながれる仕事は、Tasklet の仕事とほとんど同じで、関数へのポイ ンタ func と data からなる。処理の主体が、ワーカ・スレッドと呼ばれるカー ネル・レベルのスレッドである所が違う。
$ ps alx
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
...
1 0 5 2 0 -20 0 0 worker S< ? 0:00 [kworker/0:0H]
1 0 6 2 20 0 0 0 worker S ? 0:00 [kworker/u:0]
1 0 7 2 0 -20 0 0 worker S< ? 0:00 [kworker/u:0H]
1 0 27 2 20 0 0 0 worker S ? 0:00 [kworker/u:1]
1 0 367 2 0 -20 0 0 worker S< ? 0:00 [kworker/0:1H]
1 0 650 2 0 -20 0 0 worker S< ? 0:00 [kworker/u:1H]
...
1 0 29461 2 20 0 0 0 worker S ? 0:00 [kworker/0:1]
1 0 29482 2 20 0 0 0 worker S ? 0:00 [kworker/0:2]
1 0 29503 2 20 0 0 0 worker S ? 0:00 [kworker/0:0]
1 0 29601 2 20 0 0 0 worker S ? 0:00 [kworker/0:3]
$
汎用の Work Queue のワーカ・スレッドの他に、専用のワーカ・スレッドを作
ることもできる。
linux-3.6.8/include/linux/workqueue.h
18: typedef void (*work_func_t)(struct work_struct *work);
79: struct work_struct {
80: atomic_long_t data;
81: struct list_head entry;
82: work_func_t func;
...
86: };
struct work_struct my_work; ... INIT_WORK(&my_work,my_work_handler);
void my_work_handler(struct work_struct *work)
{
...
}
schedule_work(&work);
この結果、INIT_WORK() で設定したハンドラがワーカ・スレッドにより「その
うち」に呼び出される。
schedule_work() では、即座に実行される可能性もある。少し後に実行したい (間を取りたい)時には、次の関数を呼ぶ。
schedule_delayed_work(&work,ticks);
ticks は、どのくらい間をとるか。単位は、jiffies (今後の授業で取り上げる)。
多くのシステムで10ミリ秒-1ミリ秒で、設定によって異なる。
void f(int arg1, int arg2) {
省略;
}
これを実現するために、どのような Tasklet のハンドラと初期化コードを書け
ばよいか。以下の空欄を埋めなさい。
void tasklet_handler(unsigned long data) { /* Tasklet ハンドラ */
int arg1, arg2;
arg1 = 省略;
arg2 = 省略;
/*空欄(a)*/
その他の仕事;
}
DECLARE_TASKLET(/*空欄(b)*/, /*空欄(c)*/, 0); /* 構造体の初期化 */
注意: 構造体の名前は、次の問題の解答で利用する。それらしいものを付けな
さい。
irqreturn_t irq_handler(int irq, void *dev) {
/*空欄(d)*/
return IRQ_HANDLED;
}