前言
在RTOS
中,需要应对各类事件。这些事件很多时候是通过硬件中断产生。假设当前系统正在运行Task1
任务,用户按下了按键,触发了按键中断。触发中断后:硬件负责: CPU跳到固定地址去执行代码,这个固定地址通常被称为中断向量。
软件负责:
- 保存现场:
Task1
被打断,需要先保存Task1
的运行环境,比如各类寄存器的值; - 调用处理函数(这个函数就被称为ISR,interrupt service
routine) - 恢复现场:继续运行
Task1
,或者运行其他优先级更高的任务。
本章主要涉及
- 两套API
- 两类中断
- 优先级
一、ISR的API函数
FreeRTOS中很多API函数都有两套:一套在任务中使用,另一套在ISR中使用。这两套区别主要在于可否阻塞和是否挑出最高优先级任务运行。
类型 | 在任务中 | 在ISR中 |
---|---|---|
队列(queue) | xQueueSendToBack | xQueueSendToBackFromISR |
xQueueSendToFront | xQueueSendToFrontFromISR | |
xQueueReceive | xQueueReceiveFromISR | |
xQueueOverwrite | xQueueOverwriteFromISR | |
xQueuePeek | xQueuePeekFromISR | |
信号量(semaphore) | xSemaphoreGive | xSemaphoreGiveFromISR |
xSemaphoreTake | xSemaphoreTakeFromISR | |
事件组(event group) | xEventGroupSetBits | xEventGroupSetBitsFromISR |
以写队列为例,来看一下这两个API有什么区别
xQueueSendToBack | xQueueSendToBackFromISR | |
---|---|---|
参数不同 | xTicksToWait: 队列满阻塞时间 | 没有xTicksToWait |
唤醒等待的任务 | 写队列后,会唤醒等待数据的任务,如果唤醒任务优先级高会发起调度 | 写队列后,会唤醒等待数据的任务但不调度,只会记录是否需要发起调度 |
阻塞 | 如果队列满,可以阻塞 也可以即刻返回 | 如果队列满,不可以阻塞,即刻返回 |
二、ISR的中断
根据中断优先级我们把中断分为两类,一类处理优先级较低允许调用 API,另一类优先级较高不能调用API,在ISR函数里面会先判断中断优先级是否有效ucCurrentPriority >= ucMaxSysCallPriority
来确认是否可以在中断使用ISR API
/* Obtain the number of the currently executing interrupt. */
ulCurrentInterrupt = vPortGetIPSR();
/* Is the interrupt number a user defined interrupt? */
if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
{
/* Look up the interrupt's priority. */
ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
/* The following assertion will fail if a service routine (ISR) for
* an interrupt that has been assigned a priority above
* configMAX_SYSCALL_INTERRUPT_PRIORITY calls an ISR safe FreeRTOS API
* function. ISR safe FreeRTOS API functions must *only* be called
* from interrupts that have been assigned a priority at or below
* configMAX_SYSCALL_INTERRUPT_PRIORITY.
*
* Numerically low interrupt priority numbers represent logically high
* interrupt priorities, therefore the priority of the interrupt must
* be set to a value equal to or numerically *higher* than
* configMAX_SYSCALL_INTERRUPT_PRIORITY.
*
* Interrupts that use the FreeRTOS API must not be left at their
* default priority of zero as that is the highest possible priority,
* which is guaranteed to be above configMAX_SYSCALL_INTERRUPT_PRIORITY,
* and therefore also guaranteed to be invalid.
*
* FreeRTOS maintains separate thread and ISR API functions to ensure
* interrupt entry is as fast and simple as possible.
*
* The following links provide detailed information:
* https://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html
* https://www.FreeRTOS.org/FAQHelp.html */
configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
}
优先级如下图
三、优先级划分
SysTick
触发任务切换:SysTick中断发生时,RTOS可能会发现当前任务需要被切换。此时,RTOS不会立即切换任务,而是设置PendSV异常挂起。
PendSV
执行任务切换:在所有中断处理完后,PendSV异常被触发,RTOS通过它来执行具体的任务切换操作(保存当前任务的上下文、恢复新任务的上下文)。
但上面这两个的中断优先级都处于最低,会被任意中断打断
四、临界资源访问
要独占式地访问临界资源,有3种方法:
- 临界资源的互斥访问
- 屏蔽/使能中断
- 暂停/恢复调度器。
4.1、屏蔽中断
代码样例
int a;
/* 在任务中,当前时刻中断是使能的
* 执行这句代码后,屏蔽中断
*/
void add_func(int val)
{
taskENTER_CRITICAL();
/* 访问临界资源 */
a += val;
/* 重新使能中断 */
taskEXIT_CRITICAL();
}
这套 taskENTER_CRITICA()/taskEXIT_CRITICAL()
宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用 taskEXIT_CRITICAL()
才会重新使能中断
在 taskENTER_CRITICA()/taskEXIT_CRITICAL()
之间: 低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断被屏蔽了。由于任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生
任务中使用: taskENTER_CRITICAL ()/taskEXIT_CRITICAL()
ISR中使用:taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()
4.2、暂停调度器
如果有别的任务来跟你竞争临界资源,你可以把中断关掉,但是这代价太大了,它会影响中断的处理。如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了,在这期间,中断还是可以发生、处理。
代码如下:
int a;
void xxx_func(int val)
{
vTaskSuspendAll();
/* 访问临界资源 */
a = val;
xTaskResumeAll();
}
vTaskSuspendAll()/xTaskResumeAll()
也是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,xTaskResumeAll()
才会重新使能调度器。