FreeRTOS中断安全API和推迟中断处理

前面的文章都提到了部分FreeRTOS的API有中断安全的版本,下面详解一下,为什么会这样。

参考资料:

《Mastering the FreeRTOS™ Real Time Kernel》-Chapter 6 Interrupt Management 6.2 Using the FreeRTOS API from an ISR/6.3 Deferred Interrupt Processing

FreeRTOS全解析-7.中断安全API和推迟中断处理

目录

1.使用FreeRTOS中断安全的API

1.1什么是中断安全API

1.2使用两套API的优点

1.3使用两套API的缺点

1.4xHigherPriorityTaskWoken参数

1.5中断中切换任务

2.中断的延迟处理


1.使用FreeRTOS中断安全的API

不了解中断的,可以看一下中断基础知识:嵌入式Linux入门-异常与中断(流程+寄存器全解析)

1.1什么是中断安全API

部分FreeRTOS的API中的部分行为,在中断服务例程(ISR)中执行是非法的,有可能破坏FreeRTOS的调度,或者出现其他不可预测的问题,就比如进入阻塞态,只有任务才能进入阻塞态,ISR又不是任务。

但是在写一个中断服务例程(ISR)时,我们也可能会需要用到一些FreeRTOS的API,这就导致了存在不安全的情况,为了解决这个问题,FreeRTOS提供了两套API,后缀名为FromISR的是中断安全API

FreeRTOS中断安全API和推迟中断处理_第1张图片

这里就引发一个问题了,为什么要使用两套API,而不是使用同一个函数,在函数中判断一下是处于任务中还是处于ISR中,再分别进行不同操作?

1.2使用两套API的优点

归根结底,就是更加高效。

(1)使用同一套函数的话,需要增加额外的判断代码、增加额外的分支,会让函数更长、更复杂、难以测试

(2)在任务、ISR中调用时,需要的参数不一样。就像阻塞,通过参数指定阻塞时间,而ISR中根本就无法阻塞,不需要这个参数。

(3)使用同一套函数的话,移植FreeRTOS时必须提供检测是处于任务还是ISR的方法,有一些芯片架构不好检测,则需要更多的代码来判断。

1.3使用两套API的缺点

使用两套API会引入一个问题,比如你要使用第三方库函数时,即需要在任务中调用它,也需要在ISR中调用它。这个第三方库函数用到了FreeRTOS的API函数。可以用下面的方法解决这个问题:

(1)把中断的处理推迟到任务中进行,在任务中调用库函数

(2)在库函数中使用"FromISR"函数:在任务中、在ISR中都可以调用"FromISR"函数,反过来就不行,非FromISR函数无法在ISR中使用。

(3)第三方库函数也许会提供OS抽象层,自行判断当前环境是在任务还是在ISR中,分别调用不同的函数。

1.4xHigherPriorityTaskWoken参数

xHigherPriorityTaskWoken字面上理解Higher更高的Priority优先级Task任务Woken被唤醒。含义就是:是否有更高优先级的任务被唤醒了。如果为pdTRUE,则意味着后面要进行任务切换,下面详解:

很多API会导致任务切换(或者说上下文切换context switch)

以写队列为例:不了解队列的可以看这篇

FreeRTOS全解析-5.队列(Queue)

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

(1)队列满了,任务A阻塞等待,另一个任务B运行

(2)队列没满,任务A成功写入队列,但是有另一个任务B(处于阻塞态,等待队列有数)任务B被唤醒,任务B的优先级更高:任务B先运行

(3)队列没满,任务A成功写入队列,即刻返回

情况1和2都导致了任务切换。并且这个切换是发生在任务A当中的,xQueueSendToBack()还没有返回就切换了。

让我们再来看看中断时调用类似API会发生什么情况

根据上文中的论述,我们在中断中要写队列,应该调用xQueueSendToBackFromISR()。

BaseType_t xQueueSendToBack( QueueHandle_t xQueue,                  const void * pvItemToQueue,                  TickType_t xTicksToWait );BaseType_t xQueueSendToBackFromISR(                  QueueHandle_t xQueue,                  const void *pvItemToQueue,                  BaseType_t *pxHigherPriorityTaskWoken);

可以看到xTicksToWait换成了pxHigherPriorityTaskWoken,因为中断是无法阻塞的。而xQueueSendToBackFromISR函数内部不会切换,只是用pxHigherPriorityTaskWoken参数来保存函数的结果:是否需要切换。

用法:pxHigherPriorityTaskWoken参数使用前需要初始化为pdFALSE。

当它为pdTRUE时表示有任务需要切换。​​​​​​​

BaseType_t xHigherPriorityTaskWoken = pdFALSE;xQueueSendToBackFromISR(xQueue, pvItemToQueue, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE){    /* 任务切换 */    }

为什么不在API内部切换?

(1)避免没必要的切换

比如UART中断:在UART的ISR中读取字符,我们处理任务往往处理的是一个字符串,而不是单个字符,收到一个字符串后(发现收到回车符),才有必要切换到字符串处理任务。假如在API内部切换,则会变成,中断,处理,中断,处理....效率低下。

(2)让ISR更可控

中断产生的时机是不可预测的,在API中进行任务切换的话,会导致问题更复杂

(3)可移植性更强

(4)高效。部分芯片架构只允许在ISR最后进行切换,如果要修改这部分限制,需要更多的代码。

(5)FreeRTOS Tick中断中可以开启钩子函数,也就是可以调用自己的代码。这时候就需要上述的优点了。

如果你不需要切换,用不到这个参数,传入NULL就可以了。

1.5中断中切换任务

在任务中主动切换,调用taskYIELD(),在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);
}

2.中断的延迟处理

ISR要尽可能得简短快速。原因包括:

(1)即使任务被分配了非常高的优先级,它们也只会在硬件没有服务中断的情况下运行。

(2)ISR会扰乱一个任务的开始时间和执行时间

(3)部分CPU架构在ISR时无法处理新的中断。

(4)写程序时需要考虑变量、外设和内存缓冲区等资源同时被任务和ISR访问的后果并加以防范。

(5)一些CPU架构允许中断嵌套,但是中断嵌套会增加复杂性并降低可预测性。中断越短,嵌套的可能性就越小。

在ISR中做一些必要的工作,比如记录和清除中断,然后把其他的工作放到一个任务中去运行,这就叫延迟中断处理。

只要把这个任务优先级设置得比其他任务高,那么发生中断后,这个任务就会立即运行,就像在中断中运行的效果一样。

FreeRTOS中断安全API和推迟中断处理_第2张图片

如图Task2是中断处理任务,一开始是处于阻塞态。

t1时Task1运行,t2时发生中断,ISR执行,清除中断,解除Task2的阻塞态。

t3时ISR运行结束,Task2的优先级比Task1高,所以ISR结束后直接跳到Task2运行。

t4时Task2运行结束进入阻塞态,等待下一次中断,Task1运行。

什么时候在ISR中处理,什么时候推迟到任务中,没有一个必然的规定,下面几种情况最好在任务中处理:

(1)处理比较复杂。例如,如果中断只是存储模拟到数字转换的结果,那么这最好在ISR中执行,但如果转换的结果也必须通过软件过滤器,那么最好在任务中执行过滤器。

(2)存在ISR内部无法执行的操作,例如写入控制台或分配内存。

(3)处理时间是不确定的——不知道处理将花费多长时间。

当然还有别的办法推迟中断处理,将在后面的文章中提到。

你可能感兴趣的:(FreeRTOS,单片机,FreeRTOS,嵌入式)