# 中断机制

# 简介

中断是指计算机运行过程中,出现某些意外情况需要主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
中断的触发一般是由外设或者 CPU 内部产生,由中断控制器进行处理再通知到 CPU 进行中断响应过程,其要点如下:

  • 外设或者 CPU 内部产生的这个中断来源称为中断源
  • CPU 响应中断转入到的新的处理程序称为中断服务程序
  • 中断处理完成后,处理器需要恢复之前的现场,简称“恢复现场”
  • 当多个中断源请求中断的时候,需要通过优先级区分中断,便于 CPU 进行中断的处理
  • 低优先级可以被高优先级打断,这个过程称之为中断嵌套
  • 中断可以被屏蔽
  • 所有的中断源都有一个编号,称为“中断号”
  • 每一个中断号通过中断向量表与中断服务程序一一对应,中断向量表保存的是所有的中断服务程序的入口地址,该入口地址被称之为中断向量

# RISC-V 架构的中断和异常

# 概述

从广义上来说,中断和异常属于一个概念。对于处理器而言,通常只区分为同步异常和异步异常。

  • 同步异常,是指由于执行程序或者试图执行指令而产生的异常,例如,非法指令访问;
  • 异步异常,最常见的异步异常是外部中断,例如外设触发一个外部中断。

# 异常处理机制

  • 进入异常时,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
mcause[XLEN-1]
Exception Code
mcause[XLEN-2:0]
Description
1
1
1
1
1
1
1
3
5
7
9
11
Supervisor software interrupt
Machine software interrupt
Supervisor timer interrupt
Machine timer interrupt
Supervisor external interrupt
Machine external interrupt
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
2
3
4
5
6
7
8
9
11
12
13
15
Intruction address misaligned
Instruction access fault
Illegal instruction
Breakpoint
Load address misaligned
Load access fault
store address misaligned
Store access fault
Environment call from U-mode
Environment call from S-mode
Environment call from M-mode
Instruction page fault
Load page fault
Store page fault
  • 退出异常时,需要从异常服务程序中退出,并返回主程序。RISC-V 架构定义了一组专门的退出异常指令:MRET、SRET、URET,分别对应机器模式、监督模式、用户模式的退出。
    以 MRET 为例,当处理器执行 MRET 指令后,硬件行为如下:
    1. 当前程序执行流程停止执行,跳转到 mepc 的地址运行
    2. 更新 mstatus 状态寄存器(具体情况可参考 RISC-V 架构介绍)

# 中断类型

RISC-V 架构定义的中断类型分为 4 种。

  • 外部中断(External Interrupt),指来自处理器核外部的中断,例如 GPIO、UART 等产生的中断
  • 计时器中断(Timer Interrupt) ,指来自计时器的中断
  • 软件中断(Software Interrupt) ,指来自软件自己触发的中断
  • 调试中断(Debug Interrupt),专用于实现调试器(Debugger)

# 中断控制器

  • CLINT 模块生成计时器中断和软件中断
    CLINT 的全称为处理器核局部中断控制器(Core Local Interrupts Controller),主要用于产生计时器中断(Timer Interrupt)和软件中断(Software Interrupt)。
  • PLIC 管理多个外部中断
    PLIC 全称为平台级别中断控制器(Platform Level Interrupt Controller),它是 RISC-V 架构标准定义的系统中断控制器,主要用于多个外部中断源的优先级仲裁。
    RISC-V中断控制器如下图所示:

XiUOS RISC-V CONTROLLER

# ARM-cortex-M架构的中断和异常

# 概述

Cortex-M 提供了一个异常响应系统,支持为数众多的系统异常和外部中断。其中编号0-15对应系统异常,编号大于等于16的为外部中断。

# 异常处理机制

Cortex-M支持的异常如下表所示:

编号 类型 优先级 简介
0 N/A N/A 没有异常在运行
1 复位 -3(最高) 复位
2 NMI -2 不可屏蔽中断(来自外部 NMI 输入脚)
3 硬(hard)fault -1 所有被除能的 fault,都将“上访”(escalation)成硬 fault。只要FAULTMASK没有置位,硬 fault 服务例程就被强制执行。Fault 被除能的原因包括被禁用,或者 FAULTMASK 被置位。
4 MemManage fault 可编程 存储器管理 fault,MPU 访问犯规以及访问非法位置均可引发。企图在“非执行区”取指也会引发此 fault。
5 总线 fault 可编程 从总线系统收到了错误响应,原因可以是预取流产(Abort)或数据流产,或者企图访问协处理器
6 用法(usage)Fault 可编程 由于程序错误导致的异常。通常是使用了一条无效指令,或者是非法的状态转换,例如尝试切换到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

  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,同时系统再次进入线程模式

# 中断类型

编号 类型 优先级 简介
16 IRQ #0 可编程 外中断 #0
17 IRQ #1 可编程 外中断 #1
... ... ... ...
255 IRQ #239 可编程 外中断 #239

NVIC共支持1到240个外部中断输入(IRQs)。另外,NVIC 还支持一个不可屏蔽输入中断,除了包含控制寄存器和中断控制逻辑外,还包含了 MPU 的控制寄存器、systick 定时器以及调试控制。

# 中断控制器

Cortex-M 系列包含一个 NVIC(嵌套中断向量控制器)提供硬件嵌套中断服务。在中断发生时,NVIC 自动取出对应的服务例程入口地址,并且直接调用,无需软件判定中断源。另外 M 系列包含一个基本的 systick 定时器,配合 NVIC 工作,用于系统计数。NVIC 控制器如下图所示:

