中断与异常

中断及异常是学习操作系统必备知识,通常CPU时将其设计作为打破当前执行程序流程的一种手段,同时也是cpu与外部硬件交互的一种手段。

一般而言中断可以分为可以屏蔽(maskable)和不可屏蔽类型(non-maskable)。

按照同步模式划分分为异步和同步中断。

  • 同步中断一般是由CPU执行指令时产生,即cpu指令终止执行之后cpu才会发出中断。
  • 异步中断一般时由其他硬件设备产生通知cpu产生的。

异常一般是由软件执行时产生错误或者cpu内存执行时产生错误。而在一些特殊情况,比如 debug模式 但单步跟踪 或者执行到断点函数等一些非error情形,也会产生异常

Exceptions usually occur as a result of software execution errors or other internal-processor errors.Exceptions can also occur in non-error situations, such as debug-program single-stepping or addressbreakpoint detection

一般内核或者cpu硬件根据不同的异常类型而需要做出不同的响应。根据amd手册,x86架构下异常主要可以分为故障(fault)、陷阱(trap)、中止(abort)三种类型:

  • 故障(fault):故障是在中断指令执行前报告的精确异常。通常是cpu执行指令时遇到一些不期望错误而产生,当然也有些异常产生是正常现象 比如page fault。有些故障产可以修复,当修复完毕之后,程序可以在不失去连贯性的情况下重新开始执行。
  • 陷阱(trap):陷阱是在中断指令执行后报告的精确异常,当中断指令导致陷阱执行完之后,其相关程序继续执行,不影响连贯性,常见导致进入陷阱常见主要就是用于调式单步跟踪 或断点等。
  • 中止(abort):中止是一种非精确异常,一般是程序遇到严重的错误,比如硬件故障 等,其程序控制单元出现问题,不能保存其引起中止指令的精确位置。由于不能保证其指令精确位置,因此不运行进行程序修复。

• Faults—A fault is a precise exception that is reported on the boundary before the interrupted instruction. Generally, faults are caused by an undesirable error condition involving the interrupted instruction, although some faults (such as page faults) are common and normal occurrences. After the service routine completes, the machine state prior to the faulting instruction is restored, and the instruction is retried
• Traps—A trap is a precise exception that is reported on the boundary following the interrupted instruction. The instruction causing the exception finishes before the service routine is invoked. Software interrupts and certain breakpoint exceptions used in debugging are traps.
• Aborts—Aborts are imprecise exceptions. The instruction causing the exception, and possibly an indeterminate additional number of instructions, complete execution before the service routine is invoked. Because they are imprecise, aborts typically do not allow reliable program restart.

中断与异常区别

中断一般是由外部或者内部硬件执行错误发送一个异常信号到cpu端而产生的,或者由软件使用中断指令而强制产生一个中断而产生的。而异常一般是由cpu在执行程序时遇到非正常条件而产生的。amd64架构一般讲异常和中断一起来讲,手册中没有特别说明就是interrupt通常指的时异常和中断两者情况。

Interrupts can be caused when system hardware signals an interrupt condition using one of the external-interrupt signals on the processor. Interrupts can also be caused by software that executes an interrupt instruction. Exceptions occur when the processor detects an abnormal condition as a result of executing an instruction. The term “interrupts” as used throughout this volume includes both interrupts and exceptions when the distinction is unnecessary.

向量号(vector)

amd64 cpu架构中为每个异常或中断都划分一个对应的编号称之为中断向量号(vector),范围为0~255。其中0~31被amd64 cpu保留使用,其他32~255可以由外部用户定义。

Every interrupt and exception is assigned a unique number which is called - vector numberVector number can be any number from 0 to 255. There is common practice to use first 32 vector numbers for exceptions, and vector numbers from 32 to 255 are used for user-defined interrupts.

amd64对的0~255向量号使用情况分布如下:

