7.添加中断描述符表

中断执行的流程:

  1. 当某个中断发生时,典型的处理方式就是CPU会 打断当前的任务,保留当前的执行现场
  2. 再转移到该中断事先安排好的中断处理函数去执行。
  3. 在中断处理函数执行结束之后再恢复中断之前的执行现场,去执行之前的任务。

中断描述符表(IDT,interrupt descriptor table) 与gdt 类似的 也有一个中断描述符表寄存器 记录这个表的起始地址

image-20240512141659651

include/idt.h 定义

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
#ifndef INCLUDE_IDT_H_
#define INCLUDE_IDT_H_

#include "types.h"

// init
void init_idt();

// 中断
typedef
struct idt_entry_t {
uint16_t base_lo; // 中断处理函数地址的 15 - 0 位
uint16_t sel; // 目标代码段描述符选择子
uint8_t always0; // 置0
uint8_t flags; // 标志
uint16_t base_hi; // 函数地址31 - 16 位
}__attribute__((packed)) idt_entry_t;

// IDTR
typedef
struct idt_ptr_t
{
uint16_t limit; // 限长
uint32_t base; // 基址
}__attribute__((packed)) idt_ptr_t;

#endif // include

按照Intel的规定,0~19号中断属于CPU所有62,而且第 20-31号中断也被Intel保留,所以从32~255号才属于用户自定义中断。虽说是”用户自定 义”,其实在x86上有些中断按照习惯还是给予了固定的设备。比如32号是timer中断,33 号是键盘中断等等。

虽然cpu在中断产生的时候自动保存了部分的执行现场,但是依然有很多寄存器需要我们自己保存和恢复

include/idt.h

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
66
67
typedef
struct pt_regs_t{
uint32_t ds; // 用于保护用户的数据段描述法
uint32_t edi; // 从 edi 到 eax 由pusha 指令压入
uint32_t esi;
uint32_t ebp;
uint32_t esp;
uint32_t ebx;
uint32_t edx;
uint32_t ecx;
uint32_t eax;
uint32_t int_no; // 中断号
uint32_t err_code; // 错误代码有中断错误代码的中断会由
uint32_t eip; // 以下处理器自动压入
uint32_t cs;
uint32_t eflags;
uint32_t useresp;
uint32_t ss;
}pt_regs;

// 定义中断处理函数指针
typedef void (*interrupt_handler_t) (pt_regs *);

// 中断处理函数
void register_interrupt_handler(uint8_t n, interrupt_handler_t h);

// 调用中断处理函数
void isr_handler(pt_regs *regs);

// 声明0 - 19 属于cpu的异常中断
void isr0(); // 0 #DE 除 0 异常
void isr1(); // 1 #DB 调试异常
void isr2(); // 2 NMI
void isr3(); // 3 BP 断点异常
void isr4(); // 4 #OF 溢出
void isr5(); // 5 #BR 对数组的引用超出边界
void isr6(); // 6 #UD 无效或未定义的操作码
void isr7(); // 7 #NM 设备不可用无数学协处理器()
void isr8(); // 8 #DF 双重故障有错误代码()
void isr9(); // 9 协处理器跨段操作
void isr10(); // 10 #TS 无效TSS有错误代码()
void isr11(); // 11 #NP 段不存在有错误代码()
void isr12(); // 12 #SS 栈错误有错误代码()
void isr13(); // 13 #GP 常规保护有错误代码()
void isr14(); // 14 #PF 页故障有错误代码()
void isr15(); // 15 CPU 保留
void isr16(); // 16 #MF 浮点处理单元错误
void isr17(); // 17 #AC 对齐检查
void isr18(); // 18 #MC 机器检查
void isr19(); // 19 #XM SIMD单指令多数据()浮点异常

// 20 ~ 31 Intel 保留
void isr20();
void isr21();
void isr22();
void isr23();
void isr24();
void isr25();
void isr26();
void isr27();
void isr28();
void isr29();
void isr30();
void isr31();

