xuos-web/docs/doc/kernel/int.md

568 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 中断机制
* [简介](#interrupt)
* [RISC-V架构的中断和异常](#riscv_interrupt)
* [概述](#riscv_intro)
* [异常处理机制](#riscv_exception)
* [中断类型](#riscv_int_type)
* [中断控制器](#riscv_int_controller)
* [ARM CORTEX-M架构的中断和异常](#arm_interrupt)
* [概述](#arm_interrupt_intro)
* [异常处理机制](#arm_interrupt_process_mechnism)
* [中断类型](#arm_int_type)
* [中断控制器](#arm_int_controller)
* [中断处理机制](#riscv_int_process)
* [中断服务程序](#riscv_int_service)
* [中断处理流程](#riscv_int_process2)
* [中断函数接口](#riscv_int_api)
* [中断响应性能测试](#time_test)
* [概述](#time_test_intro)
* [基于 ARM 处理器的中断响应性能测试](#time_test_arm)
* [基于 RISC-V 处理器的中断响应性能测试](#time_test_riscv)
* [中断响应性能测试对比](#comparison)
* [使用场景](#situation)
<span id="interrupt"></span>
## 简介
中断是指计算机运行过程中,出现某些意外情况需要主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
中断的触发一般是由外设或者 CPU 内部产生,由中断控制器进行处理再通知到 CPU 进行中断响应过程,其要点如下:
+ 外设或者 CPU 内部产生的这个中断来源称为中断源
+ CPU 响应中断转入到的新的处理程序称为中断服务程序
+ 中断处理完成后,处理器需要恢复之前的现场,简称“恢复现场”
+ 当多个中断源请求中断的时候,需要通过优先级区分中断,便于 CPU 进行中断的处理
+ 低优先级可以被高优先级打断,这个过程称之为中断嵌套
+ 中断可以被屏蔽
+ 所有的中断源都有一个编号,称为“中断号”
+ 每一个中断号通过中断向量表与中断服务程序一一对应,中断向量表保存的是所有的中断服务程序的入口地址,该入口地址被称之为中断向量
<span id="riscv_interrupt"></span>
## RISC-V 架构的中断和异常
<span id="riscv_intro"></span>
### 概述
从广义上来说,中断和异常属于一个概念。对于处理器而言,通常只区分为同步异常和异步异常。
+ 同步异常,是指由于执行程序或者试图执行指令而产生的异常,例如,非法指令访问;
+ 异步异常,最常见的异步异常是外部中断,例如外设触发一个外部中断。
<span id="riscv_exception"></span>
### 异常处理机制
+ 进入异常时RISC-V架构规定(以机器模式为例)
+ 当前的程序执行流停止执行,直接跳转到 CSR 寄存器的 mtvec 定义的 PC 地址执行;
+ 硬件同时更新下列几个 CSR 寄存器(具体情况可参考 RISC-V 架构介绍)
+ mcause (Machine Cause Register):机器模式异常原因寄存
+ mepc (Machine Exception Program Register):机器模式异常 PC 寄存器
+ mtval (Machine Trap Value Register):机器模式异常值寄存器
+ mstatus (Machine Status Register):机器模式状态寄存器
mcause 寄存器的 Exception Code 域标识是何种异常或者何种中断。定义如下图表格所示:
| Interrupt/Exception<br/> mcause[XLEN-1] | Exception Code<br/> mcause[XLEN-2:0] | Description |
| :---: | :---: | --- |
| 1<br/>1<br/>1<br/>1<br/>1<br/>1 | 1<br/>3<br/>5<br/>7<br/>9<br/>11 | Supervisor software interrupt<br/>Machine software interrupt<br/>Supervisor timer interrupt<br/>Machine timer interrupt<br/>Supervisor external interrupt<br/>Machine external interrupt |
| 0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0<br/>0 | 0<br/>1<br/>2<br/>3<br/>4<br/>5<br/>6<br/>7<br/>8<br/>9<br/>11<br/>12<br/>13<br/>15 | Intruction address misaligned<br/>Instruction access fault<br/>Illegal instruction<br/>Breakpoint<br/>Load address misaligned<br/>Load access fault<br/>store address misaligned<br/>Store access fault<br/>Environment call from U-mode<br/>Environment call from S-mode<br/>Environment call from M-mode<br/>Instruction page fault<br/>Load page fault<br/>Store page fault |
+ 退出异常时需要从异常服务程序中退出并返回主程序。RISC-V 架构定义了一组专门的退出异常指令MRET、SRET、URET分别对应机器模式、监督模式、用户模式的退出。
以 MRET 为例,当处理器执行 MRET 指令后,硬件行为如下:
1. 当前程序执行流程停止执行,跳转到 mepc 的地址运行
2. 更新 mstatus 状态寄存器(具体情况可参考 RISC-V 架构介绍)
<span id="riscv_int_type"></span>
### 中断类型
RISC-V 架构定义的中断类型分为 4 种。
+ 外部中断External Interrupt指来自处理器核外部的中断例如 GPIO、UART 等产生的中断
+ 计时器中断Timer Interrupt ,指来自计时器的中断
+ 软件中断Software Interrupt ,指来自软件自己触发的中断
+ 调试中断Debug Interrupt专用于实现调试器Debugger
<span id="riscv_int_controller"></span>
### 中断控制器
+ CLINT 模块生成计时器中断和软件中断
CLINT 的全称为处理器核局部中断控制器Core Local Interrupts Controller主要用于产生计时器中断Timer Interrupt和软件中断Software Interrupt
+ PLIC 管理多个外部中断
PLIC 全称为平台级别中断控制器Platform Level Interrupt Controller它是 RISC-V 架构标准定义的系统中断控制器,主要用于多个外部中断源的优先级仲裁。
RISC-V中断控制器如下图所示
![XiUOS RISC-V CONTROLLER](./imagesrc/int_riscv_controller.png)
<span id="arm_interrupt"></span>
## ARM-cortex-M架构的中断和异常
<span id="arm_interrupt_intro"></span>
### 概述
Cortex-M 提供了一个异常响应系统支持为数众多的系统异常和外部中断。其中编号0-15对应系统异常编号大于等于16的为外部中断。
<span id="arm_interrupt_process_mechnism"></span>
### 异常处理机制
Cortex-M支持的异常如下表所示
| 编号 | 类型 | 优先级 | 简介 |
| --- | --- | --- | --- |
| 0 | N/A | N/A | 没有异常在运行 |
| 1 | 复位 | -3最高 | 复位 |
| 2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) |
| 3 | 硬hardfault | -1 | 所有被除能的 fault都将“上访”escalation成硬 fault。只要FAULTMASK没有置位硬 fault 服务例程就被强制执行。Fault 被除能的原因包括被禁用,或者 FAULTMASK 被置位。 |
| 4 | MemManage fault | 可编程 | 存储器管理 faultMPU 访问犯规以及访问非法位置均可引发。企图在“非执行区”取指也会引发此 fault。 |
| 5 | 总线 fault | 可编程 | 从总线系统收到了错误响应原因可以是预取流产Abort或数据流产或者企图访问协处理器 |
| 6 | 用法usageFault | 可编程 | 由于程序错误导致的异常。通常是使用了一条无效指令或者是非法的状态转换例如尝试切换到ARM状态 |
| 7-10 | 保留 | N/A | N/A |
| 11 | SVCall | 可编程 | 执行系统服务调用指令SVC引发的异常 |
| 12 | 调试监视器 | 可编程 | 调试监视器(断点,数据 观察点,或者是外部调试请求) |
| 13 | 保留 | N/A | N/A |
| 14 | PendSV | 可编程 | 为系统设备而设的“可悬挂请求”pendable request |
| 15 | SysTick | 可编程 | 系统滴答定时器(也就是周期性溢出的时基定时器----译注) |
表格中的SVCall异常属于系统服务调用用于产生系统函数的调用请求该异常必须得到响应例如操作系统不让用户程序直接操作硬件通过一些系统服务函数发出SVC请求触发一个SVC异常然后通过SVC异常服务程序执行。PendSV异常属于可悬挂系统调用它可以像普通中断一样被悬起典型应用是提供线程切换服务。
![ARM PENSV EXAMPLE](./imagesrc/arm_pensv_example.png)
1. 任务 A 呼叫 SVC 来请求任务切换(例如,等待某些工作完成)
2. OS 接收到请求,做好上下文切换的准备,并且 pend 一个 PendSV 异常
3. 当 CPU 退出 SVC 后,它立即进入 PendSV执行上下文切换
4. 当 PendSV 执行完毕后,将返回到任务 B同时进入线程模式
5. 发生了一个中断,并且中断服务程序开始执行
6. 在 ISR 执行过程中,发生 SysTick 异常,并且抢占了该 ISR
7. OS 执行必要的操作,然后 pend 起 PendSV 异常以作好上下文切换的准备
8. 当 SysTick 退出后,回到先前被抢占的 ISR 中ISR 继续执行
9. ISR 执行完毕并退出后PendSV 服务例程开始执行,并且在里面执行上下文切换
10. 当 PendSV 执行完毕后,回到任务 A同时系统再次进入线程模式
<span id="arm_int_type"></span>
### 中断类型
| 编号 | 类型 | 优先级 | 简介 |
| --- | --- | --- | --- |
| 16 | IRQ #0 | 可编程 | 外中断 #0 |
| 17 | IRQ #1 | 可编程 | 外中断 #1 |
| ... | ... | ... | ... |
| 255 | IRQ #239 | 可编程 | 外中断 #239 |
NVIC共支持1到240个外部中断输入IRQs。另外NVIC 还支持一个不可屏蔽输入中断,除了包含控制寄存器和中断控制逻辑外,还包含了 MPU 的控制寄存器、systick 定时器以及调试控制。
<span id="arm_int_controller"></span>
### 中断控制器
Cortex-M 系列包含一个 NVIC嵌套中断向量控制器提供硬件嵌套中断服务。在中断发生时NVIC 自动取出对应的服务例程入口地址,并且直接调用,无需软件判定中断源。另外 M 系列包含一个基本的 systick 定时器,配合 NVIC 工作用于系统计数。NVIC 控制器如下图所示:
<center>
![NVIC CONTROLLER](./imagesrc/arm_nvic_controller.jpg)
</center>
<span id="riscv_int_process"></span>
### 中断处理机制
<span id="riscv_int_service"></span>
#### 中断服务程序
每一个中断源对应一个中断号,每一个中断号又通过中断向量表和中断服务程序进行关联。当中断产生后,通过中断向量表跳转到中断服务程序的入口地址进行执行。如下图所示:
<center>
![XiUOS ISR](./imagesrc/int_isr.png)
</center>
<span id="riscv_int_process2"></span>
#### 中断处理流程
CPU 响应中断并进行处理,通常经历以下过程:保存当前线程的栈信息、跳转中断服务程序运行、恢复被打断的线程栈继续运行。
如下图所示:
<center>
![XiUOS PROCESS](./imagesrc/int_process.png)
</center>
<span id="riscv_int_api"></span>
## 中断函数接口
```c
typedef void (*IsrHandlerType)(int vector, void *param);
int32 RegisterHwIrq(uint32 irq_num, IsrHandlerType handler, void *arg);
```
该函数用于注册一个中断,当产生中断时,将调用该硬件中断号相应的回调函数进行执行 。
| 参数 | 描述 |
| --- | --- |
| irq_num | 硬件中断号 |
| handler | 中断处理回调函数 |
| arg | 中断处理回调函数的参数 |
```c
int32 FreeHwIrq(uint32 irq_num);
```
该函数用于释放一个中断。
| 参数 | 描述 |
| --- | --- |
| irq_num | 硬件中断号 |
```c
int32 DisableHwIrq(uint32 irq_num);
```
该函数用于屏蔽一个中断。
| 参数 | 描述 |
| --- | --- |
| irq_num | 硬件中断号 |
```c
int32 EnableHwIrq(uint32 irq_num);
```
该函数用于注使能一个中断。
| 参数 | 描述 |
| --- | --- |
| irq_num | 硬件中断号 |
<span id="time_test"></span>
## 中断响应性能测试
<span id="time_test_intro"></span>
### 概述
下面分别测试 XiUOS 系统运行在基于 ARM 和 RISC-V 不同处理器的开发板上时,中断响应时间。
<span id="time_test_arm"></span>
### 基于 ARM 处理器的中断响应性能测试
#### 测试方法
为了测试系统的中断响应时间,考虑使用 GPIO 管脚进行中断测试,利用 GPIO 中断服务函数当中管脚波形输出配合输入源波形进行分析。使用 Tektronix TB1202B 示波器的 1KHz 方波输出作为中断触发源。
* 配置 GPIO C2 为输入模式,配置上升沿触发中断
* 配置 C13 管脚为输出模式接示波器通道1
* 示波器的 1KHz 信号输出分别接 C2 管脚和示波器通道2
* 示波器 GND 和开发板 GND 共地对接
示波器的 1KHz 方波输出将在1s钟内触发1000个中断接线图如下图所示。
<center>
![XiUOS PROCESS](./imagesrc/int_arm_test_connect.png)
</center>
1KHz 的方波信号输出到 GPIO C2 上,将触发上升沿中断,在 GPIO 中断处理函数当中,将 GPIO C13 拉高,再将其置为低电平。触发源 1KHz 方波周期为1ms因此不会影响到中断的响应。根据 CMOS 电路的电平特性,当电压值达到 0.7 * Vcc 时,识别为高电平。
从波形上来看,当输入源触发信号给到 GPIO C2 的电平值达到 3.3v * 0.7 = 2.31v0.7*Vcc时将触发中断进入中断处理。因此通道1的电平值高于 2.31v 到 GPIO C13 变为高电平这段时间即为中断响应时间。
#### 编程代码清单
```c
static BusType pin;
#define GPIO_C2 17
#define GPIO_C13 7
void PinIrqIsr(void *args){
*(volatile *)0x40020818 = 0x2000; /////< GPIO_C13 set high
*(volatile *)0x4002081a = 0x2000; /////< GPIO_C13 set low
}
int RealtimeIrqTest()
{
int ret = 0;
struct PinParam output_pin;
struct PinStat output_pin_stat;
struct PinParam input_pin;
struct BusConfigureInfo configure_info;
struct BusConfigureInfo configure_info_2;
struct BusBlockWriteParam write_param;
configure_info.configure_cmd = OPE_CFG;
configure_info.private_data = (void *)&output_pin;
write_param.buffer = (void *)&output_pin_stat;
configure_info_2.configure_cmd = OPE_CFG;
configure_info_2.private_data = (void *)&input_pin;
KPrintf("%s irq test\n",__func__);
/* config test pin 1 as output*/
output_pin.cmd = GPIO_CONFIG_MODE;
output_pin.pin = GPIO_C13;
output_pin.mode = GPIO_CFG_OUTPUT;
ret = BusDrvConfigure(pin->owner_driver, &configure_info);
if (ret != EOK) {
KPrintf("config output_pin %d failed!\n", GPIO_C13);
return -ERROR;
}
/* set test pin 1 as high*/
output_pin_stat.pin = GPIO_C13;
output_pin_stat.val = GPIO_LOW;
BusDevWriteData(pin->owner_haldev, &write_param);
/* config test pin 2 as input*/
input_pin.cmd = GPIO_CONFIG_MODE;
input_pin.pin = GPIO_C2;
input_pin.mode = GPIO_CFG_INPUT;
ret = BusDrvConfigure(pin->owner_driver, &configure_info_2);
if (ret != EOK) {
KPrintf("config input_pin %d input failed!\n", input_pin.pin);
return -ERROR;
}
input_pin.cmd = GPIO_IRQ_REGISTER;
input_pin.pin = GPIO_C2;
input_pin.irq_set.irq_mode = GPIO_IRQ_EDGE_BOTH;
input_pin.irq_set.hdr = PinIrqIsr;
input_pin.irq_set.args = NONE;
ret = BusDrvConfigure(pin->owner_driver, &configure_info_2);
if (ret != EOK) {
KPrintf("register input_pin %d irq failed!\n", input_pin.pin);
return -ERROR;
}
input_pin.cmd = GPIO_IRQ_ENABLE;
input_pin.pin = GPIO_C2;
ret = BusDrvConfigure(pin->owner_driver, &configure_info_2);
if (ret != EOK) {
KPrintf("enable input_pin %d irq failed!\n", input_pin.pin);
return -ERROR;
}
KPrintf("%s irq test\n",__func__);
return 0;
}
int TestRealtime(int argc, char * argv[])
{
int ret = 0;
struct BusConfigureInfo configure_info;
pin = BusFind(PIN_BUS_NAME);
if (!pin) {
KPrintf("find %s failed!\n", PIN_BUS_NAME);
return -ERROR;
}
pin->owner_driver = BusFindDriver(pin, PIN_DRIVER_NAME);
pin->owner_haldev = BusFindDevice(pin, PIN_DEVICE_NAME);
configure_info.configure_cmd = OPE_INT;
ret = BusDrvConfigure(pin->owner_driver, &configure_info);
if (ret != EOK) {
KPrintf("initialize %s failed!\n", PIN_BUS_NAME);
return -ERROR;
}
RealtimeIrqTest();
return 0;
}
```
#### 示波器测试选项设置
* 通道设置
* 耦合:直流
* 带宽限制:关闭
* 伏/格:粗调
* 探头10X 电压
* 反相:关闭
* 触发设置
* 类型:边沿
* 信源: CH1
* 斜率:上升
* 模式:自动
* 触发电压2.28v (略低于 2.31v即可)
* 测量设置
* 测量选通:开启
* 类型:时间
* 信源CH1
* CH1周期、频率、上升时间
* CH2周期、频率、上升时间
* Scale2.5us
#### 测试结果
<center>
![XiUOS PROCESS](./imagesrc/int_arm_test_result.png)
</center>
从示波器测试结果上来看从触发源电平达2.28v到C13管脚拉高响应时间为 11.9us。
<span id="time_test_riscv"></span>
### 基于 RISC-V 处理器的中断响应性能测试
#### 测试方法
为了测试系统的中断响应时间考虑使用GPIO管脚进行中断测试利用GPIO中断服务函数当中管脚波形输出配合输入源波形进行分析。使用Tektronix TB1202B示波器的1KHz方波输出作为中断触发源。
* 配置 GPIO19 为输入模式,配置上升沿触发中断
* 配置 GPIO18 管脚为输出模式接示波器通道1
* 示波器的1KHz信号输出分别接 GPIO19 管脚和示波器通道2
* 示波器 GND 和开发板 GND 共地对接
示波器的 1KHz 方波输出将在1s钟内触发1000个中断接线图如下图所示。
<center>
![RISC-V INT TIME TEST](./imagesrc/int_riscv_test_connect.png)
</center>
1KHz 的方波信号输出到 GPIO19 上,将触发上升沿中断,在 GPIO 中断处理函数当中,将 GPIO18 拉高延时100us再将其置为低电平。触发源 1KHz 方波周期为1ms因此不会影响到中断的响应。根据 CMOS 电路的电平特性,当电压值达到 0.7 * Vcc 时,识别为高电平。
从波形上来看,当输入源触发信号给到 GPIO19 的电平值达到 3.3v * 0.7 = 2.31v0.7 * Vcc时将触发中断进入中断处理。因此通道1的电平值高于 2.31v 到 GPIO18 变为高电平这段时间即为中断响应时间。
#### 编程代码清单
```c
static BusType pin;
#define GPIO_18 18
#define GPIO_19 19
void PinIrqIsr(void *args){
*(volatile *)0x3800100c |= 0x5;
usleep(100);
*(volatile *)0x3800100c &= ~0x5;
}
int realtime_irq_test()
{
struct PinParam output_pin;
struct PinStat output_pin_stat;
struct PinParam input_pin;
struct BusConfigureInfo configure_info;
struct BusConfigureInfo configure_info_2;
struct BusBlockWriteParam write_param;
configure_info.configure_cmd = OPE_CFG;
configure_info.private_data = (void *)&output_pin;
write_param.buffer = (void *)&output_pin_stat;
configure_info_2.configure_cmd = OPE_CFG;
configure_info_2.private_data = (void *)&input_pin;
/* config GPIO18 as output and set as low */
output_pin.cmd = GPIO_CONFIG_MODE;
output_pin.pin = GPIO_18;
output_pin.mode = GPIO_CFG_OUTPUT;
BusDrvConfigure(pin->owner_driver, &configure_info);
output_pin_stat.pin = GPIO_18;
output_pin_stat.val = GPIO_LOW;
BusDevWriteData(pin->owner_haldev, &write_param);
/* config GPIO18 as input */
input_pin.cmd = GPIO_CONFIG_MODE;
input_pin.pin = GPIO_19;
input_pin.mode = GPIO_CFG_INPUT;
BusDrvConfigure(pin->owner_driver, &configure_info_2);
input_pin.cmd = GPIO_IRQ_REGISTER;
input_pin.pin = GPIO_19;
input_pin.irq_set.irq_mode = GPIO_IRQ_EDGE_RISING;
input_pin.irq_set.hdr = PinIrqIsr;
input_pin.irq_set.args = NONE;
BusDrvConfigure(pin->owner_driver, &configure_info_2);
input_pin.cmd = GPIO_IRQ_ENABLE;
input_pin.pin = GPIO_19;
BusDrvConfigure(pin->owner_driver, &configure_info_2);
return 0;
}
int TestRealtime(int argc, char * argv[])
{
int ret = 0;
struct BusConfigureInfo configure_info;
pin = BusFind(PIN_BUS_NAME);
if (!pin) {
KPrintf("find %s failed!\n", PIN_BUS_NAME);
return -ERROR;
}
pin->owner_driver = BusFindDriver(pin, PIN_DRIVER_NAME);
pin->owner_haldev = BusFindDevice(pin, PIN_DEVICE_NAME);
configure_info.configure_cmd = OPE_INT;
ret = BusDrvConfigure(pin->owner_driver, &configure_info);
if (ret != EOK) {
KPrintf("initialize %s failed!\n", PIN_BUS_NAME);
return -ERROR;
}
RealtimeIrqTest();
return 0;
}
```
#### 示波器测试选项设置
* 通道设置
* 耦合:直流
* 带宽限制:关闭
* 伏/格:粗调
* 探头10X 电压
* 反相:关闭
* 触发设置
* 类型:边沿
* 信源: CH1
* 斜率:上升
* 模式:自动
* 触发电压2.28v (略低于 2.31v即可)
* 测量设置
* 测量选通:开启
* 类型:时间
* 信源CH1
* CH1周期、频率、上升时间
* CH2周期、频率、上升时间
* Scale250us
#### 测试结果
<center>
![RISC-V INT TIME TEST](./imagesrc/int_riscv_test_result.png)
</center>
从示波器测试结果上来看,从触发源电平达 2.28v 到 GPIO18 管脚拉高响应时间为2.6us。
<span id="comparison"></span>
### 中断响应性能测试对比
对sylixos的中断响应性能测试结果如下
| 操作系统 | 测试开发板 | CPU | 中断响应时间(us) |
| --- | --- | --- | --- |
| sylixos | mini210s开发板 | ARM Cortex-A8 主频 1GHz | 3.612 |
| XiUOS | KD233开发板 | RISC-V K210 主频 400MHz | 2.6 |
| XiUOS | STM32F407G-DISC1开发板 | STM32f407 主频 168MHz | 11.9 |
结果分析:
* XiUOS在RISC-V K210 400MHz CPU主频上中断响应时间为 2.6 us低于sylixos的 3.612 us
* 若进行同等1GHz主频换算K210上的中断响应时间应为 1.016 usXiUOS中断响应的效率比sylixos提高 2.5倍
* 在ARM stm32f407 168MHz CPU主频中断响应时间 11.9 us高于1GHz主频测试的sylixos
* 若进行同等1GHz主频换算,STM32F407上的中断响应时间应为 1.952 usXiUOS的中断响应的效率比sylixos提高 0.8倍
由于XiUOS优化了中断响应的流程减少了执行指令数量因此同等主频条件下中断响应时间更短。
<span id="situation"></span>
## 使用场景