2011年02月15日 情報科学類 オペレーティングシステム II 筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2010/2011-02-15
あるいは、次のページから手繰っていくこともできます。
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でのハンドラの実行
ハードウェアの割り込みinclude/linux/interrupt.h struct softirq_action { void (*action)(struct softirq_action *); }; kernel/softirq.c static struct softirq_action softirq_vec[NR_SOFTIRQS];
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)の変数になっ ている。 次の関数でアクセスする。
kernel/softirq.c 55: static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; ... 191: asmlinkage void __do_softirq(void) 192: { ... 194: __u32 pending; ... 198: pending = local_softirq_pending(); ... 207: set_softirq_pending(0); 209: local_irq_enable(); 211: h = softirq_vec; 213: do { 214: if (pending & 1) { ... 219: h->action(h); ... 231: } 232: h++; 233: pending >>= 1; 234: } while (pending); ... 249: }
include/linux/interrupt.h 376: enum 377: { 378: HI_SOFTIRQ=0, 379: TIMER_SOFTIRQ, 380: NET_TX_SOFTIRQ, 381: NET_RX_SOFTIRQ, 382: BLOCK_SOFTIRQ, 383: BLOCK_IOPOLL_SOFTIRQ, 384: TASKLET_SOFTIRQ, 385: SCHED_SOFTIRQ, 386: HRTIMER_SOFTIRQ, 387: RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ 388: 389: NR_SOFTIRQS 390: };Softirq の主な利用場所
include/linux/interrupt.h 401: struct softirq_action 402: { 403: void (*action)(struct softirq_action *); 404: }; net/core/dev.c 6003: static int __init net_dev_init(void) 6004: { ... 6066: open_softirq(NET_TX_SOFTIRQ, net_tx_action); 6067: open_softirq(NET_RX_SOFTIRQ, net_rx_action); ... 6075: } ... 2557: static void net_tx_action(struct softirq_action *h) 2558: { ... 2612: } ... 3485: static void net_rx_action(struct softirq_action *h) 3486: { ... 3569: }
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]
...
$
問題
Tasklet で1つの仕事は次のような、struct tasklet_struct で表現される。
include/linux/interrupt.h 455: struct tasklet_struct 456: { 457: struct tasklet_struct *next; 458: unsigned long state; 459: atomic_t count; 460: void (*func)(unsigned long); 461: unsigned long data; 462: };
図? 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度だけ実行される。
kernel/softirq.c 351: struct tasklet_head 352: { 353: struct tasklet_struct *head; 354: struct tasklet_struct **tail; 355: }; 356: 357: static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); 358: static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
include/linux/interrupt.h 501: static inline void tasklet_schedule(struct tasklet_struct *t) 502: { 503: if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) 504: __tasklet_schedule(t); 505: } kernel/softirq.c 360: void __tasklet_schedule(struct tasklet_struct *t) 361: { 362: unsigned long flags; 363: 364: local_irq_save(flags); 365: t->next = NULL; 366: *__get_cpu_var(tasklet_vec).tail = t; 367: __get_cpu_var(tasklet_vec).tail = &(t->next); 368: raise_softirq_irqoff(TASKLET_SOFTIRQ); 369: local_irq_restore(flags); 370: }
kernel/softirq.c 399: static void tasklet_action(struct softirq_action *a) 400: { 401: struct tasklet_struct *list; 402: 403: local_irq_disable(); 404: list = __get_cpu_var(tasklet_vec).head; 405: __get_cpu_var(tasklet_vec).head = NULL; 406: __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head; 407: local_irq_enable(); 408: 409: while (list) { 410: struct tasklet_struct *t = list; 411: 412: list = list->next; 413: 414: if (tasklet_trylock(t)) { 415: if (!atomic_read(&t->count)) { 416: if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)) 417: BUG(); 418: t->func(t->data); 419: tasklet_unlock(t); 420: continue; 421: } 422: tasklet_unlock(t); 423: } 424: 425: local_irq_disable(); 426: t->next = NULL; 427: *__get_cpu_var(tasklet_vec).tail = t; 428: __get_cpu_var(tasklet_vec).tail = &(t->next); 429: __raise_softirq_irqoff(TASKLET_SOFTIRQ); 430: local_irq_enable(); 431: } 432: }
drivers/net/wireless/ath/ath9k/init.c 565: tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc); drivers/net/wireless/ath/ath9k/main.c 623: irqreturn_t ath_isr(int irq, void *dev) 624: { ... 738: tasklet_schedule(&sc->intr_tq); ... 741: return IRQ_HANDLED; 744: } 559: void ath9k_tasklet(unsigned long data) 560: { ... 621: }
キューにつながれる仕事は、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]
...
$
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 h(void) { .... }これを実現するために、どのような初期化コードを書けばよいか。以下の空欄 を埋めなさい。
void tasklet_handler(unsigned long data) { /*空欄(a)*/ } DECLARE_TASKLET(/*空欄(b)*/, /*空欄(c)*/, 0);注意: 構造体の名前は、次の問題の解答で利用する。それらしいものを付けな さい。
irqreturn_t irq_handler(int irq, void *dev) { /*空欄(d)*/ return IRQ_HANDLED; }