// 32 ~ 255 用户自定义异常
void isr255();

把原本的中断处理函数逻辑上拆解为三部分,第一部分是一致 的现场保护操作;第二部分是每个中断特有的处理逻辑;第三部分又是一致的现场恢复。

实际上拆分成了四段。

idt/idt_s.s

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
66
67
68
69
70
71
72
; --------------------------------------------------
; 将 IDT 地址 载入 IDTR
;
; hurley 2013/11/07
;
;---------------------------------------------------

[GLOBAL idt_flush]
idt_flush:
mov eax, [esp+4] ; 参数存入 eax 寄存器
lidt [eax] ; 加载到 IDTR
ret
.end:

; 定义两个构造中断处理函数的宏(有的中断有错误代码,有的没有)
; 用于没有错误代码的中断
%macro ISR_NOERRCODE 1
[GLOBAL isr%1]
isr%1:
cli ; 首先关闭中断
push 0 ; push 无效的中断错误代码(起到占位作用,便于所有isr函数统一清栈)
push %1 ; push 中断号
jmp isr_common_stub
%endmacro

; 用于有错误代码的中断
%macro ISR_ERRCODE 1
[GLOBAL isr%1]
isr%1:
cli ; 关闭中断
push %1 ; push 中断号
jmp isr_common_stub
%endmacro

; 定义中断处理函数
ISR_NOERRCODE 0 ; 0 #DE 除 0 异常
ISR_NOERRCODE 1 ; 1 #DB 调试异常
ISR_NOERRCODE 2 ; 2 NMI
ISR_NOERRCODE 3 ; 3 BP 断点异常
ISR_NOERRCODE 4 ; 4 #OF 溢出
ISR_NOERRCODE 5 ; 5 #BR 对数组的引用超出边界
ISR_NOERRCODE 6 ; 6 #UD 无效或未定义的操作码
ISR_NOERRCODE 7 ; 7 #NM 设备不可用(无数学协处理器)
ISR_ERRCODE 8 ; 8 #DF 双重故障(有错误代码)
ISR_NOERRCODE 9 ; 9 协处理器跨段操作
ISR_ERRCODE 10 ; 10 #TS 无效TSS(有错误代码)
ISR_ERRCODE 11 ; 11 #NP 段不存在(有错误代码)
ISR_ERRCODE 12 ; 12 #SS 栈错误(有错误代码)
ISR_ERRCODE 13 ; 13 #GP 常规保护(有错误代码)
ISR_ERRCODE 14 ; 14 #PF 页故障(有错误代码)
ISR_NOERRCODE 15 ; 15 CPU 保留
ISR_NOERRCODE 16 ; 16 #MF 浮点处理单元错误
ISR_ERRCODE 17 ; 17 #AC 对齐检查
ISR_NOERRCODE 18 ; 18 #MC 机器检查
ISR_NOERRCODE 19 ; 19 #XM SIMD(单指令多数据)浮点异常

; 20~31 Intel 保留
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
; 32~255 用户自定义
ISR_NOERRCODE 255

idt_s.s

共有的现场保护操作

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
; 共有的现场保护的部分
[GLOBAL isr_common_stub]
[EXTERN isr_handler]
; 中断服务程序
isr_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 ; 此时的 esp 寄存器的值等价于 pt_regs 结构体的指针
call isr_handler ; 在 C 语言代码里
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, esp, ebx, edx, ecx, eax
add esp, 8 ; 清理栈里的 error code 和 ISR
iret
.end:

idt/idt.c

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

#include "common.h"
#include "string.h"
#include "debug.h"
#include "idt.h"

// 中断描述符表
idt_entry_t idt_entries[256];

// IDTR
idt_ptr_t idt_ptr;

// 中断处理函数的指针数组
interrupt_handler_t interrupt_handlers[256];

