RT-Thread 中断处理过程

发布时间:2024年01月12日

中断处理过程

RT-Thread中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图:
在这里插入图片描述

中断前导程序

中断前导程序主要工作:

  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()函数进行上下文切换,该函数跟CPU架构相关,不同CPU架构的实现方式有差异。

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

中断后续程序

  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上下文。

在这里插入图片描述

中断嵌套

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

在这里插入图片描述

中断栈

在中断处理过程中,在系统响应中断前,软件代码需要把当前线程的上下文保存下来(通常保存在当前线程的线程栈中),再调用中断服务程序进行中断响应、处理。
在进行中断处理时(实质是调用用户的中断服务程序函数),中断处理函数很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应依然需要一个栈空间来作为上下文,运行中断处理函数。

中断栈可以保存在被打断线程的栈中,当从中断中退出时,返回相应的线程继续执行。

中断栈也可以与线程栈完全分离开来,即每次进入中断时,在保存完打断线程上下文后,切换到新的中断栈中独立运行。在中断退出时,再做相应的上下文恢复。

使用独立中断栈相对来说更容易实现,并且对于线程栈使用情况也比较容易了解和掌握(否则必须要为中断栈预留空间,如果系统支持中断嵌套,还需要考虑应该为嵌套中断预留多大的空间)。

RT-Thread采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。
这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。

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

中断的底半处理

RT-Thread不对中断服务程序所需要的处理时间做任何假设、限制,但如同其他实时操作系统或非实时操作系统一样,用户需要保证所有的中断服务程序在尽可能短的事件内完成(中断服务程序在系统中相当于拥有最高的优先级,会抢占所有线程优先执行)。这样在发生中断嵌套,或屏蔽了相应中断源的过程中,不会耽误嵌套的其它中断处理过程,或自身中断源的下一次中断信号。

当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。
如果中断服务程序接下来要对状态或者数据进行简单处理,比如CPU时钟中断,中断服务程序只需对一个系统时钟变量进行加一操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。

但对于另外一些中断,中断服务程序在取得硬件状态或者数据以后,还需要进行一系列更耗时的处理过程,通常需要将中断分割为两部分,即上半部分(Top Half)和底半部分(Bottom Half)。

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

为了详细描述底半处理在RT-Thread中的实现,我们以一个虚拟的网络设备接收网络数据包作为范例,并假设接收到数据报文后,系统对报文的分析、处理是一个相对耗时的,比外部中断源信号重要性小许多的,而且在不屏蔽中断源信号情况下也能处理的过程。

程序创建了一个nwt线程,这个线程在启动运行后,将阻塞在nw_bh_sem信号上,一旦这个信号量被释放,将执行接下来的nw_packet_parser过程,开始Bottom Half的事件处理。

/* 用于唤醒线程的信号量 */
rt_sem_t nw_bh_sem;

/* 数据读取、分析的线程 */
void demo_nw_thread(void *param)
{
	device_init_setting();

	nw_bh_sem = rt_sem_create("bh_sem", 0, RT_IPC_FLAG_PRIO);
	while(1)
	{
		rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER);
		
		nw_packet_parser(packet_buffer);
		nw_packet_process(packet_buffer);
	}
}

int main(void)
{
	rt_thread_t thread;
	thread = rt_thread_create("nwt", demo_nw_thread, RT_NULL, 1024, 20, 5);
	if(thread != RT_NULL)
		rt_thread_startup(thread);
}

demo_nw_isr是如何处理Top Half,并开启Bottom Half的。

void demo_nw_isr(int vector, void *param)
{
	/* 当network设备接收到数据后,陷入中断异常,开始执行此ISR */
	/* 开始Top Half部分的处理,如读取硬件设备的状态以判断发生了何种中断 */
	nw_device_status_read();

	/* 其它一些数据操作等 */

	rt_sem_release(nw_bh_sem);
}

从上面例子的两个代码片段可以看出,中断服务程序通过对一个信号量对象的等待和释放,来完成中断Bottom Half的起始和终结。
由于将中断处理划分为Top和Bottom两个部分后,使得中断处理过程变为异步过程。
这部分系统开销需要用户在使用RT-Thread时,必须认真考虑中断服务的处理时间是否大于给Bottom Half发送通知并处理的时间。

文章来源:https://blog.csdn.net/Caramel_biscuit/article/details/135537520
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。