> 文档中心 > RTT学习笔记7-中断管理

RTT学习笔记7-中断管理


基础知识

寄存器

  • Cortex-M 系列 CPU 的寄存器组里有 R0~R15 共 16 个通用寄存器组和若干特殊功能寄存器

  • 通用寄存器组里的 R13 作为堆栈指针寄存器 (Stack Pointer,SP);

  • R14 作为连接寄存器 (Link Register,LR),用于在调用子程序时,存储返回地址;

  • R15 作为程序计数器 (Program Counter,PC),其中堆栈指针寄存器可以是主堆栈指针(MSP),也可以是进程堆栈指针(PSP)

  • 中断屏蔽寄存器组控制 Cortex-M 的中断除能。

  • 控制寄存器用来定义特权级别和当前使用哪个堆栈指针RTT学习笔记7-中断管理

  • 进入异常或中断处理则进入处理模式,其他情况则为线程模式

  • 处理模式总是使用 MSP 作为堆栈,线程模式可以选择使用 MSP 或 PSP 作为堆栈,同样通过 CONTROL 特殊寄存器控制。

  • 复位后,Cortex-M 默认进入线程模式、特权级、使用 MSP 堆栈。

嵌套向量中断控制器

  • 当一个中断触发并且系统进行响应时,处理器硬件会将当前运行位置的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器
  • 当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样会打断当前运行的中断服务程序,然后把这个中断服务程序上下文的 PSR、PC、LR、R12、R3-R0 寄存器自动保存到中断栈中(硬件完成保存)

PendSV 系统调用

PendSV 也称为可悬起的系统调用,它是一种异常,可以像普通的中断一样被挂起,它是专门用来辅助操作系统进行上下文切换的。PendSV 异常会被初始化为最低优先级的异常。每次需要进行上下文切换的时候,会手动触发 PendSV 异常,在 PendSV 异常处理函数中进行上下文切换

RT-Thread 中断工作机制

在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务程序必须排列在一起放在统一的地址(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义或在起始代码中给出

中断处理过程

将中断处理程序分为

  1. 中断前导程序、
  2. 用户中断服务程序、
  3. 中断后续程序三部分RTT学习笔记7-中断管理

中断前导程序

中断前导程序主要工作如下:

1)保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。

对于 Cortex-M 来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。

2)通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest 加 1,用它来记录中断嵌套的层

void rt_interrupt_enter(void){    rt_base_t level;    level = rt_hw_interrupt_disable();    rt_interrupt_nest ++;    rt_hw_interrupt_enable(level);}

用户中断服务程序

在用户中断服务程序(ISR)中,分为两种情况

第一种情况是不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。

第二种种情况是,在中断处理过程中需要进行线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换

在 Cortex-M 架构中,rt_hw_context_switch_interrupt() 的函数实现流程如下图所示,它将设置需要切换的线程 rt_interrupt_to_thread 变量,然后触发 PendSV 异常(PendSV 异常是专门用来辅助上下文切换的,且被初始化为最低优先级的异常)。PendSV 异常被触发后,不会立即进行 PendSV 异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入 PendSV 异常中断处理程序
RTT学习笔记7-中断管理

中断后续程序

中断后续程序主要完成的工作是:

  1. 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1
void rt_interrupt_leave(void){    rt_base_t level;    level = rt_hw_interrupt_disable();    rt_interrupt_nest --;    rt_hw_interrupt_enable(level);}
  1. 恢复中断前的 CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复 from 线程的 CPU 上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文
    RTT学习笔记7-中断管理

中断嵌套

在允许中断嵌套的情况下,在执行中断服务程序的过程中,如果出现高优先级的中断,当前中断服务程序的执行将被打断,以执行高优先级中断的中断服务程序,当高优先级中断的处理完成后,被打断的中断服务程序才又得到继续执行,如果需要进行线程调度,线程的上下文切换将在所有中断处理程序都运行结束时才发生
RTT学习笔记7-中断管理

中断栈

RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针

在 Cortex-M 处理器内核里有两个堆栈指针,

  • 一个是主堆栈指针(MSP),是默认的堆栈指针,在运行第一个线程之前和在中断和异常服务程序里使用;
  • 另一个是线程堆栈指针(PSP),在线程里使用。
  • 在中断和异常服务程序退出时,修改 LR 寄存器的第 2 位的值为 1,线程的 SP 就由 MSP 切换到 PSP。

中断的底半处理

通常需要将该中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。
在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;
而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理

RT-Thread 中断管理接口

RTT学习笔记7-中断管理

中断服务程序挂接

Cortex-M0/M3/M4 的移植分支中就没有这个 API

rt_isr_handler_t rt_hw_interrupt_install(int vector,     rt_isr_handler_t  handler,     void *param,     char *name);

RTT学习笔记7-中断管理

中断源管理

Cortex-M0/M3/M4 的移植分支中就没有这个 API

ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在 ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源

屏蔽函数

void rt_hw_interrupt_mask(int vector);

取消屏蔽

void rt_hw_interrupt_umask(int vector);

全局中断开关

关闭中断

rt_base_t rt_hw_interrupt_disable(void);

打开中断

void rt_hw_interrupt_enable(rt_base_t level);

互斥最快速的方法是使用中断锁而不是信号量或互斥量–前提时执行时间非常短
开关全局中断的 API 支持多级嵌套使用

中断通知

当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态

  1. 使用 rt_interrupt_enter/leave() 的作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。

例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即进行切换。

  1. 但如果中断服务程序不会调用内核相关的函数(释放信号量等操作),这个时候,也可以不调用 rt_interrupt_enter/leave() 函数。
void rt_interrupt_enter(void);void rt_interrupt_leave(void);

返回嵌套深度

rt_uint8_t rt_interrupt_get_nest(void);

RTT学习笔记7-中断管理

中断与轮询

  • 在实时系统中轮询模式可能会出现非常大问题,因为在实时操作系统中,当一个程序持续地执行时(轮询时),它所在的线程会一直运行,比它优先级低的线程都不会得到运行
  • 实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作
    实时系统想要提升数据吞吐量时,可以考虑的几种方式:
    1)增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据;

2)必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。

全局中断示例

#include #include #define THREAD_PRIORITY      20#define THREAD_STACK_SIZE    512#define THREAD_TIMESLICE     5/* 同时访问的全局变量 */static rt_uint32_t cnt;void thread_entry(void *parameter){    rt_uint32_t no;    rt_uint32_t level;    no = (rt_uint32_t) parameter;    while (1)    { /* 关闭全局中断 */ level = rt_hw_interrupt_disable(); cnt += no; /* 恢复全局中断 */ rt_hw_interrupt_enable(level); rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt); rt_thread_mdelay(no * 10);    }}/* 用户应用程序入口 */int interrupt_sample(void){    rt_thread_t thread;    /* 创建 t1 线程 */    thread = rt_thread_create("thread1", thread_entry, (void *)10,  THREAD_STACK_SIZE,  THREAD_PRIORITY, THREAD_TIMESLICE);    if (thread != RT_NULL) rt_thread_startup(thread);    /* 创建 t2 线程 */    thread = rt_thread_create("thread2", thread_entry, (void *)20,  THREAD_STACK_SIZE,  THREAD_PRIORITY, THREAD_TIMESLICE);    if (thread != RT_NULL) rt_thread_startup(thread);    return 0;}/* 导出到 msh 命令列表中 */MSH_CMD_EXPORT(interrupt_sample, interrupt sample);