// 设置中断描述符
static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);

// 声明加载 IDTR 的函 这个函数在
extern void idt_flush(uint32_t);

// 初始化中断描述符表
void init_idt()
{
bzero((uint8_t *)&interrupt_handlers, sizeof(interrupt_handler_t) * 256);

idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1; // 设置表的界限
idt_ptr.base = (uint32_t)&idt_entries; // 基地址 数组第一个元素

bzero((uint8_t *)&idt_entries, sizeof(idt_entry_t) * 256);

// 0-32: 用于 CPU 的中断处理 编号 基址 选择子 等
idt_set_gate( 0, (uint32_t)isr0, 0x08, 0x8E);
idt_set_gate( 1, (uint32_t)isr1, 0x08, 0x8E);
idt_set_gate( 2, (uint32_t)isr2, 0x08, 0x8E);
idt_set_gate( 3, (uint32_t)isr3, 0x08, 0x8E);
idt_set_gate( 4, (uint32_t)isr4, 0x08, 0x8E);
idt_set_gate( 5, (uint32_t)isr5, 0x08, 0x8E);
idt_set_gate( 6, (uint32_t)isr6, 0x08, 0x8E);
idt_set_gate( 7, (uint32_t)isr7, 0x08, 0x8E);
idt_set_gate( 8, (uint32_t)isr8, 0x08, 0x8E);
idt_set_gate( 9, (uint32_t)isr9, 0x08, 0x8E);
idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E);
idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E);
idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E);
idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E);
idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E);
idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E);
idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E);
idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E);
idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E);
idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E);
idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E);
idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E);
idt_set_gate(22, (uint32_t)isr22, 0x08, 0x8E);
idt_set_gate(23, (uint32_t)isr23, 0x08, 0x8E);
idt_set_gate(24, (uint32_t)isr24, 0x08, 0x8E);
idt_set_gate(25, (uint32_t)isr25, 0x08, 0x8E);
idt_set_gate(26, (uint32_t)isr26, 0x08, 0x8E);
idt_set_gate(27, (uint32_t)isr27, 0x08, 0x8E);
idt_set_gate(28, (uint32_t)isr28, 0x08, 0x8E);
idt_set_gate(29, (uint32_t)isr29, 0x08, 0x8E);
idt_set_gate(30, (uint32_t)isr30, 0x08, 0x8E);
idt_set_gate(31, (uint32_t)isr31, 0x08, 0x8E);

// 255 将来用于实现系统调用
idt_set_gate(255, (uint32_t)isr255, 0x08, 0x8E);

// 更新设置中断描述符表
idt_flush((uint32_t)&idt_ptr);
}

// 设置中断描述符
static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
{
idt_entries[num].base_lo = base & 0xFFFF;
idt_entries[num].base_hi = (base >> 16) & 0xFFFF;

idt_entries[num].sel = sel;
idt_entries[num].always0 = 0;

// 先留下 0x60 这个魔数,以后实现用户态时候
// 这个与运算可以设置中断门的特权级别为 3
idt_entries[num].flags = flags; // | 0x60
}

// 调用中断处理函数
void isr_handler(pt_regs *regs)
{ // 如果注册了会执行这个函数
if (interrupt_handlers[regs->int_no]) {
interrupt_handlers[regs->int_no](regs);
} else {
// 如果没有注册 打印信息
printk_color(rc_black, rc_blue, "Unhandled interrupt: %d\n", regs->int_no);
}
}

// 注册一个中断处理函数
void register_interrupt_handler(uint8_t n, interrupt_handler_t h)
{
interrupt_handlers[n] = h;
}

bzero((uint8_t *)&interrupt_handlers, sizeof(interrupt_handler_t) * 256);

确保在初始化这些结构之前,它们不包含任何无意义的数据。

修改入口函数

init/entry.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "types.h"
#include "console.h"
#include "debug.h"
#include "gdt.h"
#include "idt.h"