vector interrupt/Exception Mnemonic Type Source Case Generated by General Purpose instructions
0 Divide-By-Zero-Error #DE Fault Software DIV,IDIV instructions yes
1 Debug #DB Fault or Trap Internal Instruction accesses and data accesses yes
2 Non-Maskable-Interrupt NMI -- External External NMI signal no
3 Breakpoint #BP Trap Software INT3 instruction yes
4 Overflow #OF Trap Software INT) instruction yes
5 Bound-Range #BR Fault Software BOUND instruction yes
6 Invalid-Opcode #UD Fault Internal INvalid instruction yes
7 Device-Not-Available #NM Fault Internal x87 instruction no
8 Double_Fault #DF Abort Internal Exception during an interrupt/execption transfer indirectly
9 Coprocessor-Segment-Overrun - -- External Unsupport(reserved) -
10 Invalid-TSS #TS Fault Internal Task-state segment access and task switch yes
11 Segment-Not-Present #NP Fault Internal Segment access through a descriptoir yes
12 Stack #SS Fault Internal SS register loads and stacj referebces yes
13 General-Protection #GP Fault Internal Memory accesses and protection checks yes
14 Page-Fault #PF Fault Internal Memory access when paging enabled yes
15 Reserved -- -- -- -- --
16 x87 Floating-Point Exception-Pending #MF Fault Software x87 floating-point and 64-bit media floating-point instructions no
17 Alignment-Checkk #AC Fault Internal Memory accesses yes
18 Machine-Check #MC Abort

Internal

External

Model specific yes
19 SIMD Floating-Point #XF Fault Internal 128-bit media floating-point instructions no
20 Reserved -- -- -- -- --
21 Control-Protection #CP Fault Internal Control transfers yes
22-27 Reserved -- -- -- -- --
28 Hypervisor Injection Exception #HV -- -- Event injection no
29 VMM Communication Exception #VC Fault -- Virtulization event no
30 Security #SX -- External Security exception no
31 Reserved -- -- -- -- --
0~255 External Interrupts(Maskable) -- -- External External interrupt signaling no
0-255 Software Interrupts -- -- Software INT instruction yes

中断处理程序interrupt handler

内核都需要为每个中断向量设置相应得中断处理程序,以便当中断发生时对相应中断进行处理。

An interrupt handler is privileged software designed to identify and respond to the cause of an interrupt or exception, and return control back to the interrupted software.

中断处理程序interrupt handler一般就是相对应中断处理函数的地址(64位系统为8个字节,32位系统位4个字节),每个中断对应自己的中断处理函数入口,当一个中断发生时,操作系统内核需要确保以下步骤程序:

  • 暂定当前正在执行的进程,保存相关进程信息。
  • 根据相应中断向量号,查找到相对应的中断程序入口interrupt handler,并执行该中断处理程序
  • 中断处理程序结束后,按照之前保存的信息,恢复被中断的进程使之能够继续执行。

Gate Descriptors(门描述符)

x86架构cpu为了描述一个个中断,仅仅使用interrput handler不够,还需要对应其他信息,该信息称之为Gate Descriptor门描述符,用于描述一个中断。门描述符里面不仅包含其他中断处理函数地址信息(interrupt handler),还包含了其他一些所需要信息,amd64 cpu主要将Gate Descriptor分为以下三种类型:

  • Call Gate:  该门通常用于访问控制,允许拥有低级别权限的代码调用高优先级代码,可以通过该方法允许用户应用程序调用kernel函数,经典使用场景就是系统调用。
  • Interrupt Gate: 所有的linux 中断处理程序都通过中断门来激活,且限定在内核态,用户进程不能访问一个中断门。
  • Trap Gate: 与interrupt gate类似,所有异常处理程序都是通过陷阱门(trap gate)实现的,同样用户进程不能访问。
  • Task Gate:不能被用户进程访问,x86 硬件调度任务切换功能即任务调度完全由硬件来完成,这样任务切换也完全有硬件完成。但是在有些场景下,软件需要根据需要进程任务切换时,软件可以通过 FAR CALL或 FAR JMP命令来强制任务切换,上述命令会触发中断从而触发该门,在该中断程序中可以完成一些任务切换初始化任务,但是该中断处理函数需要较高优先级。

Call Gate 格式(long mode)

