实验3前篇——X86的中断管理
Posted yiye_01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实验3前篇——X86的中断管理相关的知识,希望对你有一定的参考价值。
实验3主要内容是用户进程的创建,运行,销毁;从操作系统的角度来看用户进程,可以看作是动态加载可运行的程序,然后进行不断地动态切换,这里我们当然也可以从虚拟机的角度来考察操作系统 —— 操作系统其实是在硬件上为用户进程创建虚拟机,每个虚拟机都是单独运行在整个硬件平台上的;而从用户进程看操作系统,则是固定的程序部分,并且提供硬件接口的程序。当我们需要理解该实验的内容时,可以想象一下,程序运行的流程 —— 它总是被加载到内存中,然后将PC(程序计数器)指向程序进入点运行之,但是正常程序流程只有顺序,判断,循环的流程,所以最终只能在程序写的一段代码中反复执行或者退出;当然可以动态加载用户进程,所以对于每个进程都应该运行流程是一样的。但是这样的话,其中一个进程运行,另外的进程就无法运行了,所以这里在程序运行之外还需要很重要的一种机制保证程序能够被切换,它就是中断。而且对于硬件处理,也需要异步来提高处理效率,所以操作系统就使用中断来作为基本工具实现进程的切换,而对于中断的实现则是依赖于处理器构架,从而我们为了更好的理解实验3的内容,就需要对x86处理器的中断有个全面而深入的理解。X86的中断管理的主要内容也是以Intel软件开发手册作为参考,但是遗憾的是没有程序代码可以编写用于实现我们的理解,当然在实验3以及之后的内容中就会涉及到。本节我们将从两个部分来描述中断——8086中断与32位保护模式下的中断,对于前者我们已经在前面的实验得到很好的使用与介绍,所以我们只需要简单的介绍之,而对于保护模式下的中断才是我们介绍的重点。
为了详实而具体介绍本文内容,我们先复习一下一些重要的概念,如下:
1.中断源——能够引起中断原因或提出中断请求的设备和异常故障;可以分为外部中断与内部中断
2.中断模式——对于外部中断来说,连接到处理器中断线上的触发方式——沿触发或者电平触发。以及可屏蔽中断与不可屏蔽中断。
3.中断优先级——如果在响应一个中断,执行中断处理的过程中,又有新的中断事件发生而发出了中断请求,应该如何处理也取决于中断事件的优先级
4.中断号——每一个中断源都对应一个中断号,用于识别不同的中断事件
5.中断屏蔽——通过设置相应的中断屏蔽位,禁止响应某个中断
6.中断服务——对中断的响应的程序
一)8086中断
当系统启动时,bios接管了整个系统的控制,处理器处于8086的状态;而bios对外(指的是对基于bios运行的程序)提供接口的主要方式就是中断,基本上能够使用系统所有的服务。而8086对中断的处理是很简单的,对于每个中断源对应一个中断号,每个中断号有一组中断向量(cs:ip)对应着每个中断服务的入口(存在于内存的最低1024个字节中)。详细的终端服务表如下:
INT 00 - CPU-generated - DIVIDE ERROR(除0)
INT 01 - CPU-generated - SINGLE STEP; (80386+) - DEBUGGING EXCEPTIONS(单步调试)
INT 02 - external hardware - NON-MASKABLE INTERRUPT(外部中断,不可屏蔽中断)
INT 03 - CPU-generated - BREAKPOINT(断点)
INT 04 - CPU-generated - INTO DETECTED OVERFLOW(算术溢出)
INT 05 - PRINT SCREEN; CPU-generated (80186+) - BOUND RANGE EXCEEDED(访问越界)
INT 06 - CPU-generated (80286+) - INVALID OPCODE(非法操作)
INT 07 - CPU-generated (80286+) - PROCESSOR EXTENSION NOT AVAILABLE(协处理器访问不到)
INT 08 - IRQ0 - SYSTEM TIMER; CPU-generated (80286+)( 系统时钟)
INT 09 - IRQ1 - KEYBOARD DATA READY; CPU-generated (80286,80386)(键盘数据ok)
INT 0A - IRQ2 - LPT2/EGA,VGA/IRQ9; CPU-generated (80286+)(EGA/VGA的中断)
INT 0B - IRQ3 - SERIAL COMMUNICATIONS (COM2); CPU-generated (80286+)(串口2)
INT 0C - IRQ4 - SERIAL COMMUNICATIONS (COM1); CPU-generated (80286+)(串口1)
INT 0D - IRQ5 - FIXED DISK/LPT2/reserved; CPU-generated (80286+)(一般保护异常)
INT 0E - IRQ6 - DISKETTE CONTROLLER; CPU-generated (80386+)(软盘控制器)
INT 0F - IRQ7 - PARALLEL PRINTER(并口打印)
INT 10 - VIDEO; CPU-generated (80286+)(视频)
INT 11 - BIOS - GET EQUIPMENT LIST; CPU-generated (80486+)(对齐检测)
INT 12 - BIOS - GET MEMORY SIZE(查看系统内存)
INT 13 - DISK(磁盘)
INT 14 - SERIAL(串口)
INT 15 - CASSETTE
INT 16 - KEYBOARD(键盘)
INT 17 - PRINTER(打印机)
INT 18 - DISKLESS BOOT HOOK (START CASSETTE BASIC)
INT 19 - SYSTEM - BOOTSTRAP LOADER
INT 1A - TIME(时钟)
INT 1B - KEYBOARD - CONTROL-BREAK HANDLER
INT 1C - TIME - SYSTEM TIMER TICK(系统时钟)
INT 1D - SYSTEM DATA - VIDEO PARAMETER TABLES
INT 1E - SYSTEM DATA - DISKETTE PARAMETERS
INT 1F - SYSTEM DATA - 8x8 GRAPHICS FONT
INT 20 - DOS 1+ - TERMINATE PROGRAM
如上只是描述了一些常用的,其他的见附件的资源。对于我们了解上表的原因是为了对比32位保护模式下的中断处理。
对于中断向量我们可以从qemu的bios中dump出来(dump binary int 0 1024),然后用hexdump -x打开得到下图,当中断发生就会跳入到设置cs:ip地址执行代码,当然也可以通过寄存器来传递若干参数:
中断优先级:可以参考32位保护模式下的优先级。
中断屏蔽:处理器中断的屏蔽位——EFLAGS.IF位。对于外部中断,由外部中断控制器的寄存器来屏蔽对应的中断。处理中断的指令:
CLI(关中断), STI(开中断), PUSHF与 POPF(通过堆栈修改EFLAGS.iF), IRET(中断返回)
当介绍完了中断概念在8086的模式下的实现后,我们还要提的是intel工程师写的bios接口的函数(在linux源码中找到arch/x86/boot/bioscall.S):
输入:int_no为中断号
ireg为中断发生前所有寄存器的指针,用于传递中断参数
oreg为中断发生后的所有寄存器的指针,用于获取中断结果
void intcall(u8 int_no, const struct biosregs *ireg, struct biosregs *oreg);//实现的功能为c语言程序代码提供bios的应用接口(中断)。
它的具体实现是以汇编代码形式实现,详细如下,当然用了巧妙的处理方式(因为int指令之后只能跟常数表示中断号):
/*
* "Glove box" for BIOS calls. Avoids the constant problems with BIOSes
* touching registers they shouldn't be.
*/
/*
*arg1-->eax--中断号
*arg2-->edx--输入的寄存器组指针
*arg3-->ecx--输出的寄存器组指针
*/
.code16
.section ".inittext","ax"
.globl intcall
.type intcall, @function
intcall:
/* Self-modify the INT instruction. Ugly, but works. */
cmpb %al, 3f
je 1f
movb %al, 3f/*中断号通过寄存器EAX传递过来,修改int指令的操作数*/
jmp 1f /* Synchronize pipeline */
1:
/* Save state,注意这个保存的过程跟我们定义的struct biosregs是一致的 */
pushfl
pushw %fs
pushw %gs
pushal
/* Copy input state to stack frame ,拷贝我们传递的参数到栈顶*/
subw $44, %sp
movw %dx, %si
movw %sp, %di
movw $11, %cx
rep; movsd
/* Pop full state from the stack,将传入的参数导入到所有寄存器中 */
popal
popw %gs
popw %fs
popw %es
popw %ds
popfl
/* Actual INT ,执行对应中断*/
.byte 0xcd /* INT opcode */
3: .byte 0
/* Push full state to the stack ,再将所有中断之后的状态保存到堆栈中*/
pushfl
pushw %ds
pushw %es
pushw %fs
pushw %gs
pushal
/* Re-establish C environment invariants,重新创建c语言执行环境,因为中断有可能将运行状态给破坏了 */
cld
movzwl %sp, %esp
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
/* Copy output state from stack frame ,拷贝中断之后的状态到输出寄存器组地址*/
movw 68(%esp), %di /* Original %cx == 3rd argument */
andw %di, %di
jz 4f
movw %sp, %si
movw $11, %cx
rep; movsd
4: addw $44, %sp
/* Restore state and return,返回程序*/
popal
popw %gs
popw %fs
popfl
retl
.size intcall, .-intcall
如以上的流程分析,可以看出堆栈在数据保存与传递过程中起了很核心作用。在用户进程的实现与调度过程中有很多使用,所以需要对过程有很好的理解。我们再从这个函数的功能来分析一下,它为c语言提供了调用bios服务的接口;所以这对我们理解整个系统有了一个基本而核心的概念。系统的服务是通过中断来提供,而程序访问系统服务,需要通过寄存器与堆栈调用中断来实现。这对于我们操作系统的实现的启示是,应用程序访问操作系统的服务也是通过中断的。
另外结构体struct biosregs 定义如下:
struct biosregs
union
struct
u32 edi;
u32 esi;
u32 ebp;
u32 _esp;
u32 ebx;
u32 edx;
u32 ecx;
u32 eax;
u32 _fsgs;
u32 _dses;
u32 eflags;
;
struct
u16 di, hdi;
u16 si, hsi;
u16 bp, hbp;
u16 _sp, _hsp;
u16 bx, hbx;
u16 dx, hdx;
u16 cx, hcx;
u16 ax, hax;
u16 gs, fs;
u16 es, ds;
u16 flags, hflags;
;
struct
u8 dil, dih, edi2, edi3;
u8 sil, sih, esi2, esi3;
u8 bpl, bph, ebp2, ebp3;
u8 _spl, _sph, _esp2, _esp3;
u8 bl, bh, ebx2, ebx3;
u8 dl, dh, edx2, edx3;
u8 cl, ch, ecx2, ecx3;
u8 al, ah, eax2, eax3;
;
;
;
如上实现的好处是,通过联合的方式,简单的进行整数的分割而不使用位操作。注意寄存器的顺序一定要根据如下顺序(PUSHF)来EAX, ECX, EDX, EBX, ESP (original value), EBP, ESI, and EDI。
二)32位保护模式下的中断与异常——详情见Intel开发手册第6章
32位保护模式中断与异常的概念:
1.中断:在程序运行中随机出现,主要是对硬件事件的响应或者软件通过INT指令调用
2.异常:在程序运行过程中处理器检测到的程序错误。它根据不同的严重程度主要分为故障,陷阱,中止。
中断与异常源:详情见下表,它的主要来源于外部硬件中断,程序运行错误,硬件检测,程序中断指令:
外部硬件中断被“local APIC”所管理控制。当local APIC被禁用时,INTR中断——系统从外部中断控制器读取中断向量(比如:8259A),NMI——被用于中断向量2描述。对于软件指令执行的中断,不受EFLAGS.IF影响。
在上表中有Error Code的一列,主要用于反应在产生中断时,所发生的错误信息,它会被放到当前堆栈上。
中断号:对于0-31号被处理器所使用,外部中断使用32-255的号。
中断优先级,详情见下表:
中断服务:对于中断服务的获取,一般需要设置中断服务进入点,然后调用中断流程。
1.设置服务进入点——IDT(中断描述表,类似GDT),基本结构如下:
如上图所示,IDT由中断描述寄存器(IDTR)指向,加载与读取它的指令为LIDT/SIDT.
对于每个IDT门(Gate)的描述具体如下图示:
如上图所示:IDT门可以分为3类Task Gate,Trap Gate,Interrupt Gate。
1.调用中断流程——主要有两个过程:其一为调用中断进入点程序,其二为堆栈现场保存。
A)调用中断进入点程序:
当中断发生之后,系统会根据配置的中断向量访问IDT,然后通过IDT门中的段选择符,从GDT或者LDT中读取程序的基地址,最后与IDT门设置的偏移量(offset)相加得到中断程序进入点。
B)堆栈现场保存:
当中断发生在同一个执行指令权限时,不需要进行堆栈切换,直接将中断发生时的EFLAGS,CS,EIP,ErrorCode依次压入堆栈,保存运行的现场,当然也是为了IRET返回做好准备。
当中断发生在不同的执行指令权限时,需要进行堆栈切换——首先需要选择执行堆栈(切换之后的堆栈),该堆栈是由第0个TSS(任务状态段)段获得,然后将之前的堆栈(SS,ESP)压入执行堆栈,最后再将EFLAGS,CS,EIP,ErrorCode依次压入堆栈。而在此时执行IRET时,也需要切换堆栈,而且也能在不同的指令执行权限中跳转。操作系统一般使用两个执行权限——0与3,内核部分运行在0级,而用户进程运行在3级。从0级切换到3级,都是使用IRET进行,而由3级到0级就由中断执行。
当我们在理解中断内容时会有另外一个概念会值得我们理解——任务管理,所以我们也需要介绍与我们实现相关的部分。
三)任务管理——第7章
任务管理在intel开发手册中有很多的内容,对于我们需要了解的部分主要是关于堆栈切换的选用,它会使用第0个TSS段设置的堆栈,所以我们需要理解任务管理结构,先看基本结构图:
如上图所示,任务管理器的基本结构体被任务寄存器(TR)指向的GDT表项(TSS描述符),通过读取设置的TSS段地址得到TSS段的信息。
TR寄存器的操作指令为:LTR/STR。
TSS描述符:
TSS段:
如上如所示,当堆栈切换时,切换到指令执行权限为0时,使用堆栈SS0与ESP0.
一叶说:中断管理是每个处理器构架都会涉及的基本处理器功能,当然它也为进程间切换与运行模式切换提供了最基本的服务。而对于操作系统来说,为了区分内核部分与用户进程部分,需要用不同执行权限来分割,在不同的执行权限的切换也需要借助中断,这样也为用户进程调用内核服务提供了一种基本结构——系统调用,但从系统效率来说,执行中断开销是很大的,所以处理器也会提供其他快速执行系统调用的方式来实现。处理器执行的任务管理与中断处理的流程,都需要一系列的数据结构来描述,当然也会对这些结构进行动态配置,从而需要实现必要的接口,用于系统调用与其他部分使用。
以上是关于实验3前篇——X86的中断管理的主要内容,如果未能解决你的问题,请参考以下文章