int kern_entry(){
init_debug();
init_gdt();
init_idt();

console_clear();
//console_write_color("Hello, OS kernel!\n", rc_black, rc_green);
//panic("test");

printk_color(rc_black, rc_green, "Hello OS!!!\n");
asm volatile("int $0x3"); // 故意触发 中断 但由于中断的处理函数都还没有注册,所以先都答应没有注册
asm volatile("int $0x4");
return 0;
}

image-20240512160646845

8.完成中断请求和定时器中断

8259A 的PIC 架构:主从架构

image-20240512162404301

8.1 8259A PIC的初始化

idt/idt.c

在init_idt中最前面加入:

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
// 重新映射 IRQ 表
// 两片级联的 Intel 8259A 芯片
// 主片端口 0x20 0x21
// 从片端口 0xA0 0xA1

// 初始化主片、从片
// 0001 0001
outb(0x20, 0x11); // 0x20 = 20
outb(0xA0, 0x11); //

// 设置主片 IRQ 从 0x20(32) 号中断开始
outb(0x21, 0x20);

// 设置从片 IRQ 从 0x28(40) 号中断开始
outb(0xA1, 0x28);

// 设置主片 IR2 引脚连接从片
outb(0x21, 0x04);

// 告诉从片输出引脚和主片 IR2 号相连
outb(0xA1, 0x02);

// 设置主片和从片按照 8086 的方式工作
outb(0x21, 0x01);
outb(0xA1, 0x01);

// 设置主从片允许中断
outb(0x21, 0x0);
outb(0xA1, 0x0);

outb(0x20, 0x11);

  • 这里的 0x11 是初始化命令,其中最高四位(0001)告诉 PIC 进行级联模式初始化,最低四位(0001)则指示 PIC 等待额外的初始化字节。

outb(0x21, 0x20);

  • 设置主 PIC 的偏移(0x20,即 32)和从 PIC 的偏移(0x28,即 40)。这意味着 IRQ0 将映射到中断向量 0x20,IRQ8(从 PIC 的第一个中断)映射到中断向量 0x28。

outb(0x21, 0x04);设置主片 IR2 引脚连接从片

  • 0x21 :主PIC 的中断屏蔽寄存器的端口地址在初始化序列中,对于 8259A PIC,0x21 在发送初始化命令(ICW1)后也用于写入初始化命令字 2(ICW2)和初始化命令字 3(ICW3)。
  • 0x04:00000100 IRQ2.在设置 ICW3 时,这个值的二进制中的每一位表示一个 IRQ 输入是否连接到从 PIC。
  • 0x02:这个值在二进制表示中为 00000010,其中,位设置表示从 PIC 连接到主 PIC 的 IRQ2 端口(对于主 PIC 端的配置是 0x04,即第二个 IRQ,从0开始计数)。

include/idt.h 对IQR处理函数的添加

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
// IRQ 处理函数
void irq_handler(pt_regs *regs);

// 定义IRQ
#define IRQ0 32 // 电脑系统计时器
#define IRQ1 33 // 键盘
#define IRQ2 34 // 与 IRQ9 相接,MPU-401 MD 使用
#define IRQ3 35 // 串口设备
#define IRQ4 36 // 串口设备
#define IRQ5 37 // 建议声卡使用
#define IRQ6 38 // 软驱传输控制使用
#define IRQ7 39 // 打印机传输控制使用
#define IRQ8 40 // 即时时钟
#define IRQ9 41 // 与 IRQ2 相接,可设定给其他硬件
#define IRQ10 42 // 建议网卡使用
#define IRQ11 43 // 建议 AGP 显卡使用
#define IRQ12 44 // 接 PS/2 鼠标,也可设定给其他硬件
#define IRQ13 45 // 协处理器使用
#define IRQ14 46 // IDE0 传输控制使用
#define IRQ15 47 // IDE1 传输控制使用

