[KEK Online Group] → [Nakayoshi's Page] → [about Linux Interrupts]
Linux カーネル2.4 の割り込み処理について(for x86)
仲吉一男
Last Modified: Mar. 15, 2001
- 割り込み(Interrupts)とは
割り込みは、「ハードウエア割り込み」と「例外割り込み」の2つに分類できる。
「ハードウエア割り込み」は、CPU 以外の周辺機器からの信号により発生し、「例外割り込み」
は、「トラップ(ソフトウエア割り込み)」や「フォルト」、「アボート」に分類される。
ここでは、ハードウエア割り込みに焦点をあてて調べる。
- ハードウエア割り込みの種類
- マスク可能割り込み
外部装置からプロセッサへ伝えられる割り込み信号はマスク可能割り込みである。それらは
eflags レジスタの IF フラグをクリアすることにより、マスク可能である。
- マスク不可割り込み
ハードウエアの障害等の致命的なエラーによりマスク不可割り込みが起こる。これは IF フラグを
クリアしてもマスクできない。
- 割り込みベクタ
割り込みは 0〜255 の番号で識別されベクタと呼ばれる。マスク不可割り込みと
例外はベクターが固定されている。マスク可能割り込みは、割り込みコントローラ
のプログラミングで変更が可能である。Linux では以下のベクタを使用している。
- 0〜31のベクタは例外およびマスク不可割り込みが使う。
- 32〜47のベクタは IRQ により割り込みを起こすマスク可能割り込みに割り当てられている。
IRQ VECTOR DEVICE COMMENT
0 32 Timer Never use
1 33 Keybord Never use
2 34 PIC cascading Never use
3 35 Serial Port#2 known ISA uses
4 36 Serial Port#1 known ISA uses
5 37 (Parallel Port#2)
6 38 Floppy Disk known ISA uses
7 39 Parallel Port#1 known ISA uses
8 40 RTC(Real-Time Clock)
9 41 -
10 42 -
11 43 -
12 44 PS/2 mouse known ISA uses
13 45 fpu Avoid using
14 46 EIDE disk cont. #1 Avoid using
15 47 EIDE disk cont. #2 Avoid using
48〜255 のベクタはソフトウエア割り込みで使用。
初期化
start_kernel()のinit_IRQ()で IDT の Interruput Gate Descriptor
の初期化を行なう。ここで割り込みベクターn+32(IRQn)と対応する
割り込みハンドラ IRQn_interrupt()のアドレスががIDT に格納される。
2.4.0(init/main.c)
asmlinkage void __init start_kernel(void)
{
char * command_line;
unsigned long mempages;
extern char saved_command_line[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
printk(linux_banner);
setup_arch(&command_line);
printk("Kernel command line: %s\n", saved_command_line);
parse_options(command_line);
trap_init();
init_IRQ(); <===
sched_init();
time_init();
softirq_init();
...
---------------------------------------------------------------------
2.4.0(arch/i386/kernel/i8259.c)
void __init init_IRQ(void)
{
int i;
...
/*
* Cover the whole vector space, no vector can escape
* us. (some of these will be overridden and become
* 'special' SMP interrupts)
*/
for (i = 0; i < NR_IRQS; i++) {
int vector = FIRST_EXTERNAL_VECTOR + i;
if (vector != SYSCALL_VECTOR)
set_intr_gate(vector, interrupt[i]);
}
...
---------------------------------------------------------------------
2.4.0(arch/i386/kernel/traps.c)
...
#define _set_gate(gate_addr,type,dpl,addr) \
do { \
int __d0, __d1; \
__asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
"movw %4,%%dx\n\t" \
"movl %%eax,%0\n\t" \
"movl %%edx,%1" \
:"=m" (*((long *) (gate_addr))), \
"=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
:"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \
} while (0)
...
void set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr);
}
...
Interrupt Descriptor Table(IDT)
IDT は、割り込みや例外とその関連するハンドラのアドレスの対応テーブルである。IDT はゲートディスクリプタを並べたもので、その各ゲートは格納されている
順序で割り込み番号に対応付けられている。割り込みが発生すると割り込み番号に
対応する位置にあるゲートが呼び出される。x86系では「割り込みゲート」「トラップゲート」「タスクゲート」の3種類があるが Linuxでは、「タスクゲート」は使用しない。
IRQ Data Structures
2.2.14 (arch/i386/kernel/irq.h)
typedef struct {
unsigned int status; /* IRQ status-IRQ_INPROGRESS,IRQ_DISABLED */
struct hw_interrupt_type *handler;/* handle/enable/disable functions */
struct irqaction *action; /* IRQ action list */
unsigned int depth; /* Disable depth for nested irq disables */
unsigned int unused[4];
} irq_desc_t;
---------------------------------------------------------------------
2.4.0 (include/linux/irq.h)
...
typedef struct {
unsigned int status; /* IRQ status */
hw_irq_controller *handler;
struct irqaction *action; /* IRQ action list */
unsigned int depth; /* nested irq disables */
spinlock_t lock;
} ____cacheline_aligned irq_desc_t;
extern irq_desc_t irq_desc [NR_IRQS];
...
struct hw_interrupt_type {
const char * typename;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq);
void (*ack)(unsigned int irq);
void (*end)(unsigned int irq);
void (*set_affinity)(unsigned int irq, unsigned long mask);
};
typedef struct hw_interrupt_type hw_irq_controller;
...
---------------------------------------------------------------------
2.4.0 (arch/i386/kernel/i8259.c)
static struct hw_interrupt_type i8259A_irq_type = {
"XT-PIC",
startup_8259A_irq,
shutdown_8259A_irq,
enable_8259A_irq,
disable_8259A_irq,
mask_and_ack_8259A,
end_8259A_irq,
NULL
};
BUILD_IRQ, BUILD_COMMON_IRRQ マクロ
2.4.0 (include/asm/hw_irq.h)
#define BUILD_IRQ(nr) \
asmlinkage void IRQ_NAME(nr); \
__asm__( \
"\n"__ALIGN_STR"\n" \
SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
"pushl $"#nr"-256\n\t" \
"jmp common_interrupt");
#define BUILD_COMMON_IRQ() \
__asm__( \
"\n" __ALIGN_STR"\n" \
"common_interrupt:\n\t" \
SAVE_ALL \
"call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
"jmp ret_from_intr\n");
SAVE_ALL マクロ
2.4.0 (include/asm/hw_irq.h)
...
#define SAVE_ALL \
"cld\n\t" \
"pushl %es\n\t" \
"pushl %ds\n\t" \
"pushl %eax\n\t" \
"pushl %ebp\n\t" \
"pushl %edi\n\t" \
"pushl %esi\n\t" \
"pushl %edx\n\t" \
"pushl %ecx\n\t" \
"pushl %ebx\n\t" \
"movl $" STR(__KERNEL_DS) ",%edx\n\t" \
"movl %edx,%ds\n\t" \
"movl %edx,%es\n\t"
...
i8259.c
2.4.0 (arch/i386/kernel/i8259.c)
BUILD_COMMON_IRQ( )
#define BI(x,y) \
BUILD_IRQ(x##y)
#define BUILD_16_IRQS(x) \
BI(x,0) BI(x,1) BI(x,2) BI(x,3) \
BI(x,4) BI(x,5) BI(x,6) BI(x,7) \
BI(x,8) BI(x,9) BI(x,a) BI(x,b) \
BI(x,c) BI(x,d) BI(x,e) BI(x,f)
...
#define IRQ(x,y) \
IRQ##x##y##_interrupt
#define IRQLIST_16(x) \
IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
void (*interrupt[NR_IRQS])(void) = {
IRQLIST_16(0x0),
#ifdef CONFIG_X86_IO_APIC
IRQLIST_16(0x1), IRQLIST_16(0x2), IRQLIST_16(0x3),
IRQLIST_16(0x4), IRQLIST_16(0x5), IRQLIST_16(0x6), IRQLIST_16(0x7),
IRQLIST_16(0x8), IRQLIST_16(0x9), IRQLIST_16(0xa), IRQLIST_16(0xb),
IRQLIST_16(0xc), IRQLIST_16(0xd)
#endif
};
...
---------------------------------------------------------------------
do_IRQ( )
2.4.0 (arch/i386/kernel/irq.c)
/*
* do_IRQ handles all normal device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
{
...
int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code */
int cpu = smp_processor_id();
irq_desc_t *desc = irq_desc + irq;
struct irqaction * action;
unsigned int status;
kstat.irqs[cpu][irq]++;
spin_lock(&desc->lock);
desc->handler->ack(irq); /* mask_and_ack_8259A() */
/*
REPLAY is when Linux resends an IRQ that was dropped earlier
WAITING is used by probe to mark irqs that are being tested
*/
status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
status |= IRQ_PENDING; /* we _want_ to handle it */
/*
* If the IRQ is disabled for whatever reason, we cannot
* use the action we have.
*/
action = NULL;
if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
action = desc->action;
status &= ~IRQ_PENDING; /* we commit to handling */
status |= IRQ_INPROGRESS; /* we are handling it */
}
desc->status = status;
/*
* If there is no IRQ handler or it was disabled, exit early.
Since we set PENDING, if another processor is handling
a different instance of this same irq, the other processor
will take care of it.
*/
if (!action)
goto out;
...
for (;;) {
spin_unlock(&desc->lock);
handle_IRQ_event(irq, ®s, action);
spin_lock(&desc->lock);
if (!(desc->status & IRQ_PENDING))
break;
desc->status &= ~IRQ_PENDING;
}
desc->status &= ~IRQ_INPROGRESS;
out:
/*
* The ->end() handler has to deal with interrupts which got
* disabled while the handler was running.
*/
desc->handler->end(irq); /* enable_8259A_irq(irq) */
spin_unlock(&desc->lock);
if (softirq_active(cpu) & softirq_mask(cpu))
do_softirq();
return 1;
}
---------------------------------------------------------------------
int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction *
action)
{
int status;
int cpu = smp_processor_id();
irq_enter(cpu, irq);
status = 1; /* Force the "do bottom halves" bit */
if (!(action->flags & SA_INTERRUPT))
__sti();
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli();
irq_exit(cpu, irq);
return status;
}
Linuxにおける割り込み処理の概要
Linuxでは割り込みの応答性を高めるため、その処理を2段階に分けている。第1段階では
割り込みコントローラレベルでの割り込みの制御(割り込み許可/禁止)を行なう。
第2段階では、第1段階の残りの処理をソフトウエア割り込みハンドラで行なう。カーネル
2.4 からは BH ハンドラの改良版であるソフトウエア割り込みハンドラが実装されている。
これは SMP におけるパフォーマンスを改善するためのもので、シングルプロセッサでは
従来のものと変わらない。
- 割り込み制御
- CPUレベルでの割り込み制御
割り込み禁止/解除のため cli(), sti() がある。cli()により CPU は全ての
割り込みを禁止できる。
- 割り込みコントローラレベルでの割り込み制御
IRQ 別に割り込みの禁止/許可することができる。割り込み禁止状態で発生した
割り込みはそれが許可されるまで保留となる。そして許可されると要求が受け付け
られる。
- 割り込みハンドリング
割り込みハンドリング中には、時間のかかる処理は避けるべきである。割り込みハンドラが
実行中は、その IRQ が発生しても無視される。
- 第1段階(割り込みハンドラ)
- デバイスで割り込みイベント発生
- デバイスは Programmable Interrupt Controller(PIC) に割り込み要求
- PIC は IRQ ラインに信号が発生したら、それをベクタに変換する。
- PICは CPU がベクタを読めるように自身の I/Oポートにそれをを保存する。
- PICは INTR ラインに信号を送る。(CPU に対する割り込み発生)
- CPUが割り込み受けつけ
- CPUは割り込み発生前の処理を中断し、割り込みエントリ関数 do_IRQ を呼ぶ
- CPUは発生したIRQのマスクを行なう
- CPUは割り込みコントローラに対しACKを返し、同じ IRQ に対する割り込みを禁止する
- PICはINTR ラインの信号をクリアし、次の割り込みを待つ。
- CPUはhandle_IRQ_event をよぶ
- CPUは割り込みネスト可のハンドラなら CPU への割り込み許可
- CPUは発生した IRQ に登録されている全てのハンドラをよぶ → 必要なら第2段階へ
- CPUへの割り込みを禁止にする
- 発生したIRQに対する割り込み発生を許可
- 割り込み発生前の処理へ戻る
- 第2段階(ソフトウエア割り込みハンドラ)
- (CPU)ソフトウエア割り込みハンドラ起動
Linuxのソフトウエア割り込み
Linux には 2.4 から導入された「ソフトウエア割り込みハンドラ」、BOTTOM HALF ハンドラの
拡張である「タスクキュー」、もっとも古い「BOTTOM HALF ハンドラ」がある。それらの
概要とバージョンによる違いを以下で述べる。
- ソフトウエア割り込みハンドラ
2.4 ではマルチプロセッサに対応し BH ハンドラーに代わるものとしてソフトウエア割り込みハンドラが
実装された。ソフトウエア割り込みハンドラは, 旧BHハンドラと異なりマルチプロセッサ環境で
複数のCPU上でマルチスレッドで動作できるため性能の向上が期待できる。
2.4 では TCP/IP プロトコル処理等が BH ハンドラからソフトウエア割り込みハンドラに
書き換えられている。
linux/interrupt.h に以下のような4種類のソフトウエア割り込みレベルが定義されている。
2.4.0
enum
{
HI_SOFTIRQ=0, /* 汎用的なハンドラ登録のメカニズム。2.2 タスクキュー機能の改良版。*/
NET_TX_SOFTIRQ, /* TCP/IP プロトコルスタックの送信処理 */
NET_RX_SOFTIRQ, /* TCP/IP プロトコルスタックの受信処理 */
TASKLET_SOFTIRQ /* 構造は TASKLET_SOFTIRQ と同じ。TASKLET_SOFTIRQ より優先。BH ハンドラのみ利用している */
};
以下のような構造体が用意されている。
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
関連する以下のような関数がある。
open_softirq(): /* 指定したソフトウエア割り込みレベルにハンドラを登録する */
__cpu_raise_softirq():/* 指定したレベルのソフトウエア割り込みを発生要求する */
do_softirq(): /* ソフトウエア割り込みレベルに対応したハンドラを起動する */
tasklet_schedule(): /* TASKLET_SOFTIRQ レベルにハンドラを登録する */
tasklet_hi_schedule():/* HI_SOFTIRQ レベルにハンドラを登録する */
tasklet_action(): /* TASKLET_SOFTIRQ レベルに登録されたハンドラ群を全て実行する */
tasklet_hi_action(): /* HI_SOFTIRQ レベルに登録されたハンドラ群を全て実行する */
タスクキュー(タスク待ち列)
BHハンドラは32種類しかなく、ほとんど予約済みなのでタスクキューという機能が
拡張されている。タスクキューの実体は、登録された関数のリンクリストである。
以下、linux/tqueue.h を示す。
2.4.0
struct tq_struct {
struct list_head list; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
typedef struct list_head task_queue;
#define DECLARE_TASK_QUEUE(q) LIST_HEAD(q)
extern task_queue tq_timer, tq_immediate, tq_disk;
2.2.14
struct tq_struct {
struct tq_struct *next; /* linked list of active bh's */
unsigned long sync; /* must be initialized to zero */
void (*routine)(void *); /* function to call */
void *data; /* argument to function */
};
typedef struct tq_struct * task_queue;
#define DECLARE_TASK_QUEUE(q) task_queue q = NULL
extern task_queue tq_timer, tq_immediate, tq_scheduler, tq_disk;
タスクキューには以下の種類がある。
2.2.14
- tq_immediate:ここに登録されている処理は、カーネルの処理がなくなったら
即実行される。
- tq_timer:ここに登録されている処理は、タイマ処理のタイミングで処理される。
- tq_scheduler:ここに登録されている処理は、プロセスディスパッチのタイミングで
スケジューラにより起動される
- tq_disk:ここに登録されている処理は、ファイルシステムが適当なタイミングで
起動する
2.4.0
- tq_immediate
- tq_timer
- tq_disk
BOTTOM HALF(BH)ハンドラ
BHハンドラとは、割り込み処理の後半部分を処理するためのもの。
BHハンドラ実行のためのソフトウエア割り込みハンドラは、BHハンドラ登録テーブル
bh_base[] を参照そ必要なBHハンドラを呼び出す。bh_base[] は固定長で 32個の
ハンドラの登録が可能。バージョン 1.0 のカーネルには BH はあったが、タスク待ち
列はまだなかった。2.4 の BH ハンドラは 2.2 以前のものとの互換性のため残されて
いる。また現時点では、ほとんどのドライバは、まだ古い BH ハンドラのメカニズムを
使用している。
linux/interrupt.h に以下の記述がある。
2.4.0 2.2.14
enum { enum {
TIMER_BH = 0, TIMER_BH = 0,
TQUEUE_BH, CONSOLE_BH,
DIGI_BH, TQUEUE_BH,
SERIAL_BH, DIGI_BH,
RISCOM8_BH, SERIAL_BH,
SPECIALIX_BH, RISCOM8_BH,
AURORA_BH, SPECIALIX_BH,
ESP_BH, AURORA_BH,
SCSI_BH, ESP_BH,
IMMEDIATE_BH, NET_BH,
CYCLADES_BH, SCSI_BH,
CM206_BH, IMMEDIATE_BH,
JS_BH, KEYBOARD_BH,
MACSERIAL_BH, CYCLADES_BH,
ISICOM_BH CM206_BH,
}; JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
BHハンドラで使用する関数
2.4.0
/* mark_bh():指定した番号のBHハンドラの起動要求を行なう。2.4 ではBH ハンドラ関数は
HI_SOFTIRQ レベルのソフトウエア割り込みとして登録される*/
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
/* bh_action():BH ハンドラを実行する. 2.2 以前との互換性のため BH ハンドラ実行中は
他の BH ハンドラが動作できないように禁止フラグを立てる */
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
/* init_bh():指定した番号のBHハンドラを登録する */
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
/* remove_bh():指定した番号のBHハンドラを登録を削除する */
void remove_bh(int nr)
{
tasklet_kill(bh_task_vec+nr);
bh_base[nr] = NULL;
}
2.2.14
/* mark_bh():指定した番号のBHハンドラの起動要求を行なう */
extern inline void mark_bh(int nr)
{
set_bit(nr, &bh_active);
}
/* init_bh():指定した番号のBHハンドラを登録する */
extern inline void init_bh(int nr, void (*routine)(void))
{
unsigned long flags;
bh_base[nr] = routine;
atomic_set(&bh_mask_count[nr], 0);
spin_lock_irqsave(&i386_bh_lock, flags);
bh_mask |= 1 << nr;
spin_unlock_irqrestore(&i386_bh_lock, flags);
}
/* remove_bh():指定した番号のBHハンドラを登録を削除する */
extern inline void remove_bh(int nr)
{
unsigned long flags;
spin_lock_irqsave(&i386_bh_lock, flags);
bh_mask &= ~(1 << nr);
spin_unlock_irqrestore(&i386_bh_lock, flags);
synchronize_bh();
bh_base[nr] = NULL;
}
注: この文章に対するコメント等は kazuo.nakayoshi@kek.jp までお願いします。
参考文献
- 高橋浩和,三好和人 「Linux カーネル2.4の設計と実装(2)」, Linux Japan 2000年12月号,
五橋研究所
- Bovet and Cesati Understanding the Linunx Kernel, O'REILLY.
- 蒲地 輝尚 「はじめて読む486」, アスキー出版局
- Alessandro Rubini 著, 山崎康宏,邦子共訳「Linux デバイスドライバ」,
オライリージャパン
- Michael Beck, et al. 著, (株)クイック訳「Linux カーネルインターナル」,
ピアソン・エデュケーションジャパン
- 安 芳次,「割り込みとは」,
Linuxデバイスドライバの書き方