long mode模式下,amd64 cpu gate descriptors大小为16个字节,格式如下:

  • target offset[0:15]  、target offset[16:31] 、target offset[32:63] 存储的是中断处理函数interrupt handler函数地址 共8个字节共64位,依次从存储的是地址低16为,中间16位和高32位部分。
  • target selector::占用16位,作为索引用于从GDT或者LDT中得到segment,该selector会被加载到CS 段寄存器中。
  • Type: 主要用于描述门类型,格式定义如下:

  • DPL(Descriptor Privilege-Level): 主要时定义cpu使用权限,可以设置位0~3,其中3 权限最少,0权限最多。
  • p(present) :是否存在
  • 其他位保留。

Interrupt Gate/Trap Gate

interrupt gate和trap gate格式相同,也是占用16个字节,格式如下:

  • IST  : 主要时用作TSS的索引,当IST不为0时,所为索引从TSS中选择作为IST指针,加载带RSP寄存器中。当IST为0时,使用legacy stack-switching 机制。
  • 其他字段意义与call gate相同。

struct gate_struct

内核中专门为门描述符定义了gate_struct结构,位于(arch\x86\include\asm\desc_defs.h)文件中:

struct gate_struct {u16     offset_low;u16      segment;struct idt_bits bits;u16        offset_middle;
#ifdef CONFIG_X86_64u32     offset_high;u32     reserved;
#endif
} __attribute__((packed));
  • offset_low: 中断处理函数interrupt handle 地址的低16位。
  • segment:target selector
  • struct idt_bits   bits:定义了32~47位
  • u16   offset_middle:对应target offset[31:16]即中断处理函数interrupt handle 地址的中间16位
  • offset_high:对应target offset[63:32]即中断处理函数interrupt handle 地址的高32位
  • reserved:预留

struct idt_bits

struct idt_bits对应的是32~47位,定义结构如下:

struct idt_bits {u16        ist : 3,zero    : 5,type    : 5,dpl : 2,p   : 1;
} __attribute__((packed));
  •  ist 占3位
  • zero 5位不用 保留 置0.
  • type: 门类型
  • dpl: 权限管理位
  • p: present是否存在,必须置1

ist 字节域

amd64 log mode 模式支持通过ist字节域为每个中断函数设置相对应专属栈空间,(其中ist为interrupt-stack-table简称)如下:

 amd log mode共支持IST1_SSP ~IST7_SSP共7个可配置的栈空间设置,IST域为相对应的IST索引,MSR_INTERRUPT_SST_TABLE为存放的SSO表基地址,其中unused为0,当每次硬件发生中断时,会自动将SSP中地址加载到RSP栈寄存器中,以实现栈切换。如果IST为0 ,则从PLn_SSP MSR 选择作为中断函数栈地址。

idt_init_desc()

idt_init_desc()函数主要是用于初始化一个门描述结构

static inline void idt_init_desc(gate_desc *gate, const struct idt_data *d)
{unsigned long addr = (unsigned long) d->addr;gate->offset_low    = (u16) addr;gate->segment       = (u16) d->segment;gate->bits     = d->bits;gate->offset_middle = (u16) (addr >> 16);
#ifdef CONFIG_X86_64gate->offset_high    = (u32) (addr >> 32);gate->reserved        = 0;
#endif
}

Interrupt Descriptor Table(中断描述表)

x86架构CPU,将每个中断对应的中断描述门组成一个表项称为中断描述表(interrupt descriptor Table)简称为IDT,来关键整个中断,中断描述表索引以中断向量号为索引,中断表述表数据类型为门表述符类型,在内核中中断描述表定义为idt_table 变量数组,定义如下(位于arch\x86\kernel\idt.c文件中):

/* Must be page-aligned because the real IDT is used in the cpu entry area */
static gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

IDT_ENTRIES 大小定义为256:

#define IDT_ENTRIES         256

idt_setup_from_table()函数设置中断描述表

内核提供idt_setup_from_table函数可以用于设置相对应的中断对应的门描述,例如set_intr_gate函数:

static __init void set_intr_gate(unsigned int n, const void *addr)
{struct idt_data data;BUG_ON(n > 0xFF);memset(&data, 0, sizeof(data));data.vector    = n;data.addr   = addr;data.segment = __KERNEL_CS;data.bits.type    = GATE_INTERRUPT;data.bits.p    = 1;idt_setup_from_table(idt_table, &data, 1, false);
}

