任务中断的两套API函数
为什么需要两套 API
在任务函数中,我们可以调用各类 API 函数,比如队列操作函数:xQueueSendToBack。
但是在 ISR 中使用这个函数会导致问题,应该使用另一个函数:xQueueSendToBackFromISR,
它的函数名含有后缀"FromISR",表示"从 ISR 中给队列发送数据"。
FreeRTOS 中很多 API 函数都有两套:一套在任务中使用,另一套在 ISR 中使用。后者的
函数名含有 "FromISR" 后缀。
为什么要引入两套 API 函数?
⚫ 很多 API 函数会导致任务计入阻塞状态:
◼ 运行这个函数的 任务 进入阻塞状态
◼ 比如写队列时,如果队列已满,可以进入阻塞状态等待一会
⚫ ISR 调用 API 函数时, ISR 不是 " 任务 " , ISR 不能进入阻塞状态
⚫ 所以,在任务中、在 ISR 中,这些函数的功能是有差别的
FreeRTOS 使用两套函数,而不是使用一套函数,是因为有如下好处:
⚫ 使用同一套函数的话,需要增加额外的判断代码、增加额外的分支,是的函
数更长、更复杂、难以测试
⚫ 在任务、 ISR 中调用时,需要的参数不一样,比如:
◼ 在任务中调用:需要指定超时时间,表示如果不成功就阻塞一会
◼ 在 ISR 中调用:不需要指定超时时间,无论是否成功都要即刻返回
◼ 如果强行把两套函数揉在一起,会导致参数臃肿、无效
⚫ 移植 FreeRTOS 时,还需要提供监测上下文的函数,比如 is_in_isr()
⚫ 有些处理器架构没有办法轻易分辨当前是处于任务中,还是处于 ISR 中,就
需要额外添加更多、更复杂的代码
使用两套函数可以让程序更高效,但是也有一些缺点,比如你要使用第三方库函数时,
即会在任务中调用它,也会在 ISR 总调用它。这个第三方库函数用到了 FreeRTOS 的 API 函数,
你无法修改库函数。这个问题可以解决:
⚫ 把中断的处理推迟到任务中进行 (Defer interrupt processing) ,在任务中调用库
函数
⚫ 尝试在库函数中使用 "FromISR" 函数:
◼ 在任务中、在 ISR 中都可以调用 "FromISR" 函数
◼ 反过来就不行,非 FromISR 函数无法在 ISR 中使用
⚫ 第三方库函数也许会提供 OS 抽象层,自行判断当前环境是在任务还是在
ISR 中,分别调用不同的函数
两套 API 函数列表
区别
ISR
有任务的唤醒,但是不进行调度
在中断API中,中断的特性是必须快速执行,所以在运行中,如果有更高级的任务B来执行,那就会唤醒B,但是不会去执行B
ISR执行操作有唤醒任务,将任务放入Readylist,并记录是否有更高优先级的任务被唤醒,以便在中断结束后执行
非中断
有任务的唤醒,进行任务的调度
改进FormISR的实时性代码
cpp
void XXX_ISR()
{
int i;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;//保证高优先级的任务被及时执行
for (i = 0; i < N; i++)
{
xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
}
/* 最后再决定是否进行任务切换
* xHigherPriorityTaskWoken 为 pdTRUE 时才切换
*/
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/**********************************************************************
* 函数名称: GetKeyFromBuf
* 功能描述: 从环形缓冲区读取按键数据
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0xff - 读不到数据, 其他值-按键值(device或key)
* 修改日期: 版本号 修改人 修改内容
* -----------------------------------------------
* 2023/08/04 V1.0 韦东山 创建
***********************************************************************/
static unsigned char GetKeyFromBuf(void)
{
unsigned char key = 0xff;
if (!isKeysBufEmpty())
{
key = g_KeysBuf[g_KeysBuf_R];
g_KeysBuf_R = NEXT_POS(g_KeysBuf_R);
}
return key;
}
void RegisterQueueHandle(QueueHandle_t queueHandle)
{
if (g_queue_cnt < 10)
{
g_xQueues[g_queue_cnt] = queueHandle;
g_queue_cnt++;
}
}
static void DispatchKey(struct ir_data *pidata)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
#if 0
extern QueueHandle_t g_xQueueCar1;
extern QueueHandle_t g_xQueueCar2;
extern QueueHandle_t g_xQueueCar3;
xQueueSendFromISR(g_xQueueCar1, pidata, NULL);
xQueueSendFromISR(g_xQueueCar2, pidata, NULL);
xQueueSendFromISR(g_xQueueCar3, pidata, NULL);
#else
int i;
for (i = 0; i < g_queue_cnt; i++)
{
xQueueSendFromISR(g_xQueues[i], pidata, &xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
#endif
}
资源管理_互斥操作的本质
在前面讲解互斥量时,引入过临界资源的概念。在前面课程里,已经实现了临界资源的
互斥访问。
本章节的内容比较少,只是引入两个功能:屏蔽 / 使能中断、暂停 / 恢复调度器。
要独占式地访问临界资源,有 3 种方法:
⚫ 公平竞争:比如使用互斥量,谁先获得互斥量谁就访问临界资源,这部分内
容前面讲过。
⚫ 谁要跟我抢,我就灭掉谁:
◼ 中断要跟我抢?我屏蔽中断
◼ 其他任务要跟我抢?我禁止调度器,不运行任务切换
屏蔽中断
屏蔽中断有两套宏:任务中使用、ISR 中使用:
⚫ 任务中使用: taskENTER_CRITICA()/taskEXIT_CRITICAL()
⚫ ISR 中使用:
taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()
cpp
void vAnInterruptServiceRoutine( void )
{
/* 用来记录当前中断是否使能 */
UBaseType_t uxSavedInterruptStatus;
/* 在 ISR 中,当前时刻中断可能是使能的,也可能是禁止的
* 所以要记录当前状态, 后面要恢复为原先的状态
* 执行这句代码后,屏蔽中断
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 访问临界资源 */
/* 恢复中断状态 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 现在,当前 ISR 可以被更高优先级的中断打断了 */
}
暂停调度器
如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运
行,但是这代价太大了。它会影响到中断的处理。
如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,
中断还是可以发生、处理。
cpp
/* 暂停调度器 */
void vTaskSuspendAll( void );
/* 恢复调度器
* 返回值: pdTRUE 表示在暂定期间有更高优先级的任务就绪了
* 可以不理会这个返回值
*/
BaseType_t xTaskResumeAll( void );