【FreeRTOS】中断机制

【FreeRTOS】之中断机制

在FreeRTOS中,中断是实现实时性必要的操作。一款芯片的中断涉及到硬件触发,软件触发,软件中断处理。所以FreeRTOS的中断机制其实不好单独拿出来看。FreeRTOS关于中断能做到的是提供一套专门在中断服务函数中使用的API,比如:xQueueSendToBack()对应xQueueSendToBackFromISR()
注意:下文有对于指令集的区分,主要以ESP-IDF(RISC-V为例)

文章目录

  • 【FreeRTOS】之中断机制
    • 中断处理
    • 中断处理流程
    • 中断处理的规则(注意事项)
    • 内核为中断提供两套API
      • xQueueSendToBack():
      • xQueueSendToBackFromISR():
      • 为何要为ISR专门设计一套API?
        • 使用两套API的好处
      • From_ISR API核心(pxHigherPriorityTaskWoken)
        • 为何`xQueueSendToBackFromISR()`中不切换最高优先级的任务?
      • 使用From_ISR API的好处
      • 用法示例
        • 中断服务函数写法(如何切换任务)
    • 中断的延迟处理(Deferring interrupt processing)
    • 如果同时发生多个中断如何处理?
      • 正在执行高优先级中断时触发一个低优先级中断
      • 中断嵌套
    • 基于中断的时间片机制--系统调度
      • Cortex-M3
      • RISC-V (ESP-IDF)
        • vPortYieldFromISR()
      • 中断代码参考
        • 中断(不同架构对中断的处理有区别,以ESP32C3 RISC-V为例)
          • 有关中断向量表的主要API调用关系
          • 在启动CPU的时候设置了中断向量表
          • _vector_table汇编
          • _interrupt_handler汇编
          • _global_interrupt_handler实现
      • 保存现场与恢复现场
        • rtos_int_enter保护现场
        • rtos_int_enter恢复现场
        • 主动任务切换函数vPortYield
          • RISCV vPortYield的实现
          • Xtensa vPortYield的实现

中断处理

中断处理主要包括硬件处理部分和软件处理部分(不同的指令集架构有不同)

  • 硬件处理:响应中断请求,关闭中断,保存断点(也就是将程序计数器PC的值保存起来,以保证执行完中断之后可以正确的返回到原来的程序),并且引出中断服务程序的入口地址(由中断隐指令完成)
  • 软件处理:保护现场(主要是保存某些通用寄存器的内容),执行中断服务程序,恢复现场,返回断点

中断处理流程

情景假设:用户在系统正在运行Task1时按下按键,此时中断的处理流程如下。

  • CPU跳到中断向量指向的固定地址执行代码,此跳转是硬件实现的
    • 关中断,防止在保存现场时被打断,导致不能在处理完新中断后正确的返回之前的地址
    • 保存断点
    • 保存现场:Task1被打断时,需要先保存Task1的运行环境,比如各类寄存器的值,保存现场参考系统时钟中断章节
    • 开中断,让更高优先级的中断可以打断此中断
    • 分辨中断类型,调用与此中断相关的中断处理服务函数(ISR,interrupt service routine),如果同时有多个中断,挑选最高优先级的中断执行。
    • 关中断,保护恢复现场过程
    • 恢复现场:继续运行Task1,
    • 开中断
    • 返回断点

【FreeRTOS】中断机制_第1张图片

中断处理的规则(注意事项)

  • 中断服务程序执行时间要尽可能的短,否则:
    • 执行时间过长导致其他低中断优先级无法被处理:实时性无法保证
    • ISR在内核中被调用,此时用户任务无法被执行:系统显得很卡顿
  • 非常耗时的中断如何处理?
    • ISR:尽快做清理、记录工作,然后通过事件或者队列等方式触发某个任务,这就需要ISR与任务之间进行通信
  • FreeRTOS把任务认为是硬件无关的,由程序员决定任务的优先级,由调度器决定任务何时运行;而ISR虽然也是软件实现,但它因为与硬件密切相关而被认为是硬件的一部分,它的执行由硬件触发决定
  • 最低优先级的中断可以打断高优先级的任务

内核为中断提供两套API

以写队列为例。

xQueueSendToBack():

  • 可阻塞
  • 可唤醒等待接收的tasK:
    • DelayList[] ==> ReadyList[]
    • 挑出最高优先级的任务运行

xQueueSendToBackFromISR():

  • 不可阻塞
  • 可以唤醒对方:
    • DelayList[] ==> ReadyList[]
    • 不挑出最高优先级的任务,只是记录是否有更高优先级的任务被唤醒

为何要为ISR专门设计一套API?

  • 普通的API,比如QueueSend在写满队列,可以进入阻塞状态,但ISR调用API时,因为ISR不是任务,所以不能进入阻塞状态
使用两套API的好处
  • 使用同一套函数的话,需要增加额外的判断代码、增加额外的分支,使得函数更长、更复杂、难以测试在任务、ISR中调用时,需要的参数不一样,比如:
    • 在任务中调用:需要指定超时时间,表示如果不成功就阻塞一会
    • 在ISR中调用:不需要指定超时时间,无论是否成功都要即刻返回
    • 如果强行把两套函数揉在一起,会导致参数臃肿、无效
  • 移植FreeRTOS时,还需要提供监测上下文的函数,比如is_in_isr()。有些处理器架构没有办法轻易分辨当前是处于任务中,还是处于ISR中,就需要额外添加更多、更复杂的代码

From_ISR API核心(pxHigherPriorityTaskWoken)

用pxHigherPriorityTaskWoken参数的返回值判断有无更高优先级的任务被唤醒。若为true则代表有高优先级任务被唤醒。可以在适当的位置进行任务切换。

为何xQueueSendToBackFromISR()中不切换最高优先级的任务?

原因:中断服务函数执行时间要尽可能的短

假设:GPIO中断被触发
中断服务函数GPIO_KEY_ISR{
   
    // 执行两次队列发送
    xQueueSendToBackFromISR();   // 假设B被唤醒,B任务的优先级大于当前任务,切换B
    xQueueSendToBackFromISR();   // 假设C又被唤醒,C任务的优先级又大于B任务,切换C
    // 这时就会发现,发生这种情况其实是不需要切换B任务的,应该直接切换最高优先级的C任务就好了。所以不如先不切换,当函数结束时判断`pxHigherPriorityTaskWoken == TRUE`的时候再切换
}

使用From_ISR API的好处

  • 效率高,避免不必要的任务切换
  • 简洁的ISR可避免问题更复杂
  • 可移植性高

用法示例

在用户自己定义的中断服务函数中,在即将退出的函数的时候判断

if (pxHigherPriorityTaskWoken == TRUE) {
   
    // 切换任务
    portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);    // 汇编实现
    // 或者调用下面用C实现的方法
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);     // C实现
}
中断服务函数写法(如何切换任务)

这样的处理方式很常见,比如UART中断:在UART的ISR中读取多个字符,发现收到回车符才进行任务切换。

void XXX_ISR() {
   
    int i;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    for (i = 0; i < N; i++) {
   
        xQueueSendToBackFromISR(..., &xHigherPriorityTaskWoken); /* 被多次调用 */
    }
    /* 最后再决定是否进行任务切换 */
    if (xHigherPriorityTaskWoken == pdTRUE) {
   
        /* 任务切换 */
        portYIELD_FROM_ISR<

你可能感兴趣的:(FreeRTOS,esp32,esp-idf,freertos,嵌入式软件)