Bran的内核开发教程(bkerndev)-08 中断服务程序(ISR)
中断服务程序(ISR)
中断服务程序(ISR)用于保存当前处理器的状态, 并在调用内核的C级中断处理程序之前正确设置内核模式所需的段寄存器。而工作只需要15到20行汇编代码来处理, 包括调用C中的处理程序。我们还需要将IDT条目指向正确的ISR以正确处理异常。
异常是导致处理器无法正常执行的特殊情况, 比如除以0结果是未知数或者非实数, 因此处理器会抛出异常, 这样内核就可以阻止进程或任务引起任何问题。如果处理器发现程序正尝试访问不允许其访问的内存, 则会引起一般保护错误。当你设置内存页时, 处理器将会产生页面错误, 但这是可以恢复的: 你可以将内存页映射到错误的地址(但这需要另开一篇教程来讲解)。
IDT的前32个条目与处理器可能产生的异常对应, 因此需要对其进行处理。某些异常会将另一个值压入堆栈中: 错误代码, 该值为每个异常的特定代码。
| Exception # | Description | Error Code? |
|---|---|---|
| 0 | Division By Zero Exception | No |
| 1 | Debug Exception | No |
| 2 | Non Maskable Interrupt Exception | No |
| 3 | Breakpoint Exception | No |
| 4 | Into Detected Overflow Exception | No |
| 5 | Out of Bounds Exception | No |
| 6 | Invalid Opcode Exception | No |
| 7 | No Coprocessor Exception | No |
| 8 | Double Fault Exception | Yes |
| 9 | Coprocessor Segment Overrun Exception | No |
| 10 | Bad TSS Exception | Yes |
| 11 | Segment Not Present Exception | Yes |
| 12 | Stack Fault Exception | Yes |
| 13 | General Protection Fault Exception | Yes |
| 14 | Page Fault Exception | Yes |
| 15 | Unknown Interrupt Exception | No |
| 16 | Coprocessor Fault Exception | No |
| 17 | Alignment Check Exception (486+) | No |
| 18 | Machine Check Exception (Pentium/586+) | No |
| 19 to 31 | Reserved Exceptions | No |
之前提到, 一些异常会错误码压入堆栈中, 为了降低复杂度, 我们为尚未压入错误码的ISR将伪错误码0压入堆栈中, 这样可以保持统一的堆栈结构。为了跟踪触发的是哪个异常, 我们将中断号也压入堆栈。我们使用汇编操作码"cli"来禁用中断并防止触发IRQ, 否则可能会导致内核冲突。为了节省内核空间, 生成较小的二进制文件, 我们让每个ISR的存根(stub)跳转到通用isr_common_stub函数。isr_common_stub用于将处理器的状态保存到堆栈上, 将当前堆栈地址压入堆栈(为我们的C处理程序提供堆栈), 调用C中的fault_handler函数, 最后恢复堆栈的状态。在"start.asm"预留的位置中添加下面的代码, 填写所有的32个ISR:
start.asm
; 在之后的教程中, 我们将添加中断
; 这里是中断服务程序(ISR)
global _isr0
global _isr1
global _isr2
global _isr3
global _isr4
global _isr5
global _isr6
global _isr7
global _isr8
global _isr9
global _isr10
global _isr11
global _isr12
global _isr13
global _isr14
global _isr15
global _isr16
global _isr17
global _isr18
global _isr19
global _isr20
global _isr21
global _isr22
global _isr23
global _isr24
global _isr25
global _isr26
global _isr27
global _isr28
global _isr29
global _isr30
global _isr31
; 0: 除以零异常
_isr0:
cli
push byte 0 ; 一个ISR占位符, 会弹出一个为错误码来保持一个统一的堆栈框架
push byte 0
jmp isr_common_stub
; 1: 调试异常
_isr1:
cli
push byte 0
push byte 1
jmp isr_common_stub
; 2: 不可屏蔽的中断异常
_isr2:
cli
push byte 0
push byte 2
jmp isr_common_stub
; 3: Int 3异常
_isr3:
cli
push byte 0
push byte 3
jmp isr_common_stub
; 4: INTO异常
_isr4:
cli
push byte 0
push byte 4
jmp isr_common_stub
; 5: 越界异常
_isr5:
cli
push byte 0
push byte 5
jmp isr_common_stub
; 6: 无效的操作码异常
_isr6:
cli
push byte 0
push byte 6
jmp isr_common_stub
; 7: 协处理器不可用异常
_isr7:
cli
push byte 0
push byte 7
jmp isr_common_stub
; 8: 双重故障异常(带错误码!)
_isr8:
cli
push byte 8 ; 注意我们不需要在此压入一个值到堆栈中, 它已经压入了一个。
; 会弹出错误码的异常可使用这类存根
jmp isr_common_stub
; 9: 协处理器段溢出异常
_isr9:
cli
push byte 0
push byte 9
jmp isr_common_stub
; 10: 错误的TSS异常(带错误码!)
_isr10:
cli
push byte 10
jmp isr_common_stub
; 11: 段不存在异常(带错误码!)
_isr11:
cli
push byte 11
jmp isr_common_stub
; 12: 堆栈故障异常(带错误码!)
_isr12:
cli
push byte 12
jmp isr_common_stub
; 13: 常规保护故障异常(带错误码!)
_isr13:
cli
push byte 13
jmp isr_common_stub
; 14: 页面错误异常(带错误码!)
_isr14:
cli
push byte 14
jmp isr_common_stub
; 15: 保留异常
_isr15:
cli
push byte 0
push byte 15
jmp isr_common_stub
; 16: 浮点异常
_isr16:
cli
push byte 0
push byte 16
jmp isr_common_stub
; 17: 对齐检查异常
_isr17:
cli
push byte 0
push byte 17
jmp isr_common_stub
; 18: 机器检查异常
_isr18:
cli
push byte 0
push byte 18
jmp isr_common_stub
; 19: 保留
_isr19:
cli
push byte 0
push byte 19
jmp isr_common_stub
; 20: 保留
_isr20:
cli
push byte 0
push byte 20
jmp isr_common_stub
; 21: 保留
_isr21:
cli
push byte 0
push byte 21
jmp isr_common_stub
; 22: 保留
_isr22:
cli
push byte 0
push byte 22
jmp isr_common_stub
; 23: 保留
_isr23:
cli
push byte 0
push byte 23
jmp isr_common_stub
; 24: 保留
_isr24:
cli
push byte 0
push byte 24
jmp isr_common_stub
; 25: 保留
_isr25:
cli
push byte 0
push byte 25
jmp isr_common_stub
; 26: 保留
_isr26:
cli
push byte 0
push byte 26
jmp isr_common_stub
; 27: 保留
_isr27:
cli
push byte 0
push byte 27
jmp isr_common_stub
; 28: 保留
_isr28:
cli
push byte 0
push byte 28
jmp isr_common_stub
; 29: 保留
_isr29:
cli
push byte 0
push byte 29
jmp isr_common_stub
; 30: 保留
_isr30:
cli
push byte 0
push byte 30
jmp isr_common_stub
; 31: 保留
_isr31:
cli
push byte 0
push byte 31
jmp isr_common_stub
; 我们在这里调用C函数
; 我们需要让汇编器知道"_fault_handler"在另一个文件中
extern _fault_handler
; 这是我们ISR的通用存根
; 它用于保存处理器的状态, 设置内核模式段, 调用C里的故障处理程序
; 最后恢复堆栈框架
isr_common_stub:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10 ; 加载内核数据段描述符
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp ; 将指向堆栈的指针压入堆栈
push eax
mov eax, _fault_handler
call eax ; 特殊调用, 保存"eip"寄存器的值
pop eax
pop gs
pop fs
pop es
pop ds
popa
add esp, 8 ; 清除压入的错误码和ISR号
iret ; 将CS、EIP、EFLAGS、SS和ESP一同弹出
创建一个新文件, 命名为"isrs.c"。别忘了在"build.bat"文件中添加一行GCC命令编译该文件。将文件"isrs.o"添加到LD文件列表中, 这样才能将其链接到内核中。"isrs.c"文件很简单: 首先是常规的#include行, 声明"start.asm"中每个ISR的原型, 将IDT条目指向正确的ISR, 最后创建一个中断处理程序来服务我们所有的异常。
isrs.c
#include <system.h>
/* 这里是所有异常处理程序的原型:
* IDT的前32个条目由英特尔保留,
* 用于处理异常 */
extern void isr0();
extern void isr1();
extern void isr2();
extern void isr3();
extern void isr4();
extern void isr5();
extern void isr6();
extern void isr7();
extern void isr8();
extern void isr9();
extern void isr10();
extern void isr11();
extern void isr12();
extern void isr13();
extern void isr14();
extern void isr15();
extern void isr16();
extern void isr17();
extern void isr18();
extern void isr19();
extern void isr20();
extern void isr21();
extern void isr22();
extern void isr23();
extern void isr24();
extern void isr25();
extern void isr26();
extern void isr27();
extern void isr28();
extern void isr29();
extern void isr30();
extern void isr31();
/* 我们将IDT的前32个条目设置为前32个ISR
* 这里我们无法使用for循环, 因为无法获取与之对应的函数名
* 我们将访问标志设置为0x8E, 代表条目存在, 并在Ring 0(内核级别)中运行
* 并将低5位设置为要求的"14", 用十六进制的"E"表示 */
void isrs_install()
{
idt_set_gate(0, (unsigned)isr0, 0x08, 0x8E);
idt_set_gate(1, (unsigned)isr1, 0x08, 0x8E);
idt_set_gate(2, (unsigned)isr2, 0x08, 0x8E);
idt_set_gate(3, (unsigned)isr3, 0x08, 0x8E);
idt_set_gate(4, (unsigned)isr4, 0x08, 0x8E);
idt_set_gate(5, (unsigned)isr5, 0x08, 0x8E);
idt_set_gate(6, (unsigned)isr6, 0x08, 0x8E);
idt_set_gate(7, (unsigned)isr7, 0x08, 0x8E);
idt_set_gate(8, (unsigned)isr8, 0x08, 0x8E);
idt_set_gate(9, (unsigned)isr9, 0x08, 0x8E);
idt_set_gate(10, (unsigned)isr10, 0x08, 0x8E);
idt_set_gate(11, (unsigned)isr11, 0x08, 0x8E);
idt_set_gate(12, (unsigned)isr12, 0x08, 0x8E);
idt_set_gate(13, (unsigned)isr13, 0x08, 0x8E);
idt_set_gate(14, (unsigned)isr14, 0x08, 0x8E);
idt_set_gate(15, (unsigned)isr15, 0x08, 0x8E);
idt_set_gate(16, (unsigned)isr16, 0x08, 0x8E);
idt_set_gate(17, (unsigned)isr17, 0x08, 0x8E);
idt_set_gate(18, (unsigned)isr18, 0x08, 0x8E);
idt_set_gate(19, (unsigned)isr19, 0x08, 0x8E);
idt_set_gate(20, (unsigned)isr20, 0x08, 0x8E);
idt_set_gate(21, (unsigned)isr21, 0x08, 0x8E);
idt_set_gate(22, (unsigned)isr22, 0x08, 0x8E);
idt_set_gate(23, (unsigned)isr23, 0x08, 0x8E);
idt_set_gate(24, (unsigned)isr24, 0x08, 0x8E);
idt_set_gate(25, (unsigned)isr25, 0x08, 0x8E);
idt_set_gate(26, (unsigned)isr26, 0x08, 0x8E);
idt_set_gate(27, (unsigned)isr27, 0x08, 0x8E);
idt_set_gate(28, (unsigned)isr28, 0x08, 0x8E);
idt_set_gate(29, (unsigned)isr29, 0x08, 0x8E);
idt_set_gate(30, (unsigned)isr30, 0x08, 0x8E);
idt_set_gate(31, (unsigned)isr31, 0x08, 0x8E);
}
/* 这里是一个简单的字符串数组, 包含与每个异常对应的消息
* 我们通过这种方式来获得对应的消息:
* exception_message[interrupt_number] */
unsigned char *exception_messages[] =
{
"Division By Zero",
"Debug",
"Non Maskable Interrupt",
"Breakpoint",
"Into Detected Overflow",
"Out of Bounds",
"Invalid Opcode",
"No Coprocessor",
"Double Fault",
"Coprocessor Segment Overrun",
"Bad TSS",
"Segment Not Present",
"Stack Fault",
"General Protection Fault",
"Page Fault",
"Unknown Interrupt",
"Coprocessor Fault",
"Alignment Check",
"Machine Check",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved"
};
/* 我们所有的异常处理中断服务程序都将指向此函数, 这会告诉我们发生了什么异常
* 现在我们只是通过死循环来暂停系统
* 当所有ISR被用作“锁定”机制时,它们会禁用中断,以防止IRQ的发生并破坏内核数据结构 */
void fault_handler(struct regs *r)
{
/* 判断是否是中断号为0~31的错误 */
if (r->int_no < 32)
{
/* 显示发生的异常的描述
* 本教程中我们简单地使用一个死循环来暂停系统 */
puts(exception_messages[r->int_no]);
puts(" Exception. System Halted!\n");
for (;;);
}
}
等一下, 在fault_handler函数的参数中有一个新的结构体struct regs我们还没有定义。regs向C代码展示了堆栈的框架结构。还记得吗, 我们在"start.asm"中我们将指向堆栈本身的指针压入堆栈, 这样我们就可以从处理程序中获取错误码和中断号。这种设计方式让我们能使用一个C程序来处理不同的ISR, 并可以确定发生的是哪个异常或中断。
在"system.h"中定义堆栈框架:
system.h
/* 这定义了ISR运行后的堆栈结构 */
struct regs
{
unsigned int gs, fs, es, ds; /* 这些段最后压入 */
unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax; /* 通过"pusha"压入栈中 */
unsigned int int_no, err_code;
unsigned int eip, cs, eflags, useresp, ss; /* 由处理器自动压入堆栈 */
};
打开"system.h"文件, 添加reg结构体的定义和isrs_install函数原型, 以便我们在"main.c"中调用。最后, 在main函数中安装IDT的后面调用isrs_install。现在可以在我们新的内核中测试一下我们的异常处理程序了。
可选操作: 在"main.c"中添加一些测试代码, 该代码进行除以0操作:
main.c
int main()
{
int i;
gdt_install();
idt_install();
isrs_install();
init_video();
puts("Hello World!\n");
i = 10 / 0;
putch(i);
for (;;);
return 0;
}
当处理器遇到该错误, 将会产生"Divide By Zero"异常, 并在屏幕上打印。测试成功后, 你可以删除这些测试代码。测试结果如下:

