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

					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/

■今日の大事な話

■割り込み、後半部

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

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

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

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

◆gfp_t gfp_mask

kmalloc() 等で使われる gfp_t gfp_mask (1月17日の資料) のスリープの可否に着目。

◆後半部(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() の実行

関数 do_softirq() を呼び出し、実行する「適当な時」とは、次の通りである。

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

Softirqの種類は、コンパイル時に静的に決まる。softirq_action 構造体の配 列で、ハンドラが保持される。
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;

◆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 があれば実行する。
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:	}

◆Softirqの利用場所

Softirq の種類
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 の主な利用場所 普通は、tasklets を使えば十分で、自分で独自の softirqs を追加する必要は あまりない。新しい Softirq を追加したい時には、次のようにする。

◆ネットワーク処理での Softirq の利用例

ネットワーク処理では、送信(TX, Transmit) と受信 (RX, Receive) の2種類使う。
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:	}

◆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]
...
$ []

問題: Softirq を優先すべきか、普通のユーザ・プロセスを優先すべきか。

解決策: ksoftirqd 注意: do_softirq() は、 ksoftirqd以外 で実行されるのが普通。

■Tasklet

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

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_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の利用例

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

■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 Queue のワーカ・スレッドの他に、専用のワーカ・スレッドを作 ることもできる。

◆work_struct構造体

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

◆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の初期化

Tasklet を使って次の関数 f() を、割り込み処理の後半で呼び出したい。
void f(void) {
   省略;
}
これを実現するために、どのような Tasklet のハンドラと初期化コードを書け ばよいか。以下の空欄を埋めなさい。
void tasklet_handler(unsigned long data) { /* ハンドラ */
    /*空欄(a)*/
    その他の仕事;
}

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

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

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

Last updated: 2012/02/13 22:21:25
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>