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