zoukankan      html  css  js  c++  java
  • 操作系统:保护模式下的中断和异常

    博客的代码均节选自《Orange's一个操作系统的实现》

    前置知识:

    GDT的结构(最好是自己写过),实模式下的中断相关知识

    正篇

    在保护模式下,因为种种原因(比如实模式寻址方式的变化之类的),BIOS提供的中断服务是不可用的。但是中断还是非常重要的一个概念,以至于以后的任务切换,外设访问,异常处理都需要依赖于中断。此时变需要用户手动编写一些中断处理程序。

    在正式开始编写中断机制前,先要了解一些基础知识。

    异常

    当我们程序把0当成除数,或者是代码段跳转的时候特权级(CPL,DPL,RPL)发生了问题,CPU便会抛出一个异常(exception)来告诉操作系统。

    异常的分类有3种,分别是:

    1. Fault -> 可更正异常,一旦被更正,程序可以不失连续性地运行下去。当fault发生的时候,CPU会把fault之前的状态保存起来,异常处理程序的返回地址将会是产生fault的那一条指令而不是其之后的指令。
    2. Trap -> 当程序发生trap异常之后会被立即报告,和fault不同的是,处理程序返回的是发生trap的下一行指令(也就是说会跳过发生trap异常的指令)。
    3. Abort -> 是一种不总是报告精确位置的异常,发生的时候意味着CPU不允许程序继续执行下去。发生这个异常的时候意味着发生了严重的错误。

    异常都是同步事件,并且都是无法屏蔽的,也就是说异常的接受与否和IF位是没有关系的。非常有意思的是,在程序中类似于int 10h的中断指令其实也是被当作异常来处理的。

    下面是intel预设的异常列表:

    向量号 助记符 类型 描述 来源
    0 #DE 错误 除零错误 DVI和IDIV指令
    1 #DB 错误/陷阱 调试异常,用于软件调试 任何代码或数据引用
    2 中断 NMI中断 不可屏蔽的外部中断
    3 #BP 陷阱 断点 INT 3指令
    4 #OF 陷阱 溢出 INTO指令
    5 #BR 错误 数组越界 BOUND指令
    6 #UD 错误 无效指令(没有定义的指令) UD2指令(奔腾Pro CPU引入此指令)或任何保留的指令
    7 #NM 错误 数学协处理器不存在或不可用 浮点或WAIT/FWAIT指令
    8 #DF 终止 双重错误(Double Fault) 任何可能产生异常的指令、不可屏蔽中断或可屏蔽中断
    9 #MF 错误 向协处理器传送操作数时检测到页错误(Page Fault)或段不存在,486及以后集成了协处理器,本错误就保留不用了 浮点指令
    10 #TS 错误 无效TSS 任务切换或访问TSS
    11 #NP 错误 段不存在 加载段寄存器或访问系统段
    12 #SS 错误 栈段错误 栈操作或加载SS寄存器
    13 #GP 错误 通用/一般保护异常,如果一个操作违反了保护模式下的规定,而且该情况不属于其他异常,CPU就是认为是该异常 任何内存引用或保护性检查
    14 #PF 错误 页错误 任何内存引用
    15 保留
    16 #MF 错误 浮点错误 浮点或WAIT/FWAIT指令
    17 #AC 错误 对齐检查 对内存中数据的引用(486CPU引入)
    18 #MC 终止 机器检查(Machine Check) 错误代码和来源与型号有关(奔腾CPU引入)
    19 #XF 错误 SIMD浮点异常 SIMD浮点指令(奔腾III CPU引入)
    20~31 保留
    32~255 用户自定义中断 中断 可屏蔽中断 来自INTR的外部中断或INT n指令

    中断

    中断其实是一种程序本身无法预料的外部设备信号(程序内部发生的异常和int指令在这里就理解成程序本身是可以预测的)。

    除了程序内部的中断调用(int),我们最关心的就是外部设备的中断了。外部设备的中断也被分为两大类:

    1. 可屏蔽中断
    2. 不可屏蔽中断

    不可屏蔽中断时由 #NMI 引脚传输,它的屏蔽与否和IF位的设置没啥关系。所以这里我们主要还是来关注可屏蔽中断

    可屏蔽中断

    可屏蔽中断通过#INTR 引脚来传输,CPU在 #INTR 上级联了两片8259A芯片,也就是可编程中断控制器8259A。可屏蔽中断和CPU的互通时通过控制8259A来实现的,不深究具体硬件细节的情况下可以把它理解成一种外部中断的统筹,可以通过对其进行设置来控制中断的接受与屏蔽。下面就是8259A的大概样子:

    1

    整个8259A芯片一共有15个接口,也就是说一共可以挂在15个外部设备。在BIOS加电的时候芯片的IRQ0 ~ IRQ7被设置成向量08H ~ 0FH。但是在之前的表中我们可以发现,08H ~ 0FH已经被占用了,所以这里我们还需要对主从8259A芯片重新设置。

    8259A芯片的初始化

    我们通常时通过写4个ICW(Initialization Command Word)来实现初始化的。加点开始的时候,主8256A芯片的端口号是20H和21H,从8259A芯片的端口号是A0H和A1H,我们通过向这几个端口写入ICW1,ICW2,ICW3,ICW4来初始化。特别要注意的是,这几个ICW必须以1 ~ 4的顺序来写入端口 ,不能颠倒顺序。下面是ICW的组成:

    2

    下面是用ICW初始化芯片的代码:

    Init8259A:
    	mov	al, 011h
    	out	020h, al	; 主8259, ICW1.
    	call	io_delay
    
    	out	0A0h, al	; 从8259, ICW1.
    	call	io_delay
    
    	mov	al, 020h	; IRQ0 对应中断向量 0x20
    	out	021h, al	; 主8259, ICW2.
    	call	io_delay
    
    	mov	al, 028h	; IRQ8 对应中断向量 0x28
    	out	0A1h, al	; 从8259, ICW2.
    	call	io_delay
    
    	mov	al, 004h	; IR2 对应从8259
    	out	021h, al	; 主8259, ICW3.
    	call	io_delay
    
    	mov	al, 002h	; 对应主8259的 IR2
    	out	0A1h, al	; 从8259, ICW3.
    	call	io_delay
    
    	mov	al, 001h
    	out	021h, al	; 主8259, ICW4.
    	call	io_delay
    
    	out	0A1h, al	; 从8259, ICW4.
    	call	io_delay
    

    8259A芯片的设置

    初始化完成后,我们就可以对芯片进行设置了。对芯片的设置是通过对端口写入OCW(Operation Command Word)来实现的。

    OCW的组成非常简单,一共有8位,每一位代表了相应中断的开关与否(1是关闭,0是打开)。OCW实际上被写入了中断屏蔽寄存器IMR(Interrupt Mask Register)中,当设备发来中断信号的时候IMR会判断是否抛弃这个信号。

    下面是一个OCW使用的例子,例子中我们屏蔽了除了时钟中断以外的所有中断。

    	mov	al, 11111110b	; 仅仅开启定时器中断
    	out	021h, al	; 主8259, OCW1.
    	call	io_delay
    
    	mov	al, 11111111b	; 屏蔽从8259所有中断
    	out	0A1h, al	; 从8259, OCW1.
    	call	io_delay
    

    io_delay是一个延时函数,代码在这里:

    io_delay:
    	nop
    	nop
    	nop
    	nop
    	ret
    

    IDT

    一个中断的正常发生包含了两个部分:

    1. 从设备要能够传递到CPU
    2. CPU要能找到中断号对应的代码

    上面对8259芯片的设置解决了第一个问题,现在我们要来解决第二个问题。

    中断描述符表IDT(Interrupt Descriptor Table),是一种用来存储中断向量对应的中断描述符的表。IDT存储的是中断向量和处理程序选择子+偏移的对应,也就是说如果我们在程序内部,从这个角度上来看其实IDT和实模式下的中断向量表是一样的概念。IDT描述符包含以下3种:

    1. 中断门
    2. 陷阱门
    3. 任务门

    一个IDT描述符包含了中断处理程序的选择子,偏移,属性等信息。中断号是从0开始连续升序的,所以没必要在描述符中包含中断号。

    中断门/陷阱门描述符结构如下图:

    3

    其实中断门和陷阱门还是有区别的。通过中断门进行中断调用的时候会对IF进行复位,所以会防止其他中断对当前中断的干扰,但是陷阱门并不会。

    IDT的简单定义:

    [SECTION .idt]
    ALIGN	32
    [BITS	32]
    LABEL_IDT:
    ; 门                        目标选择子,            偏移, DCount, 属性
    %rep 255
    		Gate	SelectorCode32, SpuriousHandler, 0, DA_386IGate
    %endrep;通过宏来使得所有IDT描述都都一样,反正也只是测试用
    
    IdtLen		equ	$ - LABEL_IDT
    IdtPtr		dw	IdtLen - 1	; 段界限
    		dd	0		; 基地址
    

    Gate宏定义(实际上就是让程序长得稍微好看了一点):

    ; usage: Gate Selector, Offset, DCount, Attr
    ;        Selector:  dw
    ;        Offset:    dd
    ;        DCount:    db
    ;        Attr:      db
    %macro Gate 4
    	dw	(%2 & 0FFFFh)				; 偏移 1				(2 字节)
    	dw	%1					; 选择子				(2 字节)
    	dw	(%3 & 1Fh) | ((%4 << 8) & 0FF00h)	; 属性					(2 字节)
    	dw	((%2 >> 16) & 0FFFFh)			; 偏移 2				(2 字节)
    %endmacro ; 共 8 字节
    

    这里偷懒把所有中断号对应的中断处理程序都初始化成同一个。

    IDT也是描述符表,所以安装过程其实和GDT类似

    	; 为加载 IDTR 作准备
    	xor	eax, eax
    	mov	ax, ds
    	shl	eax, 4
    	add	eax, LABEL_IDT		; eax <- idt 基地址
    	mov	dword [IdtPtr + 2], eax	; [IdtPtr + 2] <- idt 基地址
    	cli
    	lidt [IdtPtr]
    

    一定要记得提前关中断

    还要记得在进入32位代码段并初始化完成寄存器之后要调用8259A芯片的初始化函数

    [SECTION .s32]; 32 位代码段. 由实模式跳入.
    [BITS	32]
    
    LABEL_SEG_CODE32:
    	mov	ax, SelectorData
    	mov	ds, ax			; 数据段选择子
    	mov	es, ax
    	mov	ax, SelectorVideo
    	mov	gs, ax			; 视频段选择子
    
    	mov	ax, SelectorStack
    	mov	ss, ax			; 堆栈段选择子
    	mov	esp, TopOfStack
    
    	call	Init8259A
    

    目前唯一的测试用中断处理程序:

    _SpuriousHandler:
    SpuriousHandler	equ	_SpuriousHandler - $$
    	mov	ah, 0Ch				; 0000: 黑底    1100: 红字
    	mov	al, '!'
    	mov	[gs:((80 * 0 + 75) * 2)], ax	; 屏幕第 0 行, 第 75 列。
    	jmp	$
    	iretd
    

    由于初始化IDT 需要的是中断处理程序起始位置的偏移,所以才会有

    SpuriousHandler	equ	_SpuriousHandler - $$
    

    这句话(其实就是相对于32位代码段开头的偏移)

    接着就可以随便发生一个中断来测试了。结果应该会在屏幕上显示一个“!”

    如果打开了8259A芯片并启用了时钟中断,再在IDT中注册了对应的中断处理程序,我们就可以在32位下使用时钟中断了。

  • 相关阅读:
    Apache Maven(三):POM
    Mysql的数据库引擎
    一文搞懂tcp和udp
    OSI七层协议大白话解读
    模型的部分知识及ajax的后端应用
    Django之模型层第二篇:多表操作
    Django基础之模型(models)层之单表及各种查询
    Django基础之视图(views)层、模板层
    Django之路由(urls)层
    创建Django项目基础
  • 原文地址:https://www.cnblogs.com/jrdxy/p/13931073.html
Copyright © 2011-2022 走看看