linux0.11内核源码修仙传第六章——中断初始化

🚀 前言

本文写了中断的介绍以及中断初始化程序trap_init函数整个初始化函数所做的事情就两件,第一件事是将一些如除零异常等的中断描述符以及保留中断描述符写进中断描述符表,通知设置可编程中断控制器的主片与从片的中断屏蔽控制器。对应于书中的第14回以及扩展阅读:什么是中断。希望各位给个三连,拜托啦,这对我真的很重要!!!

目录

🏆 什么是中断

中断的基本概念很简单,就是在做某件事情的时候突来来了另一件事情要立刻去做或者稍后去做。其实整个操作系统就是一个中断驱动的死循环,依据第四章的内容(linux0.11内核源码修仙传第四章------操作系统的框架代码),我们已经知道整个main函数最后是一个死循环:

c 复制代码
void main(void)
{
	...
	for(;;) pause();
}

其他所有事情都是由操作系统提前注册的中断机制及其对应的中断处理函数完成的,比如点鼠标,敲键盘,执行一个应用等,当没有事件时,操作系统就在循环里面待着。

📃中断分类

对于中断分类有很多,我之前也有相关的博客:嵌入式驱动学习第一周------内核的中断机制。这里根据Intel CPU手册,中断可以分为中断和异常,异常分为故障,陷阱和中止。

中断是一个异步事件,通常由IO设备触发,如按下键盘。异常是一个同步事件,是CPU在执行指令时检测到的反常条件,如除法异常等。这两个机制都是让CPU收到一个中断号。

📃中断号的产生

采用的是可编程中断控制器,如PIC,APIC,有很多IRQ引脚线,接入了一堆能发出中断请求的硬件设备。当硬件设备给IRQ引脚线发送信号时,由于可编程中断控制器提前被设置好了IRQ与中断号的对应关系,所以就转换成对应的中断号,CPU收到INTR引脚信号后,去对应端口即可获取中断号的值。过程如下所示:

异常是CPU自己执行指令时监测到一些反常情况,自己给自己一个中断号,无需外接提供。比如CPU执行到了一个无效指令,则会自己给自己中断号0x06。无需纠结具体怎么设置的,这些都是CPU提前规定好写死的硬布线逻辑。

还有一种中断,既不是外部设备触发的,也不是由于异常触发的,而是在bootloader阶段经常遇到的 INT中断 。

CPU接收到中断号后就不会区分是哪种触发方式了,均会一视同仁的处理所有的中断。

📃中断描述符找中断处理程序地址

众所周知,我们是开启了分段与分页机制的(linux0.11内核源码修仙传第四章------head.s),并且前面的博客中也构建了中断描述符表IDT。中断描述符表的起始地址存放在IDTR寄存器内。依据中断号以及中断描述符表就可以找到对应的中断处理程序的逻辑地址,至于这个表的构建就是在下一节中断初始化中设置的。

🏆 中断初始化 trap_init

上一节介绍了中断,并且说设置中断向量表是在中断初始化中实现的,具体初始化函数的内容如下所示:

c 复制代码
void trap_init(void)
{
	int i;

	set_trap_gate(0,&divide_error);
	set_trap_gate(1,&debug);
	set_trap_gate(2,&nmi);
	set_system_gate(3,&int3);	/* int3-5 can be called from all */
	set_system_gate(4,&overflow);
	set_system_gate(5,&bounds);
	set_trap_gate(6,&invalid_op);
	set_trap_gate(7,&device_not_available);
	set_trap_gate(8,&double_fault);
	set_trap_gate(9,&coprocessor_segment_overrun);
	set_trap_gate(10,&invalid_TSS);
	set_trap_gate(11,&segment_not_present);
	set_trap_gate(12,&stack_segment);
	set_trap_gate(13,&general_protection);
	set_trap_gate(14,&page_fault);
	set_trap_gate(15,&reserved);
	set_trap_gate(16,&coprocessor_error);
	for (i=17;i<48;i++)
		set_trap_gate(i,&reserved);
	set_trap_gate(45,&irq13);
	outb_p(inb_p(0x21)&0xfb,0x21);
	outb(inb_p(0xA1)&0xdf,0xA1);
	set_trap_gate(39,&parallel_interrupt);
}

上面的代码前面都是很统一的set_system_gate函数与set_trap_gate函数。要理解这两个函数,就要看这两个函数的定义:

