FreeRTOS:为什么要在isrHandler中调用taskYIELD_FROM_ISR()方法

10

我试图理解为什么用户必须调用taskYIELD_FROM_ISR()方法,而为什么它不会在xStreamBufferSendFromISR方法内部被RTOS自动调用。

我的问题涉及到FreeRTOS手册第369页。

/* A stream buffer that has already been created. */
StreamBufferHandle_t xStreamBuffer;

void vAnInterruptServiceRoutine( void ) {
  size_t xBytesSent;
  char *pcStringToSend = "String to send";
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  /* Attempt to send the string to the stream buffer. */
  xBytesSent = xStreamBufferSendFromISR(xStreamBuffer,(void *) pcStringToSend,strlen( pcStringToSend),&xHigherPriorityTaskWoken);

  if(xBytesSent != strlen(pcStringToSend)){
    /* There was not enough free space in the stream buffer for the entire string to be written, ut xBytesSent bytes were written. */
  }

  /* 
    If xHigherPriorityTaskWoken was set to pdTRUE inside xStreamBufferSendFromISR() then a    
    task that has a priority above the priority of the currently executing task was unblocked 
    and a context switch should be performed to ensure the ISR returns to the unblocked task. 
    In most FreeRTOS ports this is done by simply passing xHigherPriorityTaskWoken into 
    taskYIELD_FROM_ISR(), which will test the variables value, and perform the context switch 
    if necessary. Check the documentation for the port in use for port specific instructions. 
  */

  taskYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

场景理解

前提条件

  • 有两个任务
    • Task1 (高优先级) 和 Task2 (低优先级)
  • Task1 处于阻塞状态,等待流缓冲区输入
  • Task2 处于运行状态,并从 vAnInterruptServiceRoutine 中断

ISR 内部

  • ISR 方法调用 xStreamBufferSendFromISR()
  • 这个调用意味着,Task1 的阻塞状态将变为就绪状态

情形 A 如果 ISR 方法现在返回,调度程序将不会被调用,Task2 将一直运行,直到时间片结束,然后调度程序切换到高优先级的 Task1。

情形 B 如果 ISR 方法最后调用了 taskYIELD_FROM_ISR(xHigherPriorityTaskWoken);,则调度程序将被调用,在 ISR 返回后,Task1 将代替 Task2 运行。

问题

  1. 是否正确,即 Task2 将执行到一个时间片段结束,或者还有其他触发任务切换的原因?
  2. 为什么在调用 xStreamBufferSendFromISR() 时,RTOS 不会自动调用 taskYIELD_FROM_ISR() 方法?
2个回答

11
  1. 是的,在没有调用 taskYIELD_FROM_ISR 的情况下,调度器会在下一个预定的时钟滴答上下文切换到 Task1。可以使用Segger SystemView进行验证。uartPrintTask 在一个信号量上阻塞,然后从缓冲区打印数据。注意,在 uartPrintTask 准备好和运行之间存在很长的延迟。这个延迟是可变的,尽管它永远不会超过1毫秒(示例中的时钟滴答率) enter image description here

现在,同样的示例加上 taskYIELD_FROM_ISR 在ISR末尾调用。uartPrintTask 在ISR执行后一致地被执行。 enter image description here 2. FreeRTOS无法自动从ISR中调用任何内容。您完全控制所有中断。应该将 taskYIELD_FROM_ISR 放置在ISR实现的末尾(但您可以在ISR中的任何位置调用 xStreamBufferSendFromISR)。

FreeRTOS(在我看来)的一个美妙之处是它不会劫持任何东西,并且非常灵活。您可以有完全在RTOS“下方”执行的中断 - FreeRTOS 不需要知道任何关于它们的信息。不自动调用taskYIELD_FROM_ISR就是这种灵活性的另一个例子(在我看来)。
SafeRTOS 手册中引用:
调用xQueueSendFromISR()或xQueueReceiveFromISR()其中之一,可能导致任务离开阻塞状态,然后如果未阻塞任务优先级高于中断任务,则需要进行上下文切换。当xQueueSend()或xQueueReceive()使比调用任务优先级高的任务退出阻止状态时,API函数内部透明地执行上下文切换。该行为对于任务是理想的,但对于中断服务例程则不是。因此,xQueueSendFromISR()和xQueueReceiveFromISR()不是自己执行上下文切换,而是返回指示是否需要上下文切换的值。如果需要上下文切换,则应用程序编写人员可以使用taskYIELD_FROM_ISR()在最合适的时间执行上下文切换,通常在中断处理程序的结尾处。请参见请参见第69页的“xQueueSendFromISR()”和第71页的“xQueueReceiveFromISR()”,分别描述了有关xQueueSendFromISR()和xQueueReceiveFromISR()函数的更多信息。

1
虽然有些人可能会将此作为FreeRTOS的“特性”来销售,但在ISR中调用taskYIELD_FROM_ISR()以及使用“FromISR”API会给程序员带来很大的负担。这也导致了API的重复(“常规”和“FromISR()”API)。但最不幸的是,FreeRTOS的这种设计方面打开了一系列可能出错的范围,其中错误的API类型被使用。例如,回调函数经常从ISR上下文中调用。此外,标志xHigherPriorityTaskWoken需要以某种方式向上传播到调用堆栈。 - Miro Samek
1
是的,额外的“FromISR”变体确实增加了另一组需要理解的函数。但我不明白你在回调方面的观点......从中断服务例程(ISR)使用回调函数与FreeRTOS有什么关系?你是在主张应该禁止回调吗?我很好奇,因为在采用RTOS之前,在事件驱动的“裸机”解决方案中我已经使用它们而没有任何问题。回调可以是一种非常轻量级的方式,可以为否则是标准可重用ISR添加灵活性。显然,它们是双刃剑,因为它们在ISR上下文中执行。 - bamos
1
回调函数肯定很快,但你总是需要非常小心地考虑回调被调用的“上下文”(context)。在FreeRTOS中,这种情况还进一步恶化,因为需要在某些回调(例如FreeRTOS软件定时器)中使用“常规”API,在其他回调(例如FreeRTOS “tick hook function”)中使用“FromISR” API。很容易犯错误,而FreeRTOS并没有帮助你知道何时犯了错。 - Miro Samek
2
所有的陈述都是公正的。我认为FreeRTOS在这些方面与'C'非常相似——有很多原始的力量,但必须小心谨慎。大量使用configAssert()可以在开发周期的早期发现配置和使用问题,这是一个很大的帮助。 - bamos
在你的实验中调用portYIELD_FROM_ISR(),为什么“浪费的任务”在中断服务程序之后还会运行一小段时间? - Daniel Chin
1
@DanielChin - wastefulTask会消耗所有可用的CPU时间:void wastefulTask( void* NotUsed) { while(1) { volatile int i, j; i = 10; j = i; i = j; } } 完整示例的代码可以在github上找到。这个例子来自于《Hands on RTOS with Microcontrollers》(https://www.amazon.com/Hands-RTOS-Microcontrollers-Building-real-time/dp/1838826734)。 - bamos

-1
  • 1)我认为是这样的。
  • 2)我不太确定。我认为这是为了保持中断快速和上下文切换开销低而做出的。因此,根据您需要的响应时间,触发上下文切换还是不触发取决于您自己。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接