两套函数

在任务函数中,可以调用各类API函数,比如队列操作函数xQueueSendToBack(),但是在中断服务程序ISR(Interrupt Service Routine)中不能使用这个函数,而应该使用xQueueSendToBackFromISR(),表示在中断中给队列发送数据。

为什么要有两套函数?

很多API函数会导致调用该函数的任务进入阻塞状态,比如任务A调用

xQueueSendToBack()写队列,此时队列满,任务A就会进入阻塞状态。但如果是ISR,由于ISR不是任务,不能进入阻塞,所以要加以区分

两套函数的区别:中断要尽快处理完

/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    TickType_t xTicksToWait
    );
/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
    );

以写队列函数为例,在任务中写入xQueueSend()时可以指定超时时间,假设xTicksToWait = 100,那么在不发生唤醒更高优先级任务的情况下,任务A执行xQueueSend()完成的耗时为0 - 100个Tick,如果写入队列后唤醒了更高优先级的任务B,那么耗时会更长,这在中断服务程序ISR中是不允许的

在中断中使用xQueueSend(Queue, Val, 0)能否替代xQueueSendToBackFromISR()呢?答案是不能,xTicksToWait = 0仅仅指定了队列满时不进入阻塞,直接返回,但是如果队列不满,写入操作成功,并且发现有更高优先级的任务正在等待该队列数据时,会在函数内部主动发起任务切换请求,这在ISR中是不允许的。xQueueSendToBackFromISR()使用pxHigherPriorityTaskWoken参数标记一个状态,等退出ISR时,手动调用portYIELD_FROM_ISR()进行安全的任务切换。

改进实时性

当前game1框架

15_two_type_functions

以IRReceiver为例,在中断服务程序IRReceiver_IRQ_Callback()中调用IRReceiver_IRQTimes_Parse(),在其中调用DispatchKey(),使用xQueueSendFromISR()写队列,整体结构为

image-20260726080145571

xQueueSendFromISR()中可能唤醒更高优先级任务,但是不发生调度,这会导致任务A中断中唤醒的高优先级任务B不能在中断结束之后得到响应,而是需要等到下一个Tick中断来时才能发生调度,运行任务B

image-20260726080433864

正确的流程应该是:任务A在中断服务程序中唤醒了高优先级任务B,但是不立刻调度,而是在中断函数即将退出时,发起调度使任务B得到响应,这样既保证了中断函数执行尽可能快,又保证了高优先级任务得到响应尽可能快

如何切换任务?使用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);
}

优化后的DispatchKey()使用xHigherPriorityTaskWoken标记写队列过程中是否唤醒了更高优先级任务,在中断服务程序退出时,由portYIELD_FROM_ISR()根据它的值判断是否需要发起调度

static void DispatchKey(struct ir_data *pidata)
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;

	int i;
	for (i = 0; i < g_queue_cnt; i++)
	{
		xQueueSendFromISR(g_xQueues[i], pidata, &xHigherPriorityTaskWoken);
	}
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