割り込みの後半部、Softirq、Tasklet、Work Queue

					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/

■今日の大事な話

■割り込み、後半部

割り込みの問題点 割り込みハンドラは、差し迫ったことだけをして、それ以外のことは、後で、 かつ、割り込みを許可した状態で実行したい。 Linux では、割り込みの処理を2つに分ける。

デバイス、割り込みハンドラ、Softirqみハンドラ、Taskletハンドラ

図? 割り込み処理の前半部分と後半部分

前回、 request_irq() で示したのは、前半の話。今日は、後半の話。

◆後半部(bottom half、botom halves)

後半部の仕事は、割り込み関連の仕事のうち、割り込みハンドラでは行わない 部分を行う。割り込みハンドラ(前半部)を軽くすると、自然に後半部の仕事 は多くなる。

割り込みハンドラ(前半部)と後半部の役割分担の目安。

Linux では、後半部の仕組みとして、歴史的事情から様々な種類がある。 普通は、Tasklet か Work Queue を使えばよい。

注意1: Tasklet は、task 構造体とはまったく関係ない。名前がよくない。

注意2: Softirq という用語を、割り込み処理の後半部という意味で使う人もい る。

注意3: 伝統的なUnixでは、top half は、システム・コールから派生する上位 層の処理、bottom half は、割り込みから派生する下位層の処理の意味で使わ れることがある。Linux では、top half, bottom half は、割り込み処理の前 半部分と後半部分の意味に使う。

■Softirq

ハードウェアの IRQ との対比

デバイス、割り込みコントローラ、CPU、割り込みハンドラ

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

デバイス、割り込みコントローラ、CPU、割り込みハンドラ

図? Softirqでのハンドラの実行

ハードウェアの割り込み Softirq 関数 do_softirq() を呼び出す「適当な時」とは、次の通りである。

◆Softirqでの張り込みハンドラの保持(配列 softirq_vec[])

Softirqの種類は、コンパイル時に静的に決まる。softirq_action 構造体の配 列で、ハンドラが保持される。
include/linux/interrupt.h 

struct softirq_action
{
        void    (*action)(struct softirq_action *);
};

kernel/softirq.c

static struct softirq_action softirq_vec[NR_SOFTIRQS];

◆Softirqでの張り込みハンドラ

softirq のハンドラは、次のような関数になる。
void softirq_handler(struct softirq_action *a) {
...
}

◆softirq_pendingとraise_softirq()

変数softirq_pendingは、ビットの配列。ビットnが1ならば、softirq_vec[n] を実行すべきであることを意味する。

この変数のビット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)の変数になっ ている。 次の関数でアクセスする。

◆do_softirq()

do_softirq() は、raise された Softirq があれば実行する。
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: }

◆Softirqの利用場所

Softirq の種類
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 の主な利用場所 普通は、tasklets を使えば十分で、自分で独自の softirqs を追加する必要は あまりない。新しい Softirq を追加したい時には、次のようにする。 以下は、ネットワーク処理での Softirq の利用例。送信(TX, Transmit) と受 信 (RX, Receive) の2種類使う。
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: }

◆ksoftirqd(kernel soft IRQ daemon)

ksoftirqd は、Softirq や Tasklet を実行するためのカーネル内のスレッド。
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]
...
$ []

問題

ksoftirqd は、実行すべき Softirq がある限界を超えた時に wakeup される。 ksoftirqd は、低優先度(nice 値 19)で Softirq (Taskletを含む)を処理する。

■Tasklet

Tasklets は、Softirqs の上に構築されている。 より簡単なインタフェースで、ロックに関する制約が緩い。

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_vec、head、next、next、next

図? Taskletにおける仕事のキュー

◆Taskletの構造体の宣言

静的に struct tasklet_struct を宣言するには、次のマクロを利用すると簡単 である。
DECLARE_TASKLET(name, func, data)
    有効な(count==0) の struct tasklet_struct を宣言する
DECLARE_TASKLET_DISABLED(name, func, data)
    無効な(count==1) の struct tasklet_struct を宣言する
