RT-Thread 中断管理接口

发布时间:2024年01月12日

中断服务程序挂接

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

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

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

中断源管理

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

屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰。

void rt_hw_interrupt_mask(int vector);

调用rt_hw_interrupt_mask函数接口后,相应的中断将会被屏蔽(通常当这个中断触发时,中断状态寄存器会有相应的变化,但并不送达到处理器进行处理。)

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

void rt_hw_interrupt_umask(int vector);

全局中断开关

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

rt_base_t rt_hw_interrupt_disable(void);
  • 返回:中断状态,函数运行前的中断状态

恢复中断也称为开中断。rt_hw_interrupt_enable()这个函数用于“使能”中断,它恢复了调用失能函数前的中断状态。
如果调用rt_hw_interrupt_disable()函数前是关中断状态,那么调用此函数后依然是关中断状态。

恢复中断往往和关闭中断成对使用。

使用中断锁来操作临界区的方法可以应用于任何场合,且其它几类同步方式都是依赖于中断锁而实现的,可以说中断锁是最强大的和最高效的同步方法。
使用中断锁最主要的问题在于,在中断关闭期间系统将不再响应任何中断,也就不能响应外部的事件。所以中断锁对系统的实时性影响非常巨大,当使用不当的时候会导致系统完全无实时性可言(可能导致系统完全偏离要求的时间需求)。

例如,为了保证一行代码(例如赋值)的互斥运行,最快速的方法是使用中断锁而不是信号量或互斥量:

level = rt_hw_interrupt_disable();
a = a + value;
rt_hw_interrupt_enable(level);

在使用中断锁时,需要确保关闭中断的时间非常短。

rt_sem_take(sem_lock, RT_WAITING_FOREVER);
a = a + value;
rt_sem_release(sem_lock);

比较来说,使用中断锁更为简洁快速。

函数 rt_base_t rt_hw_interrupt_disable(void) 和函数 void rt_hw_interrupt_enable(rt_base_t level) 一般需要配对使用,从而保证正确的中断状态。

在RT-Thread中,开关全局中断的API支持多级嵌套使用。

#include <rthw.h>

void global_interrupt_demo(void)
{
	rt_base_t level0;
	rt_base_t level1;

	/* 第一次关闭全局中断,关闭之前的全局中断状态可能是打开的,也可能是关闭的 */
	level0 = rt_hw_interrupt_disable();
	/* 第二次关闭全局中断,关闭之前的全局中断是关闭的,关闭之后全局中断还是关闭的 */
	level1 = rt_hw_interrupt_disable();

	do_something();

	/* 恢复全局中断到第二次关闭之前的状态,所以本次 enable 之后全局中断还是关闭的 */
    rt_hw_interrupt_enable(level1);
    /* 恢复全局中断到第一次关闭之前的状态,这时候的全局中断状态可能是打开的,也可能是关闭的 */
    rt_hw_interrupt_enable(level0);
}

这个特性可以给代码的开发带来很大的便利。
例如在某个函数里关闭了中断,然后调用某些子函数,再打开中断。这些子函数里面也可能存在开关中断的代码。由于全局中断的API支持嵌套使用,用户无需为这些代码做特殊处理。

中断通知

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

void rt_interrupt_enter(void);
void rt_interrupt_leave(void);

这两个接口分别用在中断前导程序和中断后续程序中,均会对rt_interrut_nest(中断嵌套深度)的值进行修改。

每当进入中断时,可以调用 rt_interrupt_enter() 函数,用于通知内核,当前已经进入了中断状态,并增加中断嵌套深度(执行 rt_interrupt_nest++);

每当退出中断时,可以调用 rt_interrupt_leave() 函数,用于通知内核,当前已经离开了中断状态,并减少中断嵌套深度(执行 rt_interrupt_nest --)。注意不要在应用程序中调用这两个接口函数。

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

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

在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度时,可调用rt_interrupt_get_nest()接口,它会返回rt_interrupt_nest。

rt_uint8_t rt_interrupt_get_nest(void);

返回值:

  • 0:当前系统不处于中断上下文环境中
  • 1:当前系统处于中断上下文环境中
  • 大于1:当前中断嵌套层次

中断与轮询

当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。

轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰。
例如往串口中写入数据,仅当串口控制器写完一个数据时,程序代码才写入下一个数据(否则这个数据丢弃掉)。

/* 轮询模式向串口写入数据 */
while(size)
{
	/* 判断UART外设中数据是佛发送完毕 */
	while(!(uart->uart_device->SR & USART_FLAG_TXE));
	/* 当所有数据发送完毕后,才发送下一个数据 */
	uart->uart_device->DR = (*ptr & 0x1FF);

	++ptr;
	--size;
}

在实时系统中,轮询模式可能会出现非常大问题,因为在实时操作系统中,当一个程序持续地执行时(轮询时),它所在的线程会一直运行,比它优先级低的线程都不会得到运行。而分时系统中,这点恰恰相反,几乎没有优先级之分,可以在一个时间片运行这个程序,然后在另外一段时间片上运行另外一段程序。

所以通常情况下,实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。

例如一些携带FIFO(包含一定数据量的先进先出队列)的串口外设,其写入过程可以是这样的,如下图所示:
在这里插入图片描述
线程先向串口的FIFO中写入数据,当FIFO满时,线程主动挂起。
串口控制器持续地从FIFO中取出数据并以配置的波特率(例如115200bps)发送出去。当FIFO中所有数据都发送完成时,将向处理器触发一个中断;中断服务程序得到执行时,唤醒这个线程。

对于低速设备来说,运用这种模式非常好,因为在串口外设把FIFO中的数据发送出去前,处理器可以运行其它的线程,这样就提高了系统的整体运行效率。

但是对于一些高速设备,例如传输速度达到10Mbps的时候,假设一次发送的数据量是32字节,我们可以计算出发送这样一段数据量需要的时间是:(32x8)/10M = 25us。
当数据需要持续传输时,系统将在25us后触发一个中断以唤醒上层线程继续下次传递。
假设系统的线程切换时间是8us(通常实时操作系统的线程上下文切换时间只有几个us),那么当整个系统运行时,对于数据带宽利用率将只有25/(25+8)=75.8%。
但是采用轮询模式,数据带宽的利用率则可能达到100%。这个也是大家普遍认为实时系统中数据吞吐量不足的缘故,系统开销消耗在了线程切换上。

通过上述的计算过程,我们可以看出其中的一些关键因素:发送数据量越小,发送速度越快,对于数据吞吐量的影响就越大。归根结底,取决于系统中产生中断的频率如何。
当一个实时系统想要提升数据吞吐量时,可以考虑几种方式:

  1. 增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据。
  2. 必要情况下更改中断模式为轮训模式。同时为了解决轮询方式一直抢占处理机,其它优先级线程得不到运行的情况,可以把轮询线程的优先级降低。

由于关闭全局中断会导致整个系统不能响应中断,所以在使用关闭全局中断做为互斥访问临界区的手段时,必须需要保证关闭全局中断的时间非常短,例如运行数条机器指令的时间。

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