16、FreeRTOS 中断管理(Interrupt Management)

一、什么是中断

1.1 中断的理解

中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

1.2 中断优先级

在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的优先级并不是从 0~15 ,默认情况下它是从 5~150~4 这 5 个中断优先级不是 FreeRTOS 控制的(5是取决于configMAX_SYSCALL_INTERRUPT_PRIORITY)。

注意:

  • 在中断中必需使用中断相关的函数
  • 中断服务函数运行时间越短越好(快进快出)

1.3 中断的特性

在RTOS中,需要应对各类事件。这些事件很多时候是通过硬件中断产生,怎么处理中断呢?

假设当前系统正在运行Task1时,用户按下了按键,触发了按键中断。这个中断的处理流程如下:

  • CPU跳到固定地址去执行代码,这个固定地址通常被称为中断向量,这个跳转时硬件实现的
  • 执行代码做什么?
    • 保存现场:Task1被打断,需要先保存Task1的运行环境,比如各类寄存器的值
    • 分辨中断、调用处理函数(这个函数就被称为ISR,interrupt service routine)
    • 恢复现场:继续运行Task1,或者运行其他优先级更高的任务

你要注意到,ISR是在内核中被调用的,ISR执行过程中,用户的任务无法执行。ISR要尽量快,否则:

  • 其他低优先级的中断无法被处理:实时性无法保证
  • 用户任务无法被执行:系统显得很卡顿

如果这个硬件中断的处理,就是非常耗费时间呢?对于这类中断的处理就要分为2部分:

  • ISR:尽快做些清理、记录工作,然后触发某个任务
  • 任务:更复杂的事情放在任务中处理
  • 所以:需要ISR和任务之间进行通信

要在FreeRTOS中熟练使用中断,有几个原则要先说明:

  • FreeRTOS把任务认为是硬件无关的,任务的优先级由程序员决定,任务何时运行由调度器决定
  • ISR虽然也是使用软件实现的,但是它被认为是硬件特性的一部分,因为它跟硬件密切相关
    • 何时执行?由硬件决定
    • 哪个ISR被执行?由硬件决定
  • ISR的优先级高于任务:即使是优先级最低的中断,它的优先级也高于任务。任务只有在没有中断 的情况下,才能执行。

二、 两套API函数

2.1 为什么会使用两套API?

在任务函数中,我们可以调用各类API函数,比如队列操作函数:xQueueSendToBack。但是在ISR中使 用这个函数会导致问题,应该使用另一个函数:xQueueSendToBackFromISR,它的函数名含有后 缀 "FromISR",表示"从ISR中给队列发送数据"。

FreeRTOS中很多API函数都有两套:一套在任务中使用,另一套在ISR中使用。后者的函数名含 有"FromISR"后缀。

为什么要引入两套API函数?

  • 很多API函数会导致任务计入阻塞状态:
    • 运行这个函数的任务进入阻塞状态
    • 比如写队列时,如果队列已满,可以进入阻塞状态等待一会
  • ISR调用API函数时,ISR不是"任务"ISR不能进入阻塞状态

所以,在任务中、在ISR中,这些函数的功能是有差别的 为什么不使用同一套函数,比如在函数里面分辨当前调用者是任务还是ISR呢?示例代码如下:

BaseType_t xQueueSend(...)
{
    if (is_in_isr())
    {
        /* 把数据放入队列 */
        /* 不管是否成功都直接返回 */
        }
    else /* 在任务中 */
    {
        /* 把数据放入队列 */
        /* 不成功就等待一会再重试 */
    }
}

FreeRTOS使用两套函数,而不是使用一套函数,是因为有如下好处:

  • 使用同一套函数的话,需要增加额外的判断代码、增加额外的分支,是的函数更长、更复杂、难以 测试
  • 在任务、ISR中调用时,需要的参数不一样,比如:
    • 在任务中调用:需要指定超时时间,表示如果不成功就阻塞一会
    • 在ISR中调用:不需要指定超时时间,无论是否成功都要即刻返回
    • 如果强行把两套函数揉在一起,会导致参数臃肿、无效
  • 移植FreeRTOS时,还需要提供监测上下文的函数,比如is_in_isr()
  • 有些处理器架构没有办法轻易分辨当前是处于任务中,还是处于ISR中,就需要额外添加更多、更 复杂的代码

使用两套函数可以让程序更高效,但是也有一些缺点,比如你要使用第三方库函数时,即会在任务中调 用它,也会在ISR总调用它。这个第三方库函数用到了FreeRTOSAPI函数,你无法修改库函数。这个 问题可以解决:

  • 把中断的处理推迟到任务中进行(Defer interrupt processing),在任务中调用库函数
  • 尝试在库函数中使用**“FromISR”**函数:
    • 在任务中、在ISR中都可以调用"FromISR"函数
    • 反过来就不行,非FromISR函数无法在ISR中使用
  • 第三方库函数也许会提供OS抽象层,自行判断当前环境是在任务还是在ISR中,分别调用不同的函 数

2.2 两套API函数列表

类型在任务中在ISR中
队列(queue)xQueueSendToBackxQueueSendToBackFromISR
xQueueSendToFrontxQueueSendToFrontFromISR
xQueueReceivexQueueReceiveFromISR
xQueueOverwritexQueueOverwriteFromISR
xQueuePeekxQueuePeekFromISR
信号量(semaphore)xSemaphoreGivexSemaphoreGiveFromISR
xSemaphoreTakexSemaphoreTakeFromISR
事件组(event group)xEventGroupSetBitsxEventGroupSetBitsFromISR
xEventGroupGetBitsxEventGroupGetBitsFromISR
任务通知(task notification)xTaskNotifyGivevTaskNotifyGiveFromISR
xTaskNotifyxTaskNotifyFromISR
软件定时器(software timer)xTimerStartxTimerStartFromISR
xTimerStopxTimerStopFromISR
xTimerResetxTimerResetFromISR
xTimerChangePeriodxTimerChangePeriodFromISR

