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; }