// 声明 IRQ 函数
// IRQ:中断请求(Interrupt Request)
void irq0(); // 电脑系统计时器
void irq1(); // 键盘
void irq2(); // 与 IRQ9 相接,MPU-401 MD 使用
void irq3(); // 串口设备
void irq4(); // 串口设备
void irq5(); // 建议声卡使用
void irq6(); // 软驱传输控制使用
void irq7(); // 打印机传输控制使用
void irq8(); // 即时时钟
void irq9(); // 与 IRQ2 相接,可设定给其他硬件
void irq10(); // 建议网卡使用
void irq11(); // 建议 AGP 显卡使用
void irq12(); // 接 PS/2 鼠标,也可设定给其他硬件
void irq13(); // 协处理器使用
void irq14(); // IDE0 传输控制使用
void irq15(); // IDE1 传输控制使用

ISR (Interrupt Service Routine) 中断服务例程, 主要处理 内核级的中断,除0 溢出等 立即报警打断原有的进程

IRQ (Interrupt Request) 中断请求,主要用于处理外设的一些中断 键盘 鼠标等 请求嘛 允许后才会产生中断

idt/idt_s.s 添加相应的处理过程

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
; 构造中断请求的宏
%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 ; 与 IRQ9 相接,MPU-401 MD 使用
IRQ 3, 35 ; 串口设备
IRQ 4, 36 ; 串口设备
IRQ 5, 37 ; 建议声卡使用
IRQ 6, 38 ; 软驱传输控制使用
IRQ 7, 39 ; 打印机传输控制使用
IRQ 8, 40 ; 即时时钟
IRQ 9, 41 ; 与 IRQ2 相接,可设定给其他硬件
IRQ 10, 42 ; 建议网卡使用
IRQ 11, 43 ; 建议 AGP 显卡使用
IRQ 12, 44 ; 接 PS/2 鼠标,也可设定给其他硬件
IRQ 13, 45 ; 协处理器使用
IRQ 14, 46 ; IDE0 传输控制使用
IRQ 15, 47 ; IDE1 传输控制使用

[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:

这个宏定义用于构建中断处理程序的框架。每个中断处理程序都有一定的共性,例如禁用中断(cli,Clear Interrupt Flag)、保存状态、调用共通处理程序等。

  • %macro IRQ 2:定义一个带有两个参数的宏,**%1 是中断号,%2 是中断向量。**
  • cli:清除中断标志,防止中断处理程序被其他中断打断。
  • push 0:对于某些中断(如除错、溢出等),CPU 会自动压入错误代码;对于不会自动压入错误代码的中断(如外部中断),为了统一处理流程,在这里手动压入一个占位的错误代码。
  • push %2:压入中断向量编号,供中断处理程序使用。
  • jmp irq_common_stub跳转到通用的中断处理代码。

idt/idt.c 构造irq相关的 描述符和 具体的irq函数

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
	idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);
idt_set_gate(34, (uint32_t)irq2, 0x08, 0x8E);
idt_set_gate(35, (uint32_t)irq3, 0x08, 0x8E);
idt_set_gate(36, (uint32_t)irq4, 0x08, 0x8E);
idt_set_gate(37, (uint32_t)irq5, 0x08, 0x8E);
idt_set_gate(38, (uint32_t)irq6, 0x08, 0x8E);
idt_set_gate(39, (uint32_t)irq7, 0x08, 0x8E);
idt_set_gate(40, (uint32_t)irq8, 0x08, 0x8E);
idt_set_gate(41, (uint32_t)irq9, 0x08, 0x8E);
idt_set_gate(42, (uint32_t)irq10, 0x08, 0x8E);
idt_set_gate(43, (uint32_t)irq11, 0x08, 0x8E);
idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
idt_set_gate(45, (uint32_t)irq13, 0x08, 0x8E);
idt_set_gate(46, (uint32_t)irq14, 0x08, 0x8E);
idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E);



