Cyan's Blog

Search

Search IconIcon to open search

OS-11_中断

Last updated Nov 28, 2021 Edit Source

# 中断

2021-11-28

Tags: #OperatingSystem

# 硬件

# 中断控制器

# 中断控制器如何与CPU交互?

注意:

# 软件

# 中断描述符 / 中断门

中断门存放对中断的描述数据:

# How it works1

# Interrupt Gate In UNIX v6++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//src\include\IDT.h

/* 定义了IDT中每一个门描述符的格式 */
struct GateDescriptor
{
	unsigned short	m_Low16BitsOffset;		/*OFFSET的低16位*/
	unsigned short	m_SegmentSelector;		/*段选择子*/
	unsigned char	m_Reserved : 5;			/*保留区域,长5个bit*/
	unsigned char	m_Zero : 3;				/*全零区域*/
	unsigned char	m_Type : 4;			/*描述符类型.  0xE为中断门  0xF为陷入门*/
	unsigned char	m_System : 1;		/*1:系统描述符  0:代码、数据段描述符*/
	unsigned char	m_DPL : 2;					/*描述符访问优先级*/
	unsigned char	m_SegmentPresent : 1;		/*存在标志位*/
	unsigned short	m_High16BitsOffset;			/*OFFSET的高16位*/
}__attribute__((packed));

# IDT 中断描述符表

IDT: Interrupt Descriptor Table, 里面存放了所有的中断, 异常, 系统调用描述符

# IDTR

400

1
2
3
4
5
6
//IDT.h
struct IDTR
{
	unsigned short	m_Limit;		/* IDT的限长 */
	unsigned int	m_BaseAddress;	/* IDT的起始地址(线性地址) */
}__attribute__((packed));

# IDT in UNIX v6++