c 复制代码
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

#define set_trap_gate(n,addr) \
	_set_gate(&idt[n],15,0,addr)

#define set_system_gate(n,addr) \
	_set_gate(&idt[n],15,3,addr)

这两个函数都是宏定义出来的,并且都调用了_set_gate。而_set_gate内是汇编语言,实现的最终效果就是在中断描述符表IDT中插入了一个中断描述符 。从定义上可以看到,虽然都是调用的_set_gate,但是set_system_gateset_trap_gate之间差异在参数上,即dpl参数,表示要访问该中断描述符所需要的特权级,前者是3,表示用户态,后者是0,表示内核态。

现在再回去看trap_init就清晰很多了, 里面就是往中断描述符表里面一项一项地写东西,第一个参数是中断号,第二个参数是中断处理程序的地址。以set_trap_gate(0,&divide_error);为例,设置了0号中断,对应的中断处理程序为divide_error。当CPU执行一条除以0的指令时,硬件层面会发起一个0号中断异常,然后执行操作系统定义的divide_error,执行完成后返回。

在单独设置完成后,还有一个for循环用于批量设置为reserved中断处理程序,用于后面再各个硬件初始化时要重新设置这些中断,把初始化里面设置的这个暂时的给覆盖掉。此时IDT的内容如下所示:

这个函数最后还调用了outboutb_p,这两个定义如下:

c 复制代码
#define outb(value,port) \
__asm__ ("outb %%al,%%dx"::"a" (value),"d" (port))

#define outb_p(value,port) \
__asm__ ("outb %%al,%%dx\n" \
		"\tjmp 1f\n" \
		"1:\tjmp 1f\n" \
		"1:"::"a" (value),"d" (port))
		
#define inb_p(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al\n" \
	"\tjmp 1f\n" \
	"1:\tjmp 1f\n" \
	"1:":"=a" (_v):"d" (port)); \
_v; \
})

outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xdf,0xA1);

在这段代码中,outb向指定端口输出一个字节数据的功能,outb_p是在outb基础上插入了一些延时,inb_p用于从指定端口读取,并且读取也插入了延时。此外,端口0x21是可编程中断控制器(PIC)主片的中断屏蔽寄存器(IMR,作用是控制哪些请求被屏蔽,哪些被允许)。inb_p(0x21)&0xfb是清零第 2 位,目的是允许从片的中断请求 ,因为主片的第 2 位用于连接从片的中断请求信号,最后写回0x21端口。0xA1是从片的IMR,设置同0x21

最后,还记得此时中断处于禁止状态吗(linux0.11内核源码修仙传第二章------setup.s 中的第一节),因此在最后需要打开中断,采用汇编指令sti,该指令在main函数中用sti函数实现:

c 复制代码
#define sti() __asm__ ("sti"::)

void main(void) {
	···
	sti();
	···
}

🎯总结

整个中断初始化就做了两件事,第一件事是将一些如除零异常等的中断描述符以及保留中断描述符写进IDT,通知设置PIC的主片与从片的IMR。

📖参考资料

[1] linux源码趣读

[2] 一个64位操作系统的设计与实现

相关推荐
云上艺旅7 分钟前
K8S学习之基础二十四:k8s的持久化存储之pv和pvc
学习·云原生·容器·kubernetes
是星辰吖~19 分钟前
C语言_数据结构_队列
c语言·数据结构
嵌入式-老费25 分钟前
Linux上位机开发实战(x86和arm自由切换)
linux·运维·arm开发
猪猪侠|ZZXia29 分钟前
# linux有哪些显示服务器协议、显示服务器、显示管理器、窗口管理器?有哪些用于开发图形用户界面的工具包?有哪些桌面环境?
linux·服务器
人间凡尔赛42 分钟前
VSCode-Server 在 Linux 容器中的手动安装指南
linux·运维·服务器·docker
Chenyu_31044 分钟前
05.基于 TCP 的远程计算器:从协议设计到高并发实现
linux·网络·c++·vscode·网络协议·tcp/ip·算法
淳杰1 小时前
ubuntu-学习笔记-nginx+php
笔记·学习·ubuntu
板鸭〈小号〉1 小时前
Linux开发工具----vim
linux·运维·vim
衡玖1 小时前
c语言闯算法--排序
c语言·数据结构·算法
Speedy1 小时前
个人设计作品集-石坪
面试·设计师·交互设计