内核入门(七)——中断管理


  中断指计算机CPU获知某些事,暂停正在执行的程序,转而去执行处理该事件的程序,当这段程序执行完毕后再继续执行之前的程序。整个过程称为中断处理,简称中断,而引起这一过程的事件称为中断事件。中断是计算机实现并发执行的关键,也是操作系统工作的根本。
  中断是一种异常,异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出错,重则会导致系统毁灭性地瘫痪。所以正确地处理异常,避免错误的发生是提高软件鲁棒性非常重要的一环。

一、Corte-M架构基础

1.1 寄存器简介

  在博客中有对Cortex-M的寄存器简介。

1.2 操作模式和特权级别

  Cortex-M 引入了操作模式和特权级别的概念,分别为线程模式和处理模式,如果进入异常或中断处理则进入处理模式,其他情况则为线程模式。
  Cortex-M 有两个运行级别,分别为特权级和用户级,线程模式可以工作在特权级或者用户级,而处理模式总工作在特权级,可通过CONTROL 特殊寄存器控制。工作模式状态切换情况如上图所示。

  Cortex-M 的堆栈寄存器SP对应两个物理寄存器MSP和PSP,MSP为主堆栈,PSP为进程堆栈,处理模式总是使用MSP作为堆栈,线程模式可以选择使用MSP或PSP作为堆栈,同样通过CONTROL特殊寄存器控制。

1.3 NVIC

  Cortex-M 中断控制器名为NVIC(Nested vectoredinterrupt controller,嵌套向量中断控制器),主要作用是为中断分组,从而分配抢占优先级响应优先级。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行位置的上下文寄存器自动压入中断栈中,这部分的寄存器包括PSR、PC、LR、R12、R3-R0 寄存器。

1.4 PenSV

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

二、RTT中断工作机制

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

2.1 中断处理过程

  RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分:

  1. 中断前导程序

  中断前导程序主要工作如下:
  1)保存CPU 中断现场,这部分跟CPU 架构相关。对于Cortex-M 来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器硬件会
将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括PSR、PC、LR、R12、R3-R0 寄存器。
  2)通知内核进入中断状态,调用rt_interrupt_enter()函数,作用是把全局变量rt_interrupt_nest加1,用它来记录中断嵌套的层数。

  1. 用户中断服务程序(ISR)

  在用户中断服务程序(ISR)中,分为两种情况,第一种情况是不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。
  另一种情况是,在中断处理过程中需要进行线程切换,这种情况会调用rt_hw_context_switch_interrupt()函数进行上下文切换,该函数跟CPU 架构相关,不同CPU 架构的实现方式有差异。

  1. 中断后续程序

  中断后续程序主要完成的工作是:
  1) 通知内核离开中断状态,通过调用rt_interrupt_leave()函数,将全局变量rt_interrupt_nest减1。
  2) 恢复中断前的CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复from 线程的CPU上下文,如果在中断中进行了线程切换,那么恢复至线程的CPU 上下文。

2.2 其他概念

  1. 中断嵌套

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

  1. 中断栈

  RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。
  在Cortex-M 处理器内核里有两个堆栈指针,一个是主堆栈指(MSP),是默认的堆栈指针,在运行第一个线程之前和在中断和异常服务程序里使用;另一个是线程堆栈指针(PSP),在线程里使用。在中断和异常服务程序退出时,修改LR 寄存器的第2 位的值为1,线程的SP 就由MSP 切换到PSP。

  1. 底半处理

  当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。如果中断服务程序接下来要对状态或者数据进行简单处理,比如CPU 时钟中断,中断服务程序只需对一个系统时钟变量进行加一操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。但对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理。将中断处理划分为Top 和Bottom 两个部分后,使得中断处理过程变为异步过程。这部分系统开销需要用户在使用RT-Thread 时,必须认真考虑中断服务的处理时间是否大于给Bottom Half 发送通知并处理的时间。

三、中断管理

  为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread 把中断和异常封装为一组抽象接口,如下图所示:

3.1 中断服务程序挂起

  系统把用户的中断服务程序(handler) 和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序:

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

  中断服务程序是一种需要特别注意的运行环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(特权模式)),在这个运行环境中不能使用挂起当前线程的操作,因为当前线程并不存在,执行相关的操作会有类似打印提示信息,“Function [abc_func] shall not used in ISR”,含义是不应该在中断服务程序中调用的函数)。

3.2 中断源管理

  通常在ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源。屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰,可调用下面这个函数接口:

void rt_hw_interrupt_mask(int vector);

  为了尽可能的不丢失硬件中断信号,可调用下面的函数接口打开被屏蔽的中断源:

void rt_hw_interrupt_umask(int vector);

  调用rt_hw_interrupt_umask函数接口后,如果中断(及对应外设)被配置正确时,中断触发后,将送到到处理器进行处理。

3.3 全局中断开关

  全局中断开关也称为中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其他事件打断(因为整个系统已经不再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制权。当需要关闭整个系统的中断时,可调用下面的函数接口:

rt_base_t rt_hw_interrupt_disable(void);

  相应的,打开中断锁调用以下函数接口:

void rt_hw_interrupt_enable(rt_base_t level);

3.4 中断通知

  当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态。针对这种情况,可通过以下接口:

//注意不要在应用程序中调用这两个接口函数
void rt_interrupt_enter(void);	//通知内核,当前已经进入了中断状态,并增加中断嵌套深度(执行rt_interrupt_nest++)
void rt_interrupt_leave(void);	//通知内核,当前已经离开了中断状态,并减少中断嵌套深度(执行rt_interrupt_nest-–)

  这两个函数的主要作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即进行切换。

四、中断与轮询

  当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。
  轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰,在分时系统中较为适用。实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。

猜你喜欢

转载自blog.csdn.net/qq_33604695/article/details/105547116