此文原创禁止转载,转载文章请联系博主并注明来源和出处,谢谢!
作者: Raina_RLN https://www.cnblogs.com/raina/
Bran的内核开发教程(bkerndev)-08 中断服务程序(ISR)的更多相关文章
- Bran的内核开发教程(bkerndev)-07 中断描述符表(IDT)
中断描述符表(IDT) 中断描述符表(IDT)用于告诉处理器调用哪个中断服务程序(ISR)来处理异常或汇编中的"int"指令.每当设备完成请求并需要服务事, 中断请求也会调用I ...
- Bran的内核开发教程(bkerndev)-02 准备工作
准备工作 内核开发是编写代码以及调试各种系统组件的漫长过程.一开始这似乎是一个让人畏惧的任务,但是并不需要大量的工具集来编写自己的内核.这个内核开发教程主要涉及使用GRUB将内核加载到内存中.GR ...
- Bran的内核开发教程(bkerndev)-06 全局描述符表(GDT)
全局描述符表(GDT) 在386平台各种保护措施中最重要的就是全局描述符表(GDT).GDT为内存的某些部分定义了基本的访问权限.我们可以使用GDT中的一个索引来生成段冲突异常, 让内核终止执行异 ...
- Bran的内核开发教程(bkerndev)-01 介绍
介绍 内核开发不是件容易的事,这是对一个程序员编程能力的考验.开发内核其实就是开发一个能够与硬件交互和管理硬件的软件.内核也是一个操作系统的核心,是管理硬件资源的逻辑. 处理器或是CPU是内核 ...
- Bran的内核开发教程(bkerndev)-04 创建main函数和链接C文件
目录 创建main函数和链接C文件 PS: 下面是我自己写的 Win10安装gcc编译器 本节教程对应的Linux下的编译脚本 _main的问题 创建main函数和链接C文件 一般C语言使用mai ...
- Bran的内核开发教程(bkerndev)-03 内核初步
目录 内核初步 内核入口 链接脚本 汇编和链接 PS: 下面是我自己写的 64位Linux下的编译脚本 内核初步 在这节教程, 我们将深入研究一些汇编程序, 学习创建链接脚本的基础知识以及使用它的 ...
- Bran的内核开发教程(bkerndev)-05 打印到屏幕
打印到屏幕 现在, 我们需要尝试打印到屏幕上.为此, 我们需要管理屏幕滚动, 如果能允许使用不同的颜色就更好了.好在VGA视频卡为我们提供了一片内存空间, 允许同时写入属性字节和字符字节对, 可以 ...
- Bran的内核开发指南_中文版
http://www.cnblogs.com/liloke/archive/2011/12/21/2296004.html 最近在看<orange’s>一书,有点想自己写一个轻量级OS的想 ...
- Windows内核开发-6-内核机制 Kernel Mechanisms
Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...
随机推荐
- RIDE的Edit界面
有四种类型的Edit界面(注:测试套件主要是存放测试案例,资源文件主要是存放用户关键字) 1.测试套件(file类型)的Edit界面 首先展开Setting: 对右侧红框按钮简单说明: Library ...
- Spring Boot项目中如何定制PropertyEditors
本文首发于个人网站:Spring Boot项目中如何定制PropertyEditors 在Spring Boot: 定制HTTP消息转换器一文中我们学习了如何配置消息转换器用于HTTP请求和响应数据, ...
- 基于redis解决session分布式一致性问题
1.session是什么 当用户在前端发起请求时,服务器会为当前用户建立一个session,服务器将sessionId回写给客户端,只要用户浏览器不关闭,再次请求服务器时,将sessionId传给服务 ...
- (记录)Jedis存放对象和读取对象--Java序列化与反序列化
一.理论分析 在学习Redis中的Jedis这一部分的时候,要使用到Protostuff(Protobuf的Java客户端)这一序列化工具.一开始看到序列化这些字眼的时候,感觉到一头雾水.于是,参考了 ...
- 使用VS2013操作MYSQL8 (ADO.NET方式 & EF6)
今天有时间测试了一下通过.net环境操作MYSQL数据库,测试过程及结果记录如下: 1.MYSQL安装 (1)我是从MYSQL官网下载的最新版,即MYSQL8.0,在MySql官网的下载页面,找到“M ...
- AQL基本语法
目录: 基本的CRUD 插入 检索 更新 删除 匹配文件 排序和限制 限制 排序 组合 图操作 地理位置查询 一.数据预览 本次使用的数据共有43条,每条数据包含姓氏.年龄.活动状态和特征等六个字段 ...
- Linux nfs服务介绍
nfs服务介绍 nfs(Network File System) 网络文件系统,能使用户访问服务器的文件系统,就像访问自己的本机的文件系统一样,并且多个 客户端共享访问该文件系统. 目前nfs服务,较 ...
- Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)(Mapper方法是如何调用到XML中的SQL的?)全网最详细,没有之一
我们上篇文章讲到了查询方法里面的doQuery方法,这里面就是调用JDBC的API了,其中的逻辑比较复杂,我们这边文章来讲,先看看我们上篇文章分析的地方 SimpleExecutor public & ...
- Java面试官最爱问的volatile关键字
在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...
- maven项目部署到tomcat方法
今天记录下,maven项目部署到服务器的过程 1.首先在ide中里将自己的maven项目打包 mvn clean install 2. 看是否需要修改war包的名字,如果要修改,就用命令 mv xxx ...