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

					2013年02月05日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2012/2013-02-05
あるいは、次のページから手繰っていくこともできます。
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月8日の資料) のスリープの可否に着目。

◆後半部(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.6.8/include/linux/interrupt.h
 442:	struct softirq_action
 443:	{
 444:	        void    (*action)(struct softirq_action *);
 445:	};

linux-3.6.8/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.6.8/kernel/softirq.c
 207:	asmlinkage void __do_softirq(void)
 208:	{
 209:	        struct softirq_action *h;
 210:	        __u32 pending;
...
 222:	        pending = local_softirq_pending();
...
 232:	        set_softirq_pending(0);
 233:	
 234:	        local_irq_enable();
 235:	
 236:	        h = softirq_vec;
 237:	
 238:	        do {
 239:	                if (pending & 1) {
...
 246:	                        h->action(h);
...
 258:	                }
 259:	                h++;
 260:	                pending >>= 1;
 261:	        } while (pending);
...
 277:	}

◆Softirqの利用場所

Softirq の種類
linux-3.6.8/include/linux/interrupt.h

 417:	enum
 418:	{
 419:	        HI_SOFTIRQ=0,
 420:	        TIMER_SOFTIRQ,
 421:	        NET_TX_SOFTIRQ,
 422:	        NET_RX_SOFTIRQ,
 423:	        BLOCK_SOFTIRQ,
 424:	        BLOCK_IOPOLL_SOFTIRQ,
 425:	        TASKLET_SOFTIRQ,
 426:	        SCHED_SOFTIRQ,
 427:	        HRTIMER_SOFTIRQ,
 428:	        RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
 429:	
 430:	        NR_SOFTIRQS
 431:	};
Softirq の主な利用場所 普通は、tasklets を使えば十分で、自分で独自の softirqs を追加する必要は あまりない。新しい Softirq を追加したい時には、次のようにする。

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

ネットワーク処理では、送信(TX, Transmit) と受信 (RX, Receive) の2種類使う。
linux-3.6.8/net/core/dev.c
6579:	static int __init net_dev_init(void)
6580:	{
...
6642:	        open_softirq(NET_TX_SOFTIRQ, net_tx_action);
6643:	        open_softirq(NET_RX_SOFTIRQ, net_rx_action);
...
6651:	}

3005:	static void net_tx_action(struct softirq_action *h)
3006:	{
...
3061:	}

3903:	static void net_rx_action(struct softirq_action *h)
3904:	{
...
3987:	}

◆ksoftirqd(kernel soft IRQ daemon)

ksoftirqd は、Softirq や Tasklet を実行するためのカーネル内のスレッド。
$ ps alx [←]
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
...
1     0     3     2  20   0      0     0 run_ks S    ?          0:00 [ksoftirqd/0]
$ []

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

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

■Tasklet

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

Tasklet で1つの仕事は次のような、struct tasklet_struct で表現される。

linux-3.6.8/include/linux/interrupt.h

 503:	struct tasklet_struct
 504:	{
 505:	        struct tasklet_struct *next;
 506:	        unsigned long state;
 507:	        atomic_t count;
 508:	        void (*func)(unsigned long);
 509:	        unsigned long data;
 510:	};
全体として、次のようなキューに接続されている。

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.6.8/drivers/net/wireless/ath/ath9k/ath9k.h

 657:	struct ath_softc {
...
 664:	        struct tasklet_struct intr_tq;
 665:	        struct tasklet_struct bcon_tasklet;
...
 734:	};

linux-3.6.8/drivers/net/wireless/ath/ath9k/init.c
 561:	        tasklet_init(&sc->intr_tq, ath9k_tasklet, (unsigned long)sc);
 562:	        tasklet_init(&sc->bcon_tasklet, ath9k_beacon_tasklet,
 563:	                     (unsigned long)sc);

linux-3.6.8/drivers/net/wireless/ath/ath9k/main.c

 429:	irqreturn_t ath_isr(int irq, void *dev)
 430:	{
...
 476:	        ath9k_hw_getisr(ah, &status);   /* NB: clears ISR too */
...
 489:	        if (status & SCHED_INTR)
 490:	                sched = true;
...
 522:	        if (status & ATH9K_INT_SWBA)
 523:	                tasklet_schedule(&sc->bcon_tasklet);
...
 550:	        if (sched) {
...
 553:	                tasklet_schedule(&sc->intr_tq);
 554:	        }
 555:	
 556:	        return IRQ_HANDLED;
...
 559:	}

 361:	void ath9k_tasklet(unsigned long data)
 362:	{
...
 427:	}

linux-3.6.8/drivers/net/wireless/ath/ath9k/beacon.c
 310:	void ath9k_beacon_tasklet(unsigned long data)
 311:	{
...
 408:	}

■Work Queue

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

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

Work Queue のワーカ・スレッドは、カーネル・レベルのスレッドで、 割り込み処理の後半部分の処理を行うことができる。 (割り込み処理以外で使ってもよい。)

workqueue_struct、next、next、next、next

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

キューにつながれる仕事は、Tasklet の仕事とほとんど同じで、関数へのポイ ンタ func と data からなる。処理の主体が、ワーカ・スレッドと呼ばれるカー ネル・レベルのスレッドである所が違う。

$ ps alx [←]
F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
...
1     0     5     2   0 -20      0     0 worker S<   ?          0:00 [kworker/0:0H]
1     0     6     2  20   0      0     0 worker S    ?          0:00 [kworker/u:0]
1     0     7     2   0 -20      0     0 worker S<   ?          0:00 [kworker/u:0H]
1     0    27     2  20   0      0     0 worker S    ?          0:00 [kworker/u:1]
1     0   367     2   0 -20      0     0 worker S<   ?          0:00 [kworker/0:1H]
1     0   650     2   0 -20      0     0 worker S<   ?          0:00 [kworker/u:1H]
...
1     0 29461     2  20   0      0     0 worker S    ?          0:00 [kworker/0:1]
1     0 29482     2  20   0      0     0 worker S    ?          0:00 [kworker/0:2]
1     0 29503     2  20   0      0     0 worker S    ?          0:00 [kworker/0:0]
1     0 29601     2  20   0      0     0 worker S    ?          0:00 [kworker/0:3]
$ []
汎用の Work Queue のワーカ・スレッドの他に、専用のワーカ・スレッドを作 ることもできる。

◆work_struct構造体

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

◆alloc_workqueue()

専用のワーカ・スレッドを作りたい時には、次のような関数を使う。

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

■課題8 割り込みの後半部、Softirq、Tasklet、Work Queue

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

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

★問題(802) Taskletの初期化

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

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

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

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

Last updated: 2013/02/11 14:10:20
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>