MIT6.828 32位操作系统笔记(11)----进程管理与中断 LAB3中

MIT EDU 6.828 实验源代码

分类 MIT6.828 32位操作系统实验笔记

实验完善代码 LAB2-4下载链接 提取码:79t8

中断与异常管理

在系统运行到env_run() 函数的时候,用户进程会遇到 int $0x30系统调用,开始中断之旅。在JOS 系统中,我们发现,为了保证大家自己写的程序段的正确性,该试验安排了一些用户进程函数,以进行阶段性检查,如果这些检查函数发现写的程序不符合实验原来的设想,就会提前垮掉。每完成一小段程序,相应的用户进程对程序进行检查,以确保系统的正确性。

为了完成基本的中断和系统调用,让处理器从用户态回到内核态,要用到保护控制转移机制。

1、Interrupts 的确认

Identifier Description

0 Divide error
1 Debug exceptions
2 Nonmaskable interrupt
3 Breakpoint (one-byte INT 3 instruction)
4 Overflow (INTO instruction)
5 Bounds check (BOUND instruction)
6 Invalid opcode
7 Coprocessor not available
8 Double fault
9 (reserved)
10 Invalid TSS //无效的任务状态栈
11 Segment not present
12 Stack exception
13 General protection
14 Page fault
15 (reserved)
16 Coprecessor error
17-31 (reserved)
32-255 Available for external interrupts via INTR pin

一个进程的执行不能对内核和其他进程产生干扰,当用户进程需要执行特权指令或者内核功能时,需要一个系统调用,使处理器从用户态切换到内核态,处理完毕返回用户态。中断和异常可以完成这些功能,他们是保护控制转移机制,会使处理器从用户态切换到内核态。 为了能够保证这些控制的转移能被保护起来,处理器的中断/异常机制通常被设计为:用户态的代码无权选择内核中的代码从哪里开始执行。处理器可以确保只有在某些条件下,才能进入内核态。在x86 体系下保护控制转移机制由两个特殊的机制实现:IDT(中断描述符表) 和 TSS( 任务状态段)。

处理器只在 一条指令的结束和另一条指令开始期间才会处理Interrupts ,不会在正在运行一条指令的时候直接去处理Interrupts。但是重复前缀用于重复字符串指令,重复之间可能发生中断和异常,因此在处理重复指令的期间可以处理Interrupts。