NVIC CONTROLLER

# 中断处理机制

# 中断服务程序

每一个中断源对应一个中断号,每一个中断号又通过中断向量表和中断服务程序进行关联。当中断产生后,通过中断向量表跳转到中断服务程序的入口地址进行执行。如下图所示:

XiUOS ISR

# 中断处理流程

CPU 响应中断并进行处理,通常经历以下过程:保存当前线程的栈信息、跳转中断服务程序运行、恢复被打断的线程栈继续运行。 如下图所示:

XiUOS PROCESS

# 中断函数接口

typedef void (*IsrHandlerType)(int vector, void *param);
int32 RegisterHwIrq(uint32 irq_num, IsrHandlerType handler, void *arg);

该函数用于注册一个中断,当产生中断时,将调用该硬件中断号相应的回调函数进行执行 。

参数 描述
irq_num 硬件中断号
handler 中断处理回调函数
arg 中断处理回调函数的参数
int32 FreeHwIrq(uint32 irq_num);

该函数用于释放一个中断。

参数 描述
irq_num 硬件中断号
int32 DisableHwIrq(uint32 irq_num);

该函数用于屏蔽一个中断。

参数 描述
irq_num 硬件中断号
int32 EnableHwIrq(uint32 irq_num);

该函数用于注使能一个中断。

参数 描述
irq_num 硬件中断号

# 中断响应性能测试

# 概述

下面分别测试 XiUOS 系统运行在基于 ARM 和 RISC-V 不同处理器的开发板上时,中断响应时间。

# 基于 ARM 处理器的中断响应性能测试

# 测试方法

为了测试系统的中断响应时间,考虑使用 GPIO 管脚进行中断测试,利用 GPIO 中断服务函数当中管脚波形输出配合输入源波形进行分析。使用 Tektronix TB1202B 示波器的 1KHz 方波输出作为中断触发源。

  • 配置 GPIO C2 为输入模式,配置上升沿触发中断
  • 配置 C13 管脚为输出模式,接示波器通道1
  • 示波器的 1KHz 信号输出分别接 C2 管脚和示波器通道2
  • 示波器 GND 和开发板 GND 共地对接

示波器的 1KHz 方波输出将在1s钟内触发1000个中断,接线图如下图所示。

XiUOS PROCESS

1KHz 的方波信号输出到 GPIO C2 上,将触发上升沿中断,在 GPIO 中断处理函数当中,将 GPIO C13 拉高,再将其置为低电平。触发源 1KHz 方波周期为1ms,因此不会影响到中断的响应。根据 CMOS 电路的电平特性,当电压值达到 0.7 * Vcc 时,识别为高电平。 从波形上来看,当输入源触发信号给到 GPIO C2 的电平值达到 3.3v * 0.7 = 2.31v(0.7*Vcc)时将触发中断,进入中断处理。因此,通道1的电平值高于 2.31v 到 GPIO C13 变为高电平这段时间即为中断响应时间。

# 编程代码清单


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:周期、频率、上升时间
    • Scale:2.5us

# 测试结果

XiUOS PROCESS

从示波器测试结果上来看,从触发源电平达2.28v到C13管脚拉高,响应时间为 11.9us。

# 基于 RISC-V 处理器的中断响应性能测试

# 测试方法

为了测试系统的中断响应时间,考虑使用GPIO管脚进行中断测试,利用GPIO中断服务函数当中管脚波形输出配合输入源波形进行分析。使用Tektronix TB1202B示波器的1KHz方波输出作为中断触发源。

  • 配置 GPIO19 为输入模式,配置上升沿触发中断
  • 配置 GPIO18 管脚为输出模式,接示波器通道1
  • 示波器的1KHz信号输出分别接 GPIO19 管脚和示波器通道2
  • 示波器 GND 和开发板 GND 共地对接

示波器的 1KHz 方波输出将在1s钟内触发1000个中断,接线图如下图所示。

RISC-V INT TIME TEST

1KHz 的方波信号输出到 GPIO19 上,将触发上升沿中断,在 GPIO 中断处理函数当中,将 GPIO18 拉高,延时100us,再将其置为低电平。触发源 1KHz 方波周期为1ms,因此不会影响到中断的响应。根据 CMOS 电路的电平特性,当电压值达到 0.7 * Vcc 时,识别为高电平。

从波形上来看,当输入源触发信号给到 GPIO19 的电平值达到 3.3v * 0.7 = 2.31v(0.7 * Vcc)时将触发中断,进入中断处理。因此,通道1的电平值高于 2.31v 到 GPIO18 变为高电平这段时间即为中断响应时间。

# 编程代码清单

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:周期、频率、上升时间
    • Scale:250us

# 测试结果

RISC-V INT TIME TEST

从示波器测试结果上来看,从触发源电平达 2.28v 到 GPIO18 管脚拉高,响应时间为2.6us。

# 中断响应性能测试对比

对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 us,XiUOS中断响应的效率比sylixos提高 2.5倍
  • 在ARM stm32f407 168MHz CPU主频中断响应时间 11.9 us高于1GHz主频测试的sylixos
  • 若进行同等1GHz主频换算,STM32F407上的中断响应时间应为 1.952 us,XiUOS的中断响应的效率比sylixos提高 0.8倍

由于XiUOS优化了中断响应的流程,减少了执行指令数量,因此,同等主频条件下,中断响应时间更短。

# 使用场景

Last Updated: 9/26/2021, 8:31:08 PM