该函数首先初始化中断门描述,然后调用idt_setup_from_table,设置idt_table 表中对应中断的门描述

static __init void
idt_setup_from_table(gate_desc *idt, const struct idt_data *t, int size, bool sys)
{gate_desc desc;for (; size > 0; t++, size--) {idt_init_desc(&desc, t);write_idt_entry(idt, t->vector, &desc);if (sys)set_bit(t->vector, system_vectors);}
}

 参数:

  • itd:一般指定为idt_table,中断描述表
  • t:所需要设置的中断数组首地址
  • size:所需要设置的中断数量
  • sys: 是否将该中断是否设置过,如果为true.,则将设置到system_vector,该变量以中断数量的bit位,当对应中断bit位设置位1,表明该中断有对应的门描述即存在对应的处理函数。

Interrupt Descriptor Table Register

中断描述表寄存器,该寄存器的作用是记录中断描述表在内存中的首地址以及中断描述表大小,以便当cpu收到一个中断时,硬件可以根据中断表示表寄存器和中断向量号进入触发到对应的门描述,以触发对应的中断处理函数interrupt handle,在amd64系统下中断表述表与中断寄存器关系如下:

中断描述表寄存器中记录IDT 基地址以及IDT Limit为IDT表大小(即占用内存大小),IDT Limit 大小定义IDT_TABLE_SIZE宏:

#define IDT_TABLE_SIZE      (IDT_ENTRIES * sizeof(gate_desc))

LIDT指令

 amd64 架构使用LIDT(Load Interrupt Descriptor Table Register)用于将中断描述表加载到中断描述寄存器中(所谓加载IDT表就是将IDTB表基地址和大小设置到IDT寄存器中),该指令描述如下:

 该指令在amd64位系统中加载的内存格式位首先是mem16bit2个字节为IDT表大小, mem64为IDT表基指地址,内核中使用idt_descr定义:

struct desc_ptr idt_descr __ro_after_init = {.size      = IDT_TABLE_SIZE - 1,.address   = (unsigned long) idt_table,
};

idt_descr大小首先是2个字节size 为IDTB表大小,后面紧跟idt_table地址。

load_idt

load_idt为内核加载IDT表封装的函数:

#define load_idt(dtr)               native_load_idt(dtr)

native_load_idt()函数用于调用LIDT指令加载中断描述表:

static __always_inline void native_load_idt(const struct desc_ptr *dtr)
{asm volatile("lidt %0"::"m" (*dtr));
}

 以load_current_idt函数为例,使用load_idt加载中断描述表:

void load_current_idt(void)
{lockdep_assert_irqs_disabled();load_idt(&idt_descr); //加载中断描述表
}

idt_invalidate()设置IDT表无效

可以通过修改IDT寄存器,通过将寄存器中的IDT表基地址设置为NULL,以及将IDT表大小设置为0,使cpu收到中断时,找不到IDT表而无效:

void idt_invalidate(void *addr)
{struct desc_ptr idt = { .address = (unsigned long) addr, .size = 0 };load_idt(&idt);
}

 idt_invalidate一个比较典型应用就是使用命令使cpu重启native_machine_emergency_restart函数,通过将IDT寄存器内容清空使IDT表无效:

static void native_machine_emergency_restart(void)
{... ...idt_invalidate()... ...
}

参考资料

https://en.wikipedia.org/wiki/Call_gate_(Intel)

x86 - The difference between Call Gate, Interrupt Gate, Trap Gate? - Stack Overflow

Managing Tasks on x86 Processors - Embedded.com

《AMD64 Architecture Programmer's manual volums》

Interrupt Descriptor Table - OSDev Wiki