2、中断标志以及描述符表
  • 中断使能标识 IF ((interrupt-enable flag)
    控制处理器对可屏蔽硬件中断请求的响应,注意这个标志并不会影响 NMI (Non Maskable Interrupt)不可屏蔽中断的产生。与其他标志位一样,处理器响应RESET信号清除IF。 CLI和STI指令对IF清0或者置1 。

  • 中断之间有优先级,当同一时间出现多个中断请求的时候,先处理优先级别高的。

  • IDT(中断描述符表)
    IDT 是将每个中断向量和一个描述符对应起来的一个门描述符,就是每一个中断或者异常处理程序的入口地址。处理器保证中断和异常只能够引起内核进入一些特定的,被事先定义好的程序入口点,而不是由触发异常或者中断的程序来决定运行什么处理程序。

  • 通用寄存器EFFLAGS保存的是CPU 的执行状态和控制信息,如下图:
    在这里插入图片描述

  • TF标志:跟踪标志,置1 则开启单步执行调试模式,置0则关闭。在单步执行模式下,处理器在每条指令后产生一个调试异常,这样在每条指令执行后都可以查看执行程序的状态;

  • IF 标志:中断使能标志。

3、中断门

x86 允许 256 种不同的中断或者异常入口,每个中断和异常都由唯一的整数值表示,称为中断向量。中断向量被CPU 用来作为IDT 的索引访问对应的门描述符,门描述符 = IDTR(基地址) + 中断向量(偏移地址);中断门用来指向目标代码,即段选择符号 + 偏移地址。中断门的格式如下图所示:
在这里插入图片描述
其结构struct Gatedesc 是在inc/mmu.h 中定义的,由其结构可知,中断或异常也有特权及别,由中断门描述符中的DPL约束,描述符中的段选择子中的CPL 说明中断处理程序运行的特权级别
通过表中的任意一项,处理器可以知道:

  • 需要加载到EIP 寄存器中的值,这个值指向了处理中断的中断处理程序;
  • 需要加载到CS寄存器中的值,里面还包含了这个中断处理程序的运行特权级。

中断门分为三类:Task gates、Interrupt gates、Trap gates,还有一种call gates。 为什么要区分这四种门?他们各自分别对应怎么样的应用场景? 不同中断门的区别

门用来实现从一段代码跳转到另一端代码(可能在不同的代码段,不同的特权级)时的保护机制问题。其中,

  • Interrupt gates 和 Trap gates的 区别是:Interrupt gates 会修改IF,会对中断响应屏蔽,这种区别在实际处理中的会导致什么差别呢?举例来说明:

比如操作系统捕获了一个硬件中断正在处理,又来了另一个,如果用的是 Trap gates ,那么第一个处理就被打断了,这样会造成数据崩溃。所以此时必须屏蔽掉第二个处理,这样的话可以保证第一次中断处理是“原子操作”,当然如果来的中断是不可屏蔽的中断,就另当别论。
还有一个区别是断点处理,这个中断必须暴露给用户程序进行调用,但是又要区别于中断,所以 Trap gates 来处理。

总的来说:Interrupt gates 用于处理意外发生的错误,一般指向内核空间;Trap gates用来处理人为机制中断,比如系统调用等,一般在用户空间被调用。

  • Task gates 和 Call gates的 区别是:task 是一个具体的可以运行的单位,可以运行、挂起、重启等,然后在这个单位上可以保存其运行状态到TSS 中,可以通过CALL 或者JMP指令来具体的调用一个task 程序。Task gates 和 Call gates都可以用来切换到一个task,但是Task gates 的寻址需要经过TSS 找到 code selector ,虽然这样貌似比call gates 麻烦,但是这样可以在切换的时候原来task 的上下文被自己动保存到TSS ,使用这个门来处理中断例程,那么可以使程序和其他例程隔离开来,多用于多任务处理中。

PS :这里对于不同门的区别,首先你需要明白从用户态切换到内核态的条件是什么?

用户态切换到内核态的唯一方法是异常。异常分为四种:中断、陷阱、故障和终止。这四种的区别见链接异常分类

4、TSS(任务状态段) task state segment

在现在操作系统中,TSS 是一个特殊的数据结构,一个任务的所有状态信息存储在其中。在处理器进程切换时需要TSS 来保存旧的处理器状态,以便在中断返回时恢复以前的进程。当x86 处理器发生中断或者陷阱的时候,切换到内核区域的一个堆栈也是由TSS指出的,由于JOS 中的内核态指的就是特权级0,所以 处理器用TSS中的ESP0,SS0字段来指明这个内核堆栈的位置和大小 。TSS 结构也是在inc/mmu.h 中定义的。
在这里插入图片描述

  • 中断映射布局
    在x86 体系中,中断向量 0 -31 为内部处理器异常, 31以上的中断仅被用于软件中断或者异步硬件中断。中断向量的类型在 inc/trap.h 中有定义,从定义中可知,系统调用为 int $0x30
#define T_SIMDERR   19		// SIMD floating point error

// These are arbitrarily chosen, but with care not to overlap
// processor defined exceptions or interrupt vectors.
#define T_SYSCALL   48		// system call
#define T_DEFAULT   500		// catchall

  • 中断实例的分析
    在这个例子中,我们分析在用户模式下的除0 中断。首先,我们应该注意的是:堆栈的切换。在内核模式下的异常处理中,需要将异常参数压到当前堆栈;而在用户模式下的异常/中断处理中, 需要切换到TSS 中 的SS0, ESP0 所指的堆栈

在除0 中断中,处理器首先会切换自己的堆栈,切换到TSS 中 SS0(GD_KD), ESP0 (KSTACKTOP) 所指的内核堆栈,然后把异常参数压入到内核堆栈,起始地址是KSTACKTOP:

                 +--------------------+ KSTACKTOP             
                 | 0x00000 | old SS   |     " - 4
                 |      old ESP       |     " - 8
                 |     old EFLAGS     |     " - 12
                 | 0x00000 | old CS   |     " - 16
                 |      old EIP       |     " - 20 <---- ESP 
                 +--------------------+      

因为我们要处理的是除0异常,它的中断向量是0,处理器会读取IDT 表中的0号表项,并且将CS:EIP 的值设置为 0 号中断处理函数的地址值。终端处理函数开始执行,并且处理中断。

对于某些特殊的异常,还需要压入错误码。

5、程序源码中 中断与异常的实现

JOS系统中,中断与异常的实现的代码集中在 trapentry.S 和 TRAP.C 文件中。首先,中断处理的流程如图
在这里插入图片描述
JOS 系统中 中断过程的控制流。
在这里插入图片描述

  • 中断的初始化

  • 处理器在用户态下和内核态下都可以处理异常和中断。只有当处理器从用户态切换到内核态的时候,才会自动地切换堆栈,并且把一些寄存器中的原来的值压入堆栈上,并且触发相应的中断处理函数。但是如果处理器已经由于正在处理中断而处在内核态时,此时CPU 只会向内核堆栈压入更多的值,已经处于内核态的CPU 不需要切换堆栈,所以不需要存储SS,ESP 寄存器的值。

  • 如果处理器在内核态下接受一个异常,而且由于一些原因,比如堆栈空间不足,不能把当前的状态信息(寄存器的值)压入到内核堆栈中时,那么处理器是无法恢复到原来的状态了,它会自动重启

  • 仔细阅读inc/trap.h 和 kenr/trap.h 中包含的关于中断异常相关的定义,非常重要!其中kenr/trap.h 中包含仅内核态可见的定义; inc/trap.h 包含了用户态也可见的定义。

  • 在JOS 中处理中断时,每个中断都有一个handler , 在_alltraps 之后,栈中所存的数据的格式满足了Trapframe 的结构,就方便了handler 统一调用trap() 函数;每个handler 会根据对应的中断是否有错误码入栈来压入一个伪错误码来保证压入堆栈的数据统一的格式。

  • Exercise 4
    在这里插入图片描述
    每个异常和中断应该有自己的中断/异常handler 在 trapentry.S中,并且 trap_init()应该初始化 用这些不同的handler的地址 初始化 IDT 。 每个handler的堆栈上应该包含一个指向struct Trapframe 的指针,并且调用trap()函数指向这个结构体;接下来 trap()函数 处理中断和异常,给它分配中断/异常处理函数。

  • 在 trapentry.S 中,我们需要判断每一种异常/中断 是否需要压入 error code(错误码),然后完成标号_alltraps 的部分,并在此文件中为inc/trap.h 文件中的每一个trap 加入一个入口指针。 关于中断的类型需要阅读Intel 手册,我们需要所有中断的类型以及是否需要错误码。

  • 在 trapentry.S 中,有两个重要的宏定义,这两种宏的其他操作是一致 的,向堆栈压入中断向量之后,跳转至标号为 _alltraps 处继续执行余下指令。

  • 对于_alltraps 处的处理,这是所有的 trap handle 所共同执行的代码,在调用中断处理程序之前必须向栈中压入一个 struct Trapframe * 变量,根据 Trapframe 的结构按照倒序向栈中压入所需的寄存器,Trapframe 中的部分成员已经在env_pop_tf 函数中被压入了栈中,_alltraps 处需要将剩下 的压入栈中。
    Trapframe 的结构

  • 注意 ,在完成上面的步骤之后,中断初始化并没有完成,还要在idt_init() 中设置中断门描述符,建立IDT,这里使用 SETGATE宏帮助我们完成设置。中断都是内核中完成的,其sel 应该设置为内核代码段。

本实验分为两个步骤:(1)在kern/trapentry 中定义好每个中断对应的中断处理程序;(2)在kern/trap.c 中的idt_init() 中将上一部定义的处理程序加入IDT。

//--------    inc/trap.h     ----------
// Trap numbers
// These are processor defined:
#define T_DIVIDE     0		// divide error
#define T_DEBUG      1		// debug exception
#define T_NMI        2		// non-maskable interrupt
#define T_BRKPT      3		// breakpoint
#define T_OFLOW      4		// overflow
#define T_BOUND      5		// bounds check
#define T_ILLOP      6		// illegal opcode
#define T_DEVICE     7		// device not available
#define T_DBLFLT     8		// double fault
/* #define T_COPROC  9 */	// reserved (not generated by recent processors)
#define T_TSS       10		// invalid task switch segment
#define T_SEGNP     11		// segment not present
#define T_STACK     12		// stack exception
#define T_GPFLT     13		// general protection fault
#define T_PGFLT     14		// page fault
/* #define T_RES    15 */	// reserved
#define T_FPERR     16		// floating point error
#define T_ALIGN     17		// aligment check
#define T_MCHK      18		// machine check
#define T_SIMDERR   19		// SIMD floating point error

inc/trap.h 文件中的这些中断处理程序需要在本实验的第一部分完成,这部分我们需要区分哪些是需要error code ,哪些是不需要的,查找IA-32的开发手册,找到以下关于错误码的内容。

在这里插入图片描述
(1)在trapentry.S 添加下面的代码

//trapentry.S
/*
 * Lab 3: Your code here for generating entry points for the different traps.
 */
	TRAPHANDLER_NOEC(Trap_divide, T_DIVIDE); //0
	TRAPHANDLER_NOEC(Trap_debug, T_DEBUG); //1
	TRAPHANDLER_NOEC(Trap_non_maskable, T_NMI); //2
	TRAPHANDLER_NOEC(Trap_breakpoint, T_BRKPT); //3
	TRAPHANDLER_NOEC(Trap_overflow, T_OFLOW); //4
	TRAPHANDLER_NOEC(Trap_bound, T_BOUND); //5
	TRAPHANDLER_NOEC(Trap_illop, T_ILLOP); //6
	TRAPHANDLER_NOEC(Trap_device, T_DEVICE);  //7

	
	TRAPHANDLER(Trap_dblflt, T_DBLFLT);  //8

//	TRAPHANDLER_NOEC(Trap_reserved, T_COPROC);  //9

	TRAPHANDLER(Trap_invalid_TSS, T_TSS);  //10
	TRAPHANDLER(Trap_segnp, T_SEGNP);  //11
	TRAPHANDLER(Trap_stack, T_STACK);  //12
	TRAPHANDLER(Trap_gpflt, T_GPFLT);  //13
	TRAPHANDLER(Trap_pgflt, T_PGFLT);  //14

//	TRAPHANDLER_NOEC(Trap_reserved, T_RES);  //15

	TRAPHANDLER_NOEC(Trap_fperr, T_FPERR);  //16
	TRAPHANDLER_NOEC(Trap_aligment, T_ALIGN);  //17
	TRAPHANDLER_NOEC(Trap_mchk, T_MCHK);  //18
	TRAPHANDLER_NOEC(Trap_SIMD, T_SIMDERR);  //19

/*
 * Lab 3: Your code here for _alltraps
 */
_alltraps:
	pushl %ds; //4字节
	pushl %es; //4字节
	
	pushal; //struct PushRegs tf_regs

	movl GD_KD , %eax;
	movb %ax , %ds;
	movb %ax , %es;
	
	pushl %esp; //

	call trap;

(2)在trap.c文件中添加下面的代码

///trap.c///
void Trap_divide(); //0
void Trap_debug();//1
void Trap_non_maskable(); //2
void Trap_breakpoint();//3
void Trap_overflow();//4
void Trap_bound();//5
void Trap_illop();//6
void Trap_device();//7
void Trap_dblflt();//8
//extern void Trap_reserved();//9
void Trap_invalid_TSS();//10
void Trap_segnp();//11
void Trap_stack();//12
void Trap_gpflt();//13
void Trap_pgflt();//14
//extern void Trap_reserved();//15
void Trap_fperr();//16
void Trap_aligment();//17
void Trap_mchk();//18
void Trap_SIMD();//19
void Trap_syscall();//48

void
trap_init(void)   
{
	extern struct Segdesc gdt[];

	// LAB 3: Your code here.
	SETGATE(idt[T_DIVIDE], 0, GD_KT, Trap_divide, 0   );  //0
	SETGATE(idt[T_DEBUG], 0, GD_KT, Trap_debug, 0   );//1
	SETGATE(idt[T_NMI], 0, GD_KT, Trap_non_maskable, 0   );//2
	SETGATE(idt[T_BRKPT], 0, GD_KT, Trap_breakpoint, 3   );//3  端点不需要内核优先级
	SETGATE(idt[T_OFLOW], 0, GD_KT, Trap_overflow, 0   );//4
	SETGATE(idt[T_BOUND], 0, GD_KT, Trap_bound, 0   );//5
	SETGATE(idt[T_ILLOP], 0, GD_KT, Trap_illop, 0   );//6
	SETGATE(idt[T_DEVICE], 0, GD_KT, Trap_device, 0   );  //7
	SETGATE(idt[T_DBLFLT], 0, GD_KT, Trap_dblflt, 0   );//8

//	SETGATE(idt[T_COPROC], 0, GD_KT, Trap_reserved, 0   );//9

	SETGATE(idt[T_TSS], 0, GD_KT, Trap_invalid_TSS, 0   );//10
	SETGATE(idt[T_SEGNP], 0, GD_KT, Trap_segnp, 0   );//11
	SETGATE(idt[T_STACK], 0, GD_KT, Trap_stack, 0   );//12
	SETGATE(idt[T_GPFLT], 0, GD_KT, Trap_gpflt, 0   );	//13
	SETGATE(idt[T_PGFLT], 0, GD_KT, Trap_pgflt, 0   );//14

//	SETGATE(idt[T_RES], 0, GD_KT, Trap_reserved, 0   );//15

	SETGATE(idt[T_FPERR], 0, GD_KT, Trap_fperr, 0   );//16
	SETGATE(idt[T_ALIGN], 0, GD_KT, Trap_aligment, 0   );//17
	SETGATE(idt[T_MCHK], 0, GD_KT, Trap_mchk, 0   );//18
	SETGATE(idt[T_SIMDERR], 0, GD_KT, Trap_SIMD, 0  );//19

	SETGATE(idt[T_SYSCALL], 0, GD_KT, Trap_syscall, 3  );//19
	// Per-CPU setup 
	trap_init_percpu();
}

(3)自此Exercise 4 完成了,make qemu
在这里插入图片描述

(4)测试下分数
make grade, 第一部分的30分全部拿到了!继续下面的实验。
在这里插入图片描述

分类 MIT6.828 32位操作系统实验笔记

本文参考文章 http://grid.hust.edu.cn/zyshao/OSEngineering.htm
推荐这位博主系列的文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值