2012年02月14日
情報科学類 オペレーティングシステム II
筑波大学 システム情報工学研究科
コンピュータサイエンス専攻, 電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2011/2012-02-14
あるいは、次のページから手繰っていくこともできます。
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.1.3/include/linux/interrupt.h
434: struct softirq_action
435: {
436: void (*action)(struct softirq_action *);
437: };
linux-3.1.3/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.1.3/kernel/softirq.c
55: static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
...
207: asmlinkage void __do_softirq(void)
208: {
209: struct softirq_action *h;
210: __u32 pending;
...
214: pending = local_softirq_pending();
...
226: local_irq_enable();
...
228: h = softirq_vec;
...
230: do {
231: if (pending & 1) {
...
238: h->action(h);
...
251: h++;
252: pending >>= 1;
253: } while (pending);
...
268: }
linux-3.1.3/include/linux/interrupt.h
409: enum
410: {
411: HI_SOFTIRQ=0,
412: TIMER_SOFTIRQ,
413: NET_TX_SOFTIRQ,
414: NET_RX_SOFTIRQ,
415: BLOCK_SOFTIRQ,
416: BLOCK_IOPOLL_SOFTIRQ,
417: TASKLET_SOFTIRQ,
418: SCHED_SOFTIRQ,
419: HRTIMER_SOFTIRQ,
420: RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
421:
422: NR_SOFTIRQS
423: };
Softirq の主な利用場所
linux-3.1.3/net/core/dev.c
6448: static int __init net_dev_init(void)
6449: {
...
6511: open_softirq(NET_TX_SOFTIRQ, net_tx_action);
6512: open_softirq(NET_RX_SOFTIRQ, net_rx_action);
...
6520: }
2933: static void net_tx_action(struct softirq_action *h)
2934: {
...
2989: }
3782: static void net_rx_action(struct softirq_action *h)
3783: {
...
3866: }
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 1 0 15 0 2160 676 stext Ss ? 0:02 init [5]
...
1 0 3 1 34 19 0 0 ksofti SN ? 0:00 [ksoftirqd/0]
...
1 0 6 1 34 19 0 0 ksofti SN ? 0:00 [ksoftirqd/1]
...
$
問題: Softirq を優先すべきか、普通のユーザ・プロセスを優先すべきか。
Tasklet で1つの仕事は次のような、struct tasklet_struct で表現される。
linux-3.1.3/include/linux/interrupt.h
499: struct tasklet_struct
500: {
501: struct tasklet_struct *next;
502: unsigned long state;
503: atomic_t count;
504: void (*func)(unsigned long);
505: unsigned long data;
506: };

図? 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.1.3/drivers/net/wireless/ath/ath9k/ath9k.h
591: struct ath_softc {
...
600: struct tasklet_struct intr_tq;
601: struct tasklet_struct bcon_tasklet;
...
654: };
linux-3.1.3/drivers/net/wireless/ath/ath9k/init.c
602: tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc);
603: tasklet_init(&sc->bcon_tasklet, ath_beacon_tasklet,
604: (unsigned long)sc);
linux-3.1.3/drivers/net/wireless/ath/ath9k/main.c
746: irqreturn_t ath_isr(int irq, void *dev)
747: {
...
788: ath9k_hw_getisr(ah, &status); /* NB: clears ISR too */
...
795: if (!status)
796: return IRQ_NONE;
...
823: if (status & ATH9K_INT_SWBA)
824: tasklet_schedule(&sc->bcon_tasklet);
...
869: if (sched) {
...
872: tasklet_schedule(&sc->intr_tq);
873: }
874:
875: return IRQ_HANDLED;
...
878: }
671: void ath9k_tasklet(unsigned long data)
672: {
...
744: }
linux-3.1.3/drivers/net/wireless/ath/ath9k/beacon.c
356: void ath_beacon_tasklet(unsigned long data)
357: {
...
477: }
キューにつながれる仕事は、Tasklet の仕事とほとんど同じで、関数へのポイ ンタ func と data からなる。処理の主体が、ワーカ・スレッドと呼ばれるカー ネル・レベルのスレッドである所が違う。
汎用の Work Queue デフォルトのワーカ・スレッドは、events/n (nはプロセッ サ番号) とよばれ、プロセッサごとに作られる。1つのスレッドで、様々な要 求元の仕事をこなす。
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
4 0 1 0 15 0 2160 676 stext Ss ? 0:02 init [5]
...
1 0 8 1 10 -5 0 0 worker S< ? 0:00 [events/0]
1 0 9 1 10 -5 0 0 worker S< ? 0:00 [events/1]
...
$
汎用の Work Queue のワーカ・スレッドの他に、専用のワーカ・スレッドを作
ることもできる。
linux-3.1.3/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ミリ秒で、設定によって異なる。
struct workqueue_struct *create_workqueue(char *name)
ワーカ・スレッドとキューを作成し、struct workqueue_struct へのポ
インタを返す。引数は、ワーカ・スレッドの名前。
int queue_work(struct workqueue_struct *queue, struct work_struct *work)
キューに仕事を加える。
int queue_delayed_work(struct workqueue_struct *queue,
struct work_struct *work, unsigned long delay)
キューに仕事を加える。ただし、delay だけ後に実行する。
void f(void) {
省略;
}
これを実現するために、どのような Tasklet のハンドラと初期化コードを書け
ばよいか。以下の空欄を埋めなさい。
void tasklet_handler(unsigned long data) { /* ハンドラ */
/*空欄(a)*/
その他の仕事;
}
DECLARE_TASKLET(/*空欄(b)*/, /*空欄(c)*/, 0); /* 構造体の初期化 */
注意: 構造体の名前は、次の問題の解答で利用する。それらしいものを付けな
さい。
irqreturn_t irq_handler(int irq, void *dev) {
/*空欄(d)*/
return IRQ_HANDLED;
}