// IRQ 处理函数
void irq_handler(pt_regs *regs)
{
// 发送中断结束信号给 PICs
// 按照我们的设置,从 32 号中断起为用户自定义中断
// 因为单片的 Intel 8259A 芯片只能处理 8 级中断
// 故大于等于 40 的中断号是由从片处理的
if (regs->int_no >= 40) {
// 发送重设信号给从片
outb(0xA0, 0x20);
}
// 发送重设信号给主片
outb(0x20, 0x20);

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

ISR 和 iRQ 的处理过程:

  • ISR的处理过程是 (isr0 - isr31) -> isr_common_stub(相同的处理部分) -> isr_handler -> 具体的ISR处理函数。
  • IRQ的处理过程是 (irq0 - irq15) -> irq_common_stub -> irq_hanlder -> 具体的IRQ处理函数。

8.2 时钟中断和产生处理

配置8253/8254 timer 芯片

drivers/timer.c

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
#include "timer.h"
#include "debug.h"
#include "common.h"
#include "idt.h"

void timer_callback(pt_regs *regs)
{
static uint32_t tick = 0;
printk_color(rc_black, rc_red, "Tick: %d\n", tick++);
}

void init_timer(uint32_t frequency)
{
// 注册时间相关的处理函数
register_interrupt_handler(IRQ0, timer_callback);

// Intel 8253/8254 PIT芯片 I/O端口地址范围是40h~43h
// 输入频率为 1193180,frequency 即每秒中断次数
uint32_t divisor = 1193180 / frequency;

// D7 D6 D5 D4 D3 D2 D1 D0
// 0 0 1 1 0 1 1 0
// 即就是 36 H
// 设置 8253/8254 芯片工作在模式 3 下
outb(0x43, 0x36);

// 拆分低字节和高字节
uint8_t low = (uint8_t)(divisor & 0xFF);
uint8_t hign = (uint8_t)((divisor >> 8) & 0xFF);

// 分别写入低字节和高字节
outb(0x40, low);
outb(0x40, hign);
}
1
2
3
4
5
void timer_callback(pt_regs *regs)
{
static uint32_t tick = 0;
printk_color(rc_black, rc_red, "Tick: %d\n", tick++);
}

回调函数

  • tick 静态成员变量 用来记录中断发生的次数

  • printk_color(rc_black, rc_red, "Tick: %d\n", tick++);: 在屏幕上打印当前的tick数,并以红色字体显示。每调用一次这个函数,tick 的值就增加1。

include/timer.h

1
2
3
4
5
6
7
8
#ifndef INCLUDE_TIMER_H_
#define INCLUDE_TIMER_H_

#include "types.h"
// 定时器每秒钟触发中断的次数。
void init_timer(uint32_t frequency);

#endif // INCLUDE_TIMER_H_

修改入口函数

init/entry.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "types.h"
#include "console.h"
#include "debug.h"
#include "gdt.h"
#include "idt.h"
#include "timer.h"

int kern_entry(){
init_debug();
init_gdt();
init_idt();

console_clear();


printk_color(rc_black, rc_green, "Hello OS!!!\n");

init_timer(200);

// 触发中断
asm volatile("sti"); //
return 0;
}

asm volatile("sti") 通常在系统末尾调用

  • 在汇编语言中,sti 指令的作用是设置中断标志(Set Interrupt Flag)
  • 在操作系统启动过程中,中断通常在执行初期阶段被禁用(通常使用 cli 指令,即 Clear Interrupt Flag),以避免在系统未完全配置好时响应中断,这可能导致不稳定或错误。
  • 在系统的关键组件如GDT、IDT和定时器等被初始化和配置好之后,使用 sti 指令允许中断,是为了启动正常的中断处理,这对于系统的进一步功能如多任务处理、响应外部设备请求等是必要的。

image-20240512205003803