2.3 xHigherPriorityTaskWoken参数

xHigherPriorityTaskWoken的含义是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着 后面要进行任务切换。

还是以写队列为例。

任务A调用 xQueueSendToBack() 写队列,有几种情况发生:

  • 队列满了,任务A阻塞等待,另一个任务B运行
  • 队列没满,任务A成功写入队列,但是它导致另一个任务B被唤醒,任务B的优先级更高:任务B先 运行
  • 队列没满,任务A成功写入队列,即刻返回

可以看到,在任务中调用API函数可能导致任务阻塞、任务切换,这叫做"context switch",上下文切 换。这个函数可能很长时间才返回,在函数的内部实现了任务切换。

xQueueSendToBackFromISR() 函数也可能导致任务切换,但是不会在函数内部进行切换,而是返回一 个参数:表示是否需要切换,函数原型与用法如下:

/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
                                    QueueHandle_t xQueue,
                                    const void *pvItemToQueue,
                                    BaseType_t *pxHigherPriorityTaskWoken
                                    );
/* 用法示例 */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendToBackFromISR(xQueue, pvItemToQueue, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE)
{
/* 任务切换 */
}

pxHigherPriorityTaskWoken参数,就是用来保存函数的结果:是否需要切换

  • *pxHigherPriorityTaskWoken 等于pdTRUE:函数的操作导致更高优先级的任务就绪了,**ISR ** 应该 进行任务切换
  • *pxHigherPriorityTaskWoken 等于pdFALSE:没有进行任务切换的必要 为什么不在**“FromISR”**函数内部进行任务切换,而只是标记一下而已呢?为了效率!示例代码如下:
void XXX_ISR()
{
    int i;
    for (i = 0; i < N; i++)
    {
    	xQueueSendToBackFromISR(...); /* 被多次调用 */
    }
}

ISR中有可能多次调用"FromISR"函数,如果在"FromISR"内部进行任务切换,会浪费时间。解决方法 是:

  • "FromISR"中标记是否需要切换

  • ISR返回之前再进行任务切换

  • 示例代码如下

    void XXX_ISR()
    {
        int i;
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        for (i = 0; i < N; i++)
        {
        	xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
        }
    	/* 最后再决定是否进行任务切换 */
        if (xHigherPriorityTaskWoken == pdTRUE)
        {
        	/* 任务切换 */
        }
    }
    
    

上述的例子很常见,比如UART中断:在UARTISR中读取多个字符,发现收到回车符时才进行任务切 换。

ISR中调用API时不进行任务切换,而只是在"xHigherPriorityTaskWoken"中标记一下,除了效率,还 有多种好处:

  • 效率高:避免不必要的任务切换
  • 让ISR更可控:中断随机产生,在API中进行任务切换的话,可能导致问题更复杂
  • 可移植性
  • 在Tick中断中,调用 vApplicationTickHook() :它运行与ISR,只能使用"FromISR"的函数

使用**"FromISR"函数时,如果不想使用xHigherPriorityTaskWoken参数,可以设置为NULL**。

2.4 怎么切换任务

FreeRTOS的ISR函数中,使用两个宏进行任务切换:

portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

这两个宏做的事情是完全一样的,在老版本的FreeRTOS中,

  • portEND_SWITCHING_ISR 使用汇编实现
  • portYIELD_FROM_ISR 使用C语言实现

新版本都统一使用 portYIELD_FROM_ISR

使用示例如下:

void XXX_ISR()
{
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    for (i = 0; i < N; i++)
    {
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
    /* 最后再决定是否进行任务切换
    * xHigherPriorityTaskWoken为pdTRUE时才切换
    */
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

三、中断的延迟处理

前面讲过,ISR要尽量快,否则:

  • 其他低优先级的中断无法被处理:实时性无法保证
  • 用户任务无法被执行:系统显得很卡顿
  • 如果运行中断嵌套,这会更复杂,ISR越快执行约有助于中断嵌套

如果这个硬件中断的处理,就是非常耗费时间呢?对于这类中断的处理就要分为2部分:

  • ISR:尽快做些清理、记录工作,然后触发某个任务
  • 任务:更复杂的事情放在任务中处理

这种处理方式叫**“中断的延迟处理”(Deferring interrupt processing)**,处理流程如下图所示:

  • t1:任务1运行,任务2阻塞

  • t2:发生中断,

    • 该中断的ISR函数被执行,任务1被打断
    • ISR函数要尽快能快速地运行,它做一些必要的操作(比如清除中断),然后唤醒任务2
  • t3:在创建任务时设置任务2的优先级比任务1高(这取决于设计者),所以ISR返回后,运行的是任 务2,它要完成中断的处理。任务2就被称为"deferred processing task",中断的延迟处理任务。

  • t4:任务2处理完中断后,进入阻塞态以等待下一个中断,任务1重新运行

在这里插入图片描述

四、中断与任务之间的通讯

前面讲解过的队列、信号量、互斥量、事件组、任务通知等等方法,都可使用。

要注意的是,在ISR中使用的函数要有"FromISR"后缀。


文章是自己总结而记录,有些知识点没说明白的,请各位看官多多提意见,多多交流,欢迎大家留言
如果技术交流可以加以下群,方便沟通
QQ群:370278903
点击链接加入群聊【蜡笔小芯的嵌入式交流群】
![])

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

^Lim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值