kmalloc()等で動的に確保した場合には、次の関数も使える。
void tasklet_init(struct tasklet_struct *t, 
    void (*func)(unsigned long), unsigned long data);
その他に、生成消滅有効無効に関して次のような操作がある。

◆Taskletのハンドラ

Tasklet のハンドラは、次のような関数である。
void tasklet_handler(unsigned long data) {
    ....
}

◆Taskletの実行要求

Tasklet のハンドラを実行したい時には、tasklet_schedule() を呼ぶ。
void tasklet_schedule(struct tasklet_struct *t)
    Tasklet t を通常の優先度でスケジュールする
void tasklet_hi_schedule(struct tasklet_struct *t)
    Tasklet t を高優先度でスケジュールする
すると、それは「そのうちに」1度だけ実行される。

◆Taskletの実装

スケジュールされた Tasklet は、struct tasklet_struct で表現され、プロセッ サごとのキューにつなげらる。
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: }

◆tasklet_action()

schedule された Tasklet のリストは、tasklet_action() で順に実行される。
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: }

◆Taskletの利用例

Atheros Communications Incの無線LANのドライバでの利用例。
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: }

■Work Queue

割り込みに関連した処理で、次のような場合(TaskletやSoftirq では不可能な 場合)にWork Queue使う。 ***

◆Work Queueのワーカ・スレッド

Work Queue のワーカ・スレッドは、カーネル・レベルのスレッドで、 割り込み処理の後半部分の処理を行うことができる。

workqueue_struct、next、next、next、next

図? Work Queueにおける仕事のキュー

キューにつながれる仕事は、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_struct構造体

ワーク・キューで用いる 1 つの仕事は、構造体 struct work_struct で表現さ れる。
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);

◆Work Queue ハンドラ

Work Queue ハンドラは、次のように引数に struct work_struct へのポインタを取る。
void my_work_handler(struct work_struct *work)
{
...
}

◆Work の実行要求

ハンドラを呼び出したい時には、次の関数を呼ぶ。
     schedule_work(&work);
この結果、INIT_WORK() で設定したハンドラがワーカ・スレッドにより「その うち」に呼び出される。

schedule_work() では、即座に実行される可能性もある。少し後に実行したい (間を取りたい)時には、次の関数を呼ぶ。

     schedule_delayed_work(&work,ticks);
ticks は、どのくらい間をとるか。単位は、jiffies (今後の授業で取り上げる)。 多くのシステムで10ミリ秒-1ミリ秒で、設定によって異なる。

◆flush_scheduled_work()

schedule_work() で要求した仕事が完了したことを待って、次の仕事を投げた いことがある。その時には、flush_scheduled_work() を呼ぶ。

◆create_workqueue()

独自のワーカ・スレッドとキューを作りたい時には、次のような関数を用い る。
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 だけ後に実行する。

■割り込みの後半部の選択

■クイズ8 割り込みの後半部、Softirq、Tasklet、Work Queue

★問題(801) 割り込み後半の処理

割り込み処理を、前半(top half)と後半(bottom half)に分ける理由を簡単に説 明しなさい。

★問題(802) Taskletの初期化

次の関数を、割り込み処理の後半で呼び出したい。
void h(void) {
   ....
}
これを実現するために、どのような初期化コードを書けばよいか。以下の空欄 を埋めなさい。
void tasklet_handler(unsigned long data) {
    /*空欄(a)*/
}

DECLARE_TASKLET(/*空欄(b)*/, /*空欄(c)*/, 0);
注意: 構造体の名前は、次の問題の解答で利用する。それらしいものを付けな さい。

★問題(803) ハンドラの実行

次のコードは、割り込みの前半部分の一部である。 問題(802) で定義したハンドラを呼ぶように、空欄を埋め なさい。
irqreturn_t irq_handler(int irq, void *dev) {
    /*空欄(d)*/
    return IRQ_HANDLED;
}

Last updated: 2011/02/15 11:50:45
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>