上次学了如何写GDT,那么IDT其实和GDT差不多,所以我就照葫芦画瓢,前半部分比较顺利,但写到后半部分就不会了,所以又学习了james先生的教程。

定义IDT结构

和GDT一样,先是定义IDT的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
include/idt.h
*/
struct idt_descriptor{
u16 offset_low;
u16 seg;
u8 zero;
u8 flags;
u16 offset_high;
}__attribute__((packed));

struct idtr_struct{
u16 limit;
u32 base;
}__attribute__((packed));

学过中断都知道,0 ~ 19号属于CPU,20 ~ 31是CPU保留的。用户只能使用32~255的中断号。

定义中断处理函数。

IDT初始化函数

init_idt 用于初始化。
idt_set_descriptor 用于安装中断描述符。

安装cpu的中断。

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
63
64
65
/*
idt/idt.c
*/
#include "idt.h"
#include "string.h"

extern void idt_flush(u32);
struct idt_descriptor idt[256];
struct idtr_struct idtr;


void idt_set_descriptor(int num,u32 offset,u16 seg,u8 flags);


void init_idt(){
idtr.limit = sizeof(struct idt_descriptor) * 256 - 1;
idtr.base = (u32)&idt;

bzero(&idt,sizeof(struct idt_descriptor) *256);

idt_set_descriptor( 0, (u32)isr0, 0x08, 0x8E);
idt_set_descriptor( 1, (u32)isr1, 0x08, 0x8E);
idt_set_descriptor( 2, (u32)isr2, 0x08, 0x8E);
idt_set_descriptor( 3, (u32)isr3, 0x08, 0x8E);
idt_set_descriptor( 4, (u32)isr4, 0x08, 0x8E);
idt_set_descriptor( 5, (u32)isr5, 0x08, 0x8E);
idt_set_descriptor( 6, (u32)isr6, 0x08, 0x8E);
idt_set_descriptor( 7, (u32)isr7, 0x08, 0x8E);
idt_set_descriptor( 8, (u32)isr8, 0x08, 0x8E);
idt_set_descriptor( 9, (u32)isr9, 0x08, 0x8E);
idt_set_descriptor(10, (u32)isr10, 0x08, 0x8E);
idt_set_descriptor(11, (u32)isr11, 0x08, 0x8E);
idt_set_descriptor(12, (u32)isr12, 0x08, 0x8E);
idt_set_descriptor(13, (u32)isr13, 0x08, 0x8E);
idt_set_descriptor(14, (u32)isr14, 0x08, 0x8E);
idt_set_descriptor(15, (u32)isr15, 0x08, 0x8E);
idt_set_descriptor(16, (u32)isr16, 0x08, 0x8E);
idt_set_descriptor(17, (u32)isr17, 0x08, 0x8E);
idt_set_descriptor(18, (u32)isr18, 0x08, 0x8E);
idt_set_descriptor(19, (u32)isr19, 0x08, 0x8E);
idt_set_descriptor(20, (u32)isr20, 0x08, 0x8E);
idt_set_descriptor(21, (u32)isr21, 0x08, 0x8E);
idt_set_descriptor(22, (u32)isr22, 0x08, 0x8E);
idt_set_descriptor(23, (u32)isr23, 0x08, 0x8E);
idt_set_descriptor(24, (u32)isr24, 0x08, 0x8E);
idt_set_descriptor(25, (u32)isr25, 0x08, 0x8E);
idt_set_descriptor(26, (u32)isr26, 0x08, 0x8E);
idt_set_descriptor(27, (u32)isr27, 0x08, 0x8E);
idt_set_descriptor(28, (u32)isr28, 0x08, 0x8E);
idt_set_descriptor(29, (u32)isr29, 0x08, 0x8E);
idt_set_descriptor(30, (u32)isr30, 0x08, 0x8E);
idt_set_descriptor(31, (u32)isr31, 0x08, 0x8E);

idt_flush((u32)&idtr);
}


void idt_set_descriptor(int num,u32 offset,u16 seg,u8 flags){
idt[num].offset_low = offset & 0xFFFF;
idt[num].offset_high = (offset >> 16 ) &0xFFFF;

idt[num].seg = seg;
idt[num].zero = 0;
idt[num].flags = flags;
}

通用中断处理函数

前面的代码都是之前写过的,但是写到中断处理函数这里,我只好学习James先生的代码。

首先是安装idtr:

1
2
3
4
5
6
7
8
9
/*
idt/idt_s.s
*/
[global idt_flush]

idt_flush:
mov eax,[esp+4]
lidt [eax]
ret

然后是定义每个中断号的处理函数,因为有些中断有错误代码,所以要分成两类,ISR_NOERRCODE 和 ISR_ERRCODE。

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
/*
idt/idt_s.s
*/
%macro ISR_NOERRCODE 1 ; define a macro, taking one parameter
[GLOBAL isr%1] ; %1 accesses the first parameter.
isr%1:
cli
push byte 0
push byte %1
jmp isr_common_stub
%endmacro

%macro ISR_ERRCODE 1
[GLOBAL isr%1]
isr%1:
cli
push byte %1
jmp isr_common_stub
%endmacro


ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6
ISR_NOERRCODE 7
ISR_ERRCODE 8
ISR_NOERRCODE 9
ISR_ERRCODE 10
ISR_ERRCODE 11
ISR_ERRCODE 12
ISR_ERRCODE 13
ISR_ERRCODE 14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_ERRCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19


ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_NOERRCODE 30
ISR_NOERRCODE 31

ISR_NOERRCODE 255

James先生的代码使用了宏,分为有错误代码和无错误代码,有错误代码的会将错误代码push到栈里,它们最后都使用同一个处理程序isr_common_stub。

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
/*
idt/idt_s.s
*/

[EXTERN isr_handler]

; This is our common ISR stub. It saves the processor state, sets
; up for kernel mode segments, calls the C-level fault handler,
; and finally restores the stack frame.
isr_common_stub:
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax

mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor

mov ax, 0x10 ; load the kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax

call isr_handler

pop eax ; reload the original data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax

popa ; Pops edi,esi,ebp...
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP

这段代码的意思大概就是,先保存现场,然后加载内核的数据段,然后调用isr_handler,这个函数就是中断处理函数,可以根据情况自由实现。

最后是恢复原来的数据段。之所以有保存和恢复这个步骤,是为了兼容以后在用户段发生中断的情况。

最后是isr_handler:

它的作用就是打印当前的中断号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
idt/isr.c
*/
typedef struct registers
{
u32 ds; // Data segment selector
u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha.
u32 int_no, err_code; // Interrupt number and error code (if applicable)
u32 eip, cs, eflags, useresp, ss; // Pushed by the processor automatically.
} registers_t;

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

}

由于我需要一些操作界面和字符串的辅助函数,我自己写了一部分,比如strlen、memcpy等,也从其它项目中copy了一部分,比如printk,我暂时先把它当做工具函数,拿过来解决眼前的问题,因为打印各种字符串并不是内核的重点,以后再去学习它的实现也不迟。

end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
idt/isr.c
*/

#include "gdt.h"
#include "console.h"
#include "idt.h"


int kmain(void){

console_clear();

init_gdt();
init_idt();

printk("Hello,kernel;\n");

asm volatile ("int $0x3"); //调用中断
return;


}

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