mit6.828-lab3 异常与中断-进入内核

概述

只有内核是被信任的第三方!!!
只有内核能够执行特权指令,处理器通过user/kenel 模式位来实施这种保护。如果用户程序需要一个特权服务,它必须要求内核代表它执行,用户程序如何转移到内核地址空间??内核如何转移到用户地址空间??这就是接下来要探讨的问题!!!
有三种事件,处理器会切换到内核的地址空间:(1)系统调用(调用了一个陷阱指令,比如,INT XX);(2) 中断,比如外部设备希望得到处理器的注意;(3)非法指令,用户程序使自己进入了一个bad state,通过转移控制进入内核,内核可以清理用户程序的混乱。

虽然有三种不同的事件,但是它们都会调用相同的机制转移控制内核。这个机制有三个步骤组成
**(a)**改变处理器 to kernel mode
**(b)**保存和重新加载MMU to kernel address space;
(c) 保存旧的PC,加载kernel entry point进入PC 。
不同的处理器实现细节上有所不同,但是这三个步骤是一起原子实现的。

这种控制转移必须受到保护,为了实现这种保护机制,当异常/中断发生的时候,处理器的中断/异常机制要设计当前运行的代码不会随意的选择内核的进入位置,相反,处理器确保进入内核必须在可控的条件下。X86使用两种机制一起提供这种保护。
1)中断描述符表 。处理器确保中断和异常只能导致内核进入几个特定的、提前定义好的入口点,这些入口点由内核本身决定,而不是由中断或异常发生时运行的代码决定。
x86允许256个不同的中断/异常入口点进入内核,每个都有一个不同的interrupt vector,一个vector是一个在0-255之间的数,the cpu 使用这个vector作为一个索引,into处理器的中断描述符表IDT,内核在内核私有的内存中建立,像GDT一样,处理器会在该表中加载相应的表项:
(1)加载值到指令寄存器(EIP)(跟上面PC一个意思,ARM体系中叫PC,x86中叫eip),指向了用来处理这种异常的内核代码。(对应上面的步骤c)
(2)加载值到代码段寄存器(CS),这个值包含0-1位的特权级,异常处理函数将在这个等级允许。
(对应上面的步骤b)

一个中断描述符的格式

2)任务状态段(Task State Segment),处理器需要一个地方保存旧的处理器状态在中断或异常发生之前,such as 调用异常处理程序之前的EIP和CS值,这样异常处理程序稍后可以恢复原先的状态,从被中断的代码的离开的地方,但是这个保存old processer state 的区域必须被保护起来,不同受到非特权模式下的代码影响,否则被修改了会危害内核。
出于这个原因,当处理器take an interrupt or trap that 引起特权级的改变,从用户模式到内核模式,它就会切换到 a stack in kernel’s memory,一个叫做task state segment 指明了段selecotr和地址where这个栈在的地方,处理器在新的栈上会pushes ,SS,ESP,EFLAFS,CS,EIP和一个可选的错误码,接着他就从中断描述符加载CS,EIP,设置ESP和SS来引用这个新的栈。

异常和中断的类型

所有同步的,处理器内部自己生成的异常使用interrupt vectors 0-31,映射到IDT entries 0-31,比如,page fault 总是引起一个异常通过向量14,大于31的interrupt vector used only by 软件中断,(可以由INT指令生成),或者异步硬件中断(caused by external 设备在它们需要注意的时候)。

最后以一个例子收尾,也对上面的内容做个总结,
假如处理器在用户模式下执行代码,遇到了一条除0的指令,
1.处理器会切换栈到TSS中定义的 SS0,ESP0字段去,
2 处理器会pushes异常的参数进入内核栈
图1
对于某些异常,除了上面的五个字,处理器还会再多push一个error code字进入栈中。如下图
图2

3.因为我们处理的是除零错误,对应的中断向量是0,处理器会读取IDT entry 0接着设置CS:EIP,指向entry 指定的处理函数。

4,处理函数获取了控制,处理异常。

/* TRAPHANDLER defines a globally-visible function for handling a trap.
 * It pushes a trap number onto the stack, then jumps to _alltraps.
 * Use TRAPHANDLER for traps where the CPU automatically pushes an error code.
 *
 * You shouldn't call a TRAPHANDLER function from C, but you may
 * need to _declare_ one in C (for instance, to get a function pointer
 * during IDT setup).  You can declare the function with
 *   void NAME();
 * where NAME is the argument passed to TRAPHANDLER.
 */
#define TRAPHANDLER(name, num)						\
	.globl name;		/* define global symbol for 'name' */	\
	.type name, @function;	/* symbol type is function */		\
	.align 2;		/* align function definition */		\
	name:			/* function starts here */		\
	pushl $(num);							\
	jmp _alltraps

/* Use TRAPHANDLER_NOEC for traps where the CPU doesn't push an error code.
 * It pushes a 0 in place of the error code, so the trap frame has the same
 * format in either case.
 */
#define TRAPHANDLER_NOEC(name, num)					\
	.globl name;							\
	.type name, @function;						\
	.align 2;							\
	name:								\
	pushl $0;							\
	pushl $(num);							\
	jmp _alltraps

.text

上面这两个函数的代码都是在处理器切换到内核栈后需要做的动作,然后调用跳转到_alltraps:

struct Trapframe {
	struct PushRegs tf_regs;
	uint16_t tf_es;
	uint16_t tf_padding1;
	uint16_t tf_ds;
	uint16_t tf_padding2;
	uint32_t tf_trapno;
	/* below here defined by x86 hardware */
	uint32_t tf_err;
	uintptr_t tf_eip;
	uint16_t tf_cs;
	uint16_t tf_padding3;
	uint32_t tf_eflags;
	/* below here only when crossing rings, such as from user to kernel */
	uintptr_t tf_esp;
	uint16_t tf_ss;
	uint16_t tf_padding4;
} __attribute__((packed));

在跳转到_alltraps后我们现在的栈里面有:
在图2后面push了一个trapno,现在我们还要保存ds,es的值,所以然后push通用寄存器的值,

struct PushRegs {
	/* registers as pushed by pusha */
	uint32_t reg_edi;
	uint32_t reg_esi;
	uint32_t reg_ebp;
	uint32_t reg_oesp;		/* Useless */
	uint32_t reg_ebx;
	uint32_t reg_edx;
	uint32_t reg_ecx;
	uint32_t reg_eax;
} __attribute__((packed));
	_alltraps:
	push %ds
	push %es

	pushal   //将所有通用寄存器的值压入内核栈中   
	pushl $GD_KD    
	popl  %ds    //更新DS GD_KD
	pushl $GD_KD
	popl %es    //更新ES GD_KD
	pushl %esp //将esp作为Trapframe*tf参数值放入
	call trap

进入trap函数,根据trapno进行trap_dispatch;处理异常。

[1]https://pdos.csail.mit.edu/archive/6.097/lec/l7.html
[2]MIT 6.828:实现操作系统 | Lab 3A:中断初始化
[3]中断描述符表
[4]6.828/lab3

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值