linux那些事之中断与异常(AMD64架构)_1相关推荐

  1. linux那些事之page fault(AMD64架构)(user space)(2)

    do_user_addr_fault 用户空间地址处理是page fault主要处理流程,x86 64位系统主要是do_user_addr_fault()函数 该处理部分是x86架构特有部分 即与架构 ...

  2. linux那些事之中断与异常(AMD64架构)_2

    内核中断初始化过程 <中断与异常(AMD64架构)_1>,主要从硬件角度分析amd64 x86 cpu 中的一些基本概念,以及如何配置x86 cpu的中断向量表等.由于中断需要在内核启动较 ...

  3. linux那些事之page fault(AMD64架构)(1)

    应用程序或者内核都是运行在虚拟内存空间之中,kernel 启动完成之后如果一个虚拟地址要访问物理内存需要通过CPU MMU硬件进行地址转换,整个虚拟地址访问物理内存逻辑过程如下: kernel 启动完 ...

  4. Linux内核深入理解中断和异常(6):IRQs的非早期初始化

    Linux内核深入理解中断和异常(6):IRQs的非早期初始化 rtoax 2021年3月 0x00-0x1f architecture-defined exceptions and interrup ...

  5. Linux内核深入理解中断和异常(1)

    Linux内核深入理解中断和异常(1) rtoax 2021年3月 1. 中断介绍 内核中第一个子系统是中断(interrupts). 1.1. 什么是中断? 我们已经在这本书的很多地方听到过 中断( ...

  6. Linux内核深入理解中断和异常(8):串口驱动程序

    Linux内核深入理解中断和异常(8):串口驱动程序 rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_setup_early_tr ...

  7. Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues

    Linux内核深入理解中断和异常(7):中断下半部:Softirq, Tasklets and Workqueues rtoax 2021年3月 0x00-0x1f architecture-defi ...

  8. Linux内核深入理解中断和异常(5):外部中断

    Linux内核深入理解中断和异常(5):外部中断 rtoax 2021年3月 1. 外部中断简介 外部中断包括:键盘,鼠标,打印机等. 外部中断包括: I/O interrupts; IO中断 Tim ...

  9. Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx)

    Linux内核深入理解中断和异常(3):异常处理的实现(X86_TRAP_xx) rtoax 2021年3月 /*** start_kernel()->setup_arch()->idt_ ...

最新文章

  1. function_core.php is missing下载,discuz中 function_core.php中的dmkdir有死环bug
  2. MyBatis中动态sql实现传递多个参数并使用if进行参数的判断和实现like模糊搜索以及foreach实现in集合
  3. 8种排序算法 java_必须知道的八大种排序算法【java实现】
  4. WinForm设置窗体默认控件焦点
  5. python cmath模块_python-cmath模块
  6. 《暗时间》时间管理法则
  7. Exploring Simple Siamese Representation Learning[arxiv Submitted on 20 Nov 2020]------论文解读
  8. js实现word生成书签_javascript下用ActiveXObject控件替换word书签,将内容导
  9. python合并excel工作簿_ExcelPython合并处理Excel工作簿、工作表
  10. python终端命令行输入一条语句后出现三个点是什么?
  11. 机器学习(十五)回归算法之线性回归
  12. java+jsp+mysql实现学习资源推荐系统LearningResourceRS 个性化推荐系统 个性化学习网站推荐系统 协同过滤推荐算法 SSH(Spring+Struts+Hiber)开发框架
  13. 阿里云服务器的网站被提示该内容禁止访问的解决办法
  14. 抓包神器:Fiddler Everywhere
  15. 我的2014:迭代的岁月,重构的人生
  16. #个人日记-电影《哆啦A梦:伴我同行2》观后感-20210530
  17. x、y的线性组合(数学)
  18. Java中zip压缩解压
  19. Charles V4系列更新 | 绿色特别版 | 视频教程
  20. 2023年鞋服配饰行业如何玩转全域经营?

热门文章

  1. JEECG 社区开源项目下载(总览)
  2. 如何制作一个360度全景
  3. 微信公众平台java开发详解(工程代码+解析)
  4. SpringBoot2 整合FreeMarker模板,完成页面静态化处理
  5. PyTorch【torchvision】
  6. 总结一些生物成像的 开源图像与插件网站
  7. 指令重排序所带来的问题及使用volatile关键字解决问题
  8. 微服务架构:如何用十步解耦你的系统?
  9. POJ 3264 Balanced Lineup 【线段树】
  10. 51CTO大数据学习006--集合