# 初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//Machine.h
class Machine
{
...
public:
	void LoadIDT();		/* 把建立好的IDT表的基地址和长度加载进IDTR寄存器 */
	void InitIDT();
	IDT& GetIDT();						/* 获取当前正在使用的IDT */
private:
	IDT* m_IDT;   /* 这里实例化了IDT */
...
1
2
3
4
5
6
7
8
9
//main.cpp
extern "C" int main0(void)
{
	Machine &machine = Machine::Instance();
...
	//init idt
	machine.InitIDT();
	machine.LoadIDT();
...
# 如何初始化IDT表

在IDT类里面只实现了"设置单个描述符"的功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//IDT.cpp
void IDT::SetInterruptGate(int number, unsigned int handler)
{
	this->m_Descriptor[number].m_Low16BitsOffset	= handler;
	this->m_Descriptor[number].m_High16BitsOffset	= handler>>16;
	this->m_Descriptor[number].m_SegmentSelector	= 0x8;
	this->m_Descriptor[number].m_Reserved	= 0;
	this->m_Descriptor[number].m_Zero		= 0;
	this->m_Descriptor[number].m_System		= 0;
	this->m_Descriptor[number].m_Type		= 0xE;	//中断门,清IF位
	this->m_Descriptor[number].m_DPL		= 0x3;
	this->m_Descriptor[number].m_SegmentPresent	= 1;
}
void IDT::SetTrapGate(int number, unsigned int handler)
{
...	//其他的和上面一样
	this->m_Descriptor[number].m_Type		= 0xF;	//陷入门,不清IF位
...	
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//machine.cpp
void Machine::InitIDT()
{
	this->m_IDT = &g_IDT;
	for (int i = 0; i <= 255; i++)
	{
	if (i < 32)
	this->GetIDT().SetTrapGate(i, (unsigned long)IDT :: DefaultExceptionHandler);
	else
	this->GetIDT().SetInterruptGate(i, (unsigned long)IDT :: DefaultInterruptHandler);
	}
	/* 初始化INT 0 - 31号异常 */
	this->GetIDT().SetTrapGate(0, (unsigned long)Exception::DivideErrorEntrance);
	...Sililar Code....
	this->GetIDT().SetTrapGate(19, (unsigned long) Exception::SIMDExceptionEntrance);
	this->GetIDT().SetInterruptGate(0x20, (unsigned long) Time::TimeInterruptEntrance);
	this->GetIDT().SetInterruptGate(0x21, (unsigned long) KeyboardInterrupt::KeyboardInterruptEntrance);
	this->GetIDT().SetInterruptGate(0x2E, (unsigned long) DiskInterrupt::DiskInterruptEntrance);
	/* 0x80号中断向量作为系统调用,设置系统调用对应的陷入门 */
	this->GetIDT().SetTrapGate(0x80, (unsigned long)SystemCall::SystemCallEntrance);
}

我们单独看其中一项:

1
this->GetIDT().SetInterruptGate(0x21, (unsigned long) KeyboardInterrupt :: KeyboardInterruptEntrance);

可以看到, 这是键盘中断, 中断号是0x21, 中断入口程序是KeyboardInterruptEntrance: 后面会介绍一个中断的完整流程.

1
2
3
4
5
6
void Machine::LoadIDT()
{
	IDTR idtr;
	GetIDT().FormIDTR(idtr);
	X86Assembly::LIDT((unsigned short *)(&idtr));
}

# 中断处理

我们有了一个完整的IDT表, 现在来看看一个中断的具体流程是什么.

# 中断隐指令2

# 中断隐指令的过程

400

  1. 查询IDT, 获得相应中断源的中断门
  2. 关中断
  3. 现场保护
    • 将当前 EFLAGS 、CS 和 EIP 寄存器的值依次压入栈( 如果中断前,进程运行在用户态,还需压入 SS 和 ESP 的值)
    • 对于会产生出错码的异常,除了将基本的EFLAGS 、CS 、EIP 压入堆栈之外,还会在后面压入一个ErrorCode
    • 中断隐指令保存的现场称为硬件现场
  4. 装入中断描述符
    • 将取得的中断门中的Segment Selector 装入CS, Offset 装入EIP。之后,CPU 将转入执行各种中断入口程序

# 中断入口程序 Interrupt service routines (ISR)

中断入口程序负责以下功能:

1
2
3
4
5
6
7
1. SaveContext();				/*保存现场*/
2. SwitchToKernel();			/*切换至核心态*/
3. CallHandler(Class, Handler); /*调用中断、异常处理子程序*/
(这里可能有例行调度)
4. RestoreContext(); 			/*恢复现场*/
5. Leave(); 					/*手工撤销栈帧*/
6. InterruptReturn();  			/*中断返回*/

# 比较不同的中断入口程序

# 中断处理时的中断保护

# 中断嵌套的一些问题

# The ideal way

  1. 关中断。CPU响应中断后,首先要保护程序的现场状态,在保护现场的过程中,CPU 不应响应更高级中断源的中断请求。否则,若现场保存不完整,在中断服务程序结束后,也就不能正确地恢复并继续执行现行程序。

  2. 保存断点。为保证中断服务程序执行完毕后能正确地返回到原来的程序,必须将原来的程序的断点(即程序计数器PC)保存起来。

  3. 中断服务程序寻址。其实质是取出中断服务程序的入口地址送入程序计数器PC。

  4. 保存现场和屏蔽字。进入中断服务程序后,首先要保存现场,现场信息一般是指程序状态字寄存器PSWR和某些通用寄存器的内容。

  5. 开中断。允许更高级中断请求得到响应。

  6. 执行中断服务程序。这是中断请求的目的。

  7. 关中断。保证在恢复现场和屏蔽字时不被中断。

  8. 恢复现场和屏蔽字。将现场和屏蔽字恢复到原来的状态。

  9. 开中断、中断返回。中断服务程序的最后一条指令通常是一条中断返回指令,使其返回到原程序的断点处,以便继续执行原程序。

# Intel Manual6

在Intel Manual里面说明了以下几点:

  • The only difference between an interrupt gate and a trap gate is the way the processor handles the IF flag in the EFLAGS register.
  • When accessing an exception- or interrupt-handling procedure through an interrupt gate, the processor clears the IF flag to prevent other interrupts from interfering with the current interrupt handler.
  • A subsequent IRET instruction restores the IF flag to its value in the saved contents of the EFLAGS register on the stack.
  • Accessing a handler procedure through a trap gate does not affect the IF flag.

在Intel手册里面没有提到中断嵌套的问题, 根据上面的叙述, 一个Interrupt Gate发起的中断不会被其他中断抢占.

# Linux Assembly Language Programming7 - by Bob Neveln
# Interrupts - Stonely Brook University Slide8

Interrupt - Slide(@nim04interruptsStonely)

# todo

# 中断处理子程序结束以后

一个完整的例子; Basic x86 interrupts | There is no magic here


  1. 本笔记里面Unix v6++相关的内容均为同济大学操作系统课程相关资料 ↩︎

  2. 找了半天也没有找到中断隐指令的英文对应词, 许多资料都只是把它当作了 Interrupt service routines (ISR)之前的一步, 没有具体说明. ↩︎

  3. Interrupt flag - Wikipedia ↩︎

  4. Non-maskable interrupt - Wikipedia ↩︎

  5. 我们常说的中断和异常到底是什么_AltlasClub_HERO联盟 这里所说的中断服务程序即前文中的中断入口程序, 也即ISR: Interrupt Service Routine ↩︎

  6. 64-ia-32-architectures-software-developer-vol-3a-part-1-manual ↩︎

  7. Linux Assembly Language Programming (Open Source Technology Series) by Bob Neveln ↩︎

    • Nima Honarmand
     ↩︎