安装好了IDT之后,接下来就来实现中断请求。

外设中断由中断控制芯片8259A进行控制,想要了解其中的原理可以看这篇文章:保护模式下的 8259A芯片编程 及中断处理探究

初始化8259A

在init_idt()函数开头加入初始化代码,用来初始化设置8259A芯片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
idt/idt.c
*/
void init_idt(){
outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0xA1, 0x02);
outb(0x21, 0x01);
outb(0xA1, 0x01);
outb(0x21, 0x0);
outb(0xA1, 0x0);

······

//注意安装irq的描述符
idt_set_descriptor(32, (u32)irq0, 0x08, 0x8E);
idt_set_descriptor(33, (u32)irq1, 0x08, 0x8E);
idt_set_descriptor(34, (u32)irq2, 0x08, 0x8E);
······
idt_set_descriptor(47, (u32)irq15, 0x08, 0x8E);

定义IRQ处理函数

声明IRQ函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
include/idt.h
*/
void irq0();
void irq1();
void irq2();
void irq3();
void irq4();
void irq5();
void irq6();
void irq7();
void irq8();
void irq9();
void irq10();
void irq11();
void irq12();
void irq13();
void irq14();
void irq15();

和安装isr差不多,使用了宏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
idt/idt_s.s
*/
%macro IRQ 2
[GLOBAL irq%1]
irq%1:
cli
push 0
push %2
jmp irq_common_stub
%endmacro


IRQ 0, 32
IRQ 1, 33
IRQ 2, 34
IRQ 3, 35
IRQ 4, 36
IRQ 5, 37
IRQ 6, 38
IRQ 7, 39
IRQ 8, 40
IRQ 9, 41
IRQ 10, 42
IRQ 11, 43
IRQ 12, 44
IRQ 13, 45
IRQ 14, 46
IRQ 15, 47


[GLOBAL irq_common_stub]
[EXTERN irq_handler]

irq_common_stub:
pusha ; pushes edi, esi, ebp, esp, ebx, edx, ecx, eax

mov ax, ds
push eax ; 保存数据段描述符

mov ax, 0x10 ; 加载内核数据段描述符
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax

push esp
call irq_handler
add esp, 4

pop ebx ; 恢复原来的数据段描述符
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
mov ss, bx

popa ; Pops edi,esi,ebp...
add esp, 8 ; 清理压栈的 错误代码 和 ISR 编号
iret ; 出栈 CS, EIP, EFLAGS, SS, ESP
.end:

IRQ处理函数实现

这段代码当时我不太理解,看了一天给我整崩溃了。

irq_handler的功能就是如果请求号>40表示是从片控制的,它是一个EOI消息,会将从片重置,目的就是清空8259A中的ISR的位。

主片每次都会重置,所以一定会执行 outb(0x20,0x20);

最后触发具体的中断处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
idt/isr.c
*/
isr_t interrupt_handlers[256];

void isr_handler(registers_t *regs){
printk("recieved interrupt: %d\n",regs->int_no);

}

void irq_handler(registers_t *regs){
if(regs->int_no >= 40){
outb(0xA0,0x20);
}
outb(0x20,0x20);

if(interrupt_handlers[regs->int_no] != 0){
interrupt_handlers[regs->int_no](regs);
}

}


void register_interrupt_handler(u8 n,isr_t handler){

interrupt_handlers[n] = handler;
}

register_interrupt_handler 用来安装中断号对应的处理函数。

PIT 时钟中断

PIT就是 programmable interval timer的缩写,它连接到了IRQ0。

原理请参考:Programmable Interval Timer

init_timer的作用就是,使IRQ0对应timer_callback这个处理函数,然后设置好PIT的频率和工作模式。

之后内核会根据频率,调用timer_callback,每次调用一次变量i就+1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
timer/timer.c
*/
u32 i = 0;

void timer_callback(registers_t regs){
i++;
printk("Tick: %d\n",i);
}



void init_timer(u32 frequency){

register_interrupt_handler(IRQ0,timer_callback);
printk("222");

u32 divisor = 1193180 / frequency;

outb(0x43,0x36);
u8 low = (u8)(divisor & 0xFF);
u8 high = (u8)((divisor >> 8) & 0xFF);

outb(0x40,low);
outb(0x40,high);

}

运行效果:

参考资料:
x86架构操作系统内核的实现
JamesM’s kernel development tutorials