FreeRTOS学习-中断管理

这篇具有很好参考价值的文章主要介绍了FreeRTOS学习-中断管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 简介

中断管理是一个操作系统中最核心的功能之一。在FreeRTOS中,中断总是可以打断task(尽管是最高优先级的task),而task永远不可能打断中断ISR(interrupt service routine)。本文先介绍了FreeRTOS中的中断管理,然后介绍了中断处理函数中如何使用FreeRTOS的API,最后介绍了中断处理函数与任务之间的同步方法——信号量。

2. 中断处理流程

2.1. 中断系统的初始化

在FreeRTOS中,中断向量表的定义在Port层完成,在GCC ARMv7中,其基本形式如下:

.extern FreeRTOS_IRQ_Handler
.extern FreeRTOS_SWI_Handler

.section .vectors
_vector_table:
_freerots_vector_table:
    B       _boot
    B       Undefined
    LDR     PC, _swi
    B       PrefetchAbortHandler
    B       DataAbortHandler
    NOP
    LDR     PC, _irq
    B       FIQHandler

_irq:   .word   FreeRTOS_IRQ_Handler
_swi:   .word   FreeRTOS_SWI_Handler

其中,.vectors段在链接脚本中与.text段关联,并且被放置在.text的首位。

portmacro.h中定义了extern void vPortInstallFreeRTOSVectorTable( void )函数用于装载该中断向量(由Port层实现)。在ARMv7中并不一定需要装载,因为可以默认从0x00000000访问。对于ARMv8,则需要在开启调度器的时候装载中断向量表的地址。

此外,中断控制器的初始化也需要在Port层完成。而中断所使用的栈则是在_boot中完成。

2.2. 中断处理

这里我们不讨论硬件级别的行为,只考虑OS需要完成的软件行为。并且,我们以ARMv7为例,中断仅限于IRQ异常的处理。

当中断发生时,CPU会跳转到异常向量表中IRQ的异常向量处,执行一个跳转指令跳转到FreeRTOS_IRQ_Handler

FreeRTOS_IRQ_Handler中,进行OS级别的中断处理:

  1. 调整返回地址:LR -= 4

  2. 将IRQ的LR和SPSR压入IRQ栈

  3. 切换到SVC模式:此时依然处于中断屏蔽状态

  4. 将寄存器上下文压栈:PUSH {R0-R4, R12}

  5. 增加中断嵌套层数:使用了R3存储ulPortInterruptNesting的地址,R1存储中断嵌套层数的原始值

  6. 读取GIC的CPU接口中的IAR,获取中断ID:表示CPU正在处理该中断

  7. 确保栈地址是4字节对齐:使用R2记录调整量

  8. 保存顶级中断响应函数的上下文:PUSH {R0-R4, LR},这里把R4也压栈是为了让栈满足32位地址对齐。

  9. 调用FreeRTOS_ApplicationIRQHandler:该函数主要完成下列操作

    {
        读取中断ID,调用GIC驱动中注册的对应的响应函数。
    }
    
  10. 恢复顶级中断响应函数的上下文:POP {R0-R4, LR}

  11. 恢复栈地址

  12. 屏蔽中断:CPSID i

  13. 通过EOIR通知GIC中断处理完成

  14. 恢复中断嵌套层数

  15. 如果中断嵌套层数不为0,那么直接跳转到不进行任务切换的中断退出,否则继续向下执行

  16. ulPortYieldRequired == pdTRUE,表明退出中断前需要进行一次任务切换:R1记录ulPortYieldRequired的地址。

不进行任务切换的中断退出:

  1. 恢复被中断的现场:POP {R0-R4, R12}
  2. 切换到IRQ模式
  3. 恢复LR和SPSR
  4. 从中断返回:MOVS PC, LR

进行任务切换的中断退出:任务切换部分可参看任务切换小节

  1. 清除ulPortYieldRequired标志
  2. 恢复被中断的现场:POP {R0-R4, R12}
  3. 切换到IRQ
  4. 恢复LR和SPSR
  5. 保存切出任务的上下文(portSAVE_CONTEXT
  6. 执行任务切换(vTaskSwitchContext()
  7. 恢复切入任务的上下文(portRESTORE_CONTEXT

2.3. 对浮点的支持

在ARM CA9中,支持NEON和VFP硬件浮点。如果开启了NEON和VFP硬件支持,GCC通常会使用向量化编译优化memcpy()memset()等类似函数的实现(而FreeRTOS的队列的实现基于memcpy())。这便意味着对其调用则可能破坏对应的浮点寄存器,对于中断处理函数而言,这通常是不符合预期的。因为默认情况下,中断处理过程中不会保护浮点上下文,因此如果ISR中使用了这类函数,那么它可能会影响在任务中执行的浮点运算的结果。

为了解决该问题,FreeRTOS提供了FPU-Safe版本的中断处理实现,即在中断处理中增加浮点上下文的保护。这种实现的缺点很明显,就是会降低中断处理的性能(因为浮点寄存器的保护和增加内存访问的时间)。

此外,还有另外一种解决方式,则是在临界区内使用硬件浮点运算。那就不存在中断打断的问题了。

2.4. 中断嵌套

对于支持中断嵌套的Port,需要在FreeRTOSConfig.h中定义如下宏定义;

描述
configMAX_SYSCALL_INTERRUPT_PRIORITYconfigMAX_API_CALL_INTERRUPT_PRIORITY 定义了可调用FreeRTOS的API的最高中断优先级,其中configMAX_SYSCALL_INTERRUPT_PRIORITY用于旧版本的FreeRTOS Port中
configKERNEL_INTERRUPT_PRIORITY Tick中断的优先级,必须设置为最低的中断优先级,这个宏在不同Port中名字不一样

只有优先级大于configMAX_SYSCALL_INTERRUPT_PRIORITY(数值上可能是低于它)的中断,才允许中断当前的中断处理。并且,要求这些中断响应函数不能调用FreeRTOS的API。

3. 在ISR中使用FreeRTOS API

3.1. 中断安全API

在FreeRTOS中,某些系统调用提供了任务上下文和中断上下文的调用版本。对于带有FromISR后缀的API则是中断上下文的版本。

引入这种两种上下文分离的API的优势:使得任务上下文和中断上下文的调用都更加高效,因为

  • 不需要判断调用者所处上下文。
  • API的参数可能不是共享的,即有的参数对于任务上下文来说是无用的,有的参数则对于中断上下文而言是无用的。
  • 每个Port都需要实现中断上下文的判断。

使用分离的API的劣势通常是在使用三方代码时,有的时候必须同时在任务上下文和中断上下文中调用FreeRTOS的API,解决方案通常是:

  • 可以将中断处理推迟到任务中处理。
  • 实现调用上下文的判断,根据所处上下文来调用合适的API。

3.2. xHigherPriorityTaskWoken参数

在中断处理中,如果调用了某些API使得比被中断的任务更高优先级的任务被唤醒了,那么在退出中断之前,可以触发一次调度。而是否具有更高优先级的任务被唤醒的标志就是xHigherPriorityTaskWoken。它通常用于在FromISR后缀的函数中作为输出参数。如果xHigherPriorityTaskWoken == pdTRUE,表示有比被中断的task更高优先级的task被唤醒,需要进行上下文切换。当然,程序员也可以选择不进行切换。

为什么不能直接在API里面触发调度呢,有以下一些原因:

  • 避免不必要的任务切换:例如Uart Shell,不必要为每一个字符都进行一次调度;
  • 控制程序的执行:中断总是随机的,但有时会有任务不能被中断切换的需求(此时需要挂起调度器);
  • 更高的可移植性:可以在所有平台中使用;

当然如果不想使用这个参数,被唤醒的任务也会在下一次调度时进行被选中执行。

3.3. portYIELD_FROM_ISR()和portEND_SWITCHING_ISR()宏

两者完全相同,用于发出一个上下文切换请求。是taskYIELD()的中断安全版本。

3.4. portASSERT_IF_INTERRUPT_PRIORITY_INVALID()宏

对于某些支持中断嵌套的FreeRTOS port,FreeRTOS引入了Maximum System Call (或Maximum API Call)中断优先级的概念。在进入临界区时,所有不大于该优先级的中断都会被屏蔽;反之,所有高于该优先级的中断不会被屏蔽,但在他们的中断处理函数中,不能调用FreeRTOS的API。

portASSERT_IF_INTERRUPT_PRIORITY_INVALID()宏就是为了检测当前中断优先级是否有效。

4. 延迟的中断处理

这部分的设计思路与Linux中的中断处理的上、下半部类似。这是为了尽可能减少ISR的处理逻辑,将耗时的任务放在任务上下文进行处理。

基本思路是通过FreeRTOS的同步机制,在任务上下文等待某些指定事件,这些事件由中断产生;当事件发生时,激活任务执行对应的处理。

在FreeRTOS中,同步机制包括队列、事件组、信号量和IPC等。这里只介绍信号量。

4.1. 集中式延迟中断处理

在软件定时器管理文章中的PendFunction章节提到了延迟函数调用,因此,可以使用这个功能来实现集中式的延迟中断处理。

需要注意的是,在中断上下文中,应该使用ISR安全的APIxTimerPendFunctionCallFromISR()

使用RTOS Timer Task的优势:

  • 更低的资源使用
  • 简单

劣势:

  • 灵活性有限:优先级是固定的
  • 更低的确定性:因为会先处理已在队列中的请求。

5. 信号量

信号量是用于任务和任务之间,任务与中断处理之间的同步机制。它不能传递消息,只实现同步功能。如果需要传递消息,可通过队列实现。

但实际上,在FreeRTOS中,信号量是基于队列实现的,所以总的来说,他们的性能相差不大。

信号量可分为二值信号量和计数信号量,实质上,二值信号量是一种特殊的计数信号量。他们的区别在于创建的流程不同,其他接口完全一致。

下面则分别来看看它们的具体设计和实现。

由于FreeRTOSv9中,队列和信号量的实现大部分耦合在同一个函数中了,而FreeRTOSv10将这部分功能解耦,更易于理解和维护,因此这里基于FreeRTOSv10的实现进行介绍。

5.1. 二值信号量

5.1.1. 创建二值信号量

函数原型:configSUPPORT_DYNAMIC_ALLOCATION == 1

#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, senSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

创建一个二值信号量,并返回该信号量的句柄。此时返回的信号量数值为0。详细的实现过程可以参看队列管理文章中的创建队列小节。

FreeRTOS还提供了静态创建的版本,这里不再赘述。

5.1.2. 获取信号量

函数原型:

#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

xSemphore获取一个信号量,当且仅当该信号量可用时。可设置等待的时间xBlockTime。如果在指定时间内获取成功,则返回pdTRUE;否则返回pdFALSE

这里只关注信号量的部分。其中与互斥锁相关的实现将在后续介绍共享资源管理的文章中给出。获取信号量的具体实现如下:

FreeRTOS学习-中断管理

5.1.3. 归还信号量

函数原型:

#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

归还信号量xSemaphore。如果成功归还,则返回pdTRUE;如果该信号量之前从未被获取,则返回pdFALSE

具体的实现可参看队列管理文章中的入队操作。

需要注意的是,该接口只用于任务上下文。对于中断上下文,需要使用以下接口:

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

其实现与xQueueGenericSendFromISR()类似,只是其消息长度为0,下面看见其具体的实现:

{
    初始化`pxQueue = ( Queue_t * ) xQueue`。

    参数校验:Assert `pxQueue != NULL`,`pxQueue->uxItemSize == 0`,`!( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->pxMutexHolder != NULL ) )`。

    校验中断优先级(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    进入临界区,并保存当前的中断优先级屏蔽状态(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。
    获取队列当前的消息数量(`uxMessagesWaiting = pxQueue->uxMessagesWaiting`)。

    如果队列的消息未满(`uxMessagesWaiting < pxQueue->uxLength`):
    {
        获取当前的队列计数写锁(`cTxLock = pxQueue->cTxLock`)。

        由于这里不需要考虑互斥锁的情况,所以不需要考虑优先级继承的情况,只是单存的增加队列的消息计数(`pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1`)。

        如果队列处于计数写锁为加锁状态(`cTxLock == queueUNLOCKED`):
        {
            (仅开启USE_QUEUE_SETS):
            {
                如果队列处于某个队列组中(`pxQueue->pxQueueSetContainer != NULL`):
                {
                    将消息通知到队列组,如果高优先级的任务被唤醒(`prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE`),设置更高优先级任务唤醒标志(`*pxHigherPriorityTaskWoken = pdTRUE`)。
                }
                否则,即队列不在任何队列组中:
                {
                    如果有任务等待该队列的消息(`listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE`):
                    {
                        将任务从事件任务队列中唤醒,且唤醒了更高优先级的任务(`xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) )`),设置更高优先级任务唤醒标志(`*pxHigherPriorityTaskWoken = pdTRUE`)。
                    }
                }
            }
        }
        否则,即队列处于加锁状态:
        {
            将队列写计数锁增1(`pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 )`)。
        }

        设置返回值`xReturn = pdPASS`。
    }
    否则,即队列消息已满:
    {
        设置返回值`xReturn = errQUEUE_NULL`。
    }

    退出临界区,并恢复中断优先级屏蔽状态(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回`xReturn`。
}
5.1.4. 获取信号量数值

函数原型:

#define uxSemaphoreGetCount( xSemaphore ) uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )

返回信号量当前的可用数量。

详细实现可参看队列管理文章中的查询队列状态小节。

5.1.5. 删除信号量

函数原型:

#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) )

具体实现可参看队列管理文章中的删除队列小节。

5.2. 计数信号量

计数信号量的主要作用:

  • 计算事件的发生次数,每发生一次事件则计数增1,而获取事件时减1。使用时,通常初始化为0
  • 资源管理:计数值表示资源的数量,当资源被分配时计数减1,归还时增1。使用时,通常初始化为资源的数量
5.2.1. 创建计数信号量

函数原型:需要开启configUSE_COUNTING_SEMAPHORES = 1

#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

该函数实际上调用的是xQueuCreateCountingSemaphore()接口,其中uxMaxCount表示计数信号量的最大值,uxInitialCount是初始值。如果创建成功,则返回信号量的句柄。

下面来看看其具体实现:

{
    参数校验:Assert `uxMaxCount != 0`, `uxInitialCount <= uxMaxCount`。

    创建队列(`xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queuQUEUE_TYPE_COUNTING_SEMAPHORE )`)。

    如果创建成功(`xHandle != NULL`):
    {
        初始化队列消息数量(`( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount`)。
    }

    返回`xHandle`。
}

可以看到,最终还是通过xQueueGenericCreate()创建了一个队列。

5.3. 信号量的实现细节

5.3.1. 信号量的接口
5.3.1.1. 信号量的句柄

句柄定义:

typedef QueueHandle_t SemaphoreHandle_t;

可以看到,信号量的句柄实际上就是队列的句柄。

5.3.2. 信号量的内涵
5.3.2.1. 信号量的私有宏

定义了信号量的消息长度和队列长度:

#define semBINARY_SEMAPHORE_QUEUE_LENGTH    ( ( uint8_t ) 1U )
#define semSEMAPHORE_QUEUE_ITEM_LENGTH      ( ( uint8_t ) 0U )
#define semGIVE_BLOCK_TIME                  ( ( TickType_t ) 0U )

6. 在ISR中使用队列

在队列管理文章中提到,中断上下文中也可以使用队列功能,但需要注意的是,需要调用ISR安全的接口,例如xQueueSendToFrontFromISR()xQueueSendToBackFromISR()

通常不建议在ISR中传输频繁抵达的数据,取而代之的是可以采用如下方法:文章来源地址https://www.toymoban.com/news/detail-494848.html

  • 使用DMA
  • 使用线程安全的buffer作为中转
  • 在ISR中进行数据处理,然后将处理后的数据通过队列传给task

到了这里,关于FreeRTOS学习-中断管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 初识FreeRTOS入门,对FreeRTOS简介、任务调度、内存管理、通信机制以及IO操作,控制两个led不同频率闪烁

    当代嵌入式系统的开发越来越复杂,实时性要求也越来越高。为了满足这些需求,开发者需要使用实时操作系统(RTOS),其中一个流行的选择是FreeRTOS(Free Real-Time Operating System)。本篇博客将详细介绍FreeRTOS的特性、任务调度、内存管理、通信机制以及一些示例代码。 FreeR

    2024年02月14日
    浏览(26)
  • 【学习FreeRTOS】第12章——FreeRTOS时间管理

    FreeRTOS的系统时钟节拍计数器是全局变量xTickCount,一般来源于系统的SysTick。在STM32F1中,SysTick的时钟源是72MHz/8=9MHz,如下代码,RELOAD = 9MHz/1000-1 = 8999,所以时钟节拍是1ms。 判断OS是否运行,运行才可以 启动正常调度 屏蔽所有中断 xTaskIncrementTick()处理系统节拍 ,并决定是否进

    2024年02月12日
    浏览(27)
  • FreeRTOS 中断

    FreeRTOS是一个C库集合,包含了实时内核和模块化库实现互补功能。 免费的实时操作系统,可裁减移植。 demo、license和source。source为主体。 source有三部分 源码.c 、include文件夹里头文件.h和portable文件夹。其中portable文件将FreeRTOS系统软件与MCU芯片建立连接。 中断由硬件产生,当

    2024年02月20日
    浏览(27)
  • 【RTOS学习】FreeRTOS中的链表 | 堆的管理

    🐱作者:一只大喵咪1201 🐱专栏:《RTOS学习》 🔥格言: 你只管努力,剩下的交给时间! 链表是FreeRTOS的核心结构,它让系统的功能正常运行,本喵下面来解释一下FreeRTOS中的链表结构以及操作。 如上图所示是FreeRTOS源码中的链表的定义 List_t ,这是一个链表头,重要的成员

    2024年02月04日
    浏览(31)
  • 中断与freeRTOS任务进行同步

    S32K144在做CAN通信时,通过FlexCAN中断接收CAN数据,并希望让freeRTOS 的CAN处理任务拿到CAN数据并进行数据处理。因此就需要找到能够满足中断与freeRTOS任务进行同步的方式方法。 遇到这个问题,第一时间想到的就是查找freeRTOS手册《FreeRTOS_Reference_Manual_V10.0.0.pdf》 通过查看手册,

    2024年02月05日
    浏览(30)
  • FreeRTOS 中断配置和临界段

    1. 中断简介 中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。 Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个

    2023年04月10日
    浏览(24)
  • 【FreeRTOS】【STM32】中断详细介绍

    本篇文章将对下面三种优先级进行概念辨析: Cortex-M3 内核的中断优先级 STM32F1XX 控制器的中断优先级 FreeRTOS 的任务的优先级 “CPU”:“Central Processing Unit”,即中央处理器。它是计算机系统中的主要组件,负责执行指令并进行数据处理和计算。CPU通常由控制单元、算术逻辑

    2024年02月11日
    浏览(30)
  • FreeRTOS通过消息队列实现串口命令解析(串口中断)

    作者:Jack_G 时间:2023.08.08 版本:V1.0 上次修改时间: 环境: quad quad quad quad STM32Cube MX V6.8.1 quad quad quad quad STM32CubeH7 Firmware Package V1.11.0 / 04-Nov-2022 quad quad quad quad Keil: V5.29 正常配置,不过 需要勾选全局中断 ,后续在接收中断中将接收到的数据送入消息队列。 在usa

    2024年02月14日
    浏览(23)
  • FreeRtos(Arm M7)中断压栈分析

      目录 1.前言 2.源码分析 2.1 xPortPendSVHandler源码 2.2 pxPortInitialiseStack源码 3. 问题总结          以Arm M7核为例,当CPU响应中断异常时,第一件事就是保存现场,进行压栈。如果当前使用的是任务堆栈,则压入PSP;如果使用的是系统主堆栈,则压入MSP。在压栈的过程中,xP

    2024年02月19日
    浏览(26)
  • STM32用FreeRTOS串口中断接收卡死问题

    现在意法半导体的配套软件做得很全面了,简直可以说是保姆式的服务。从芯片选型,引脚定义,到代码模板生成,一条龙服务,很方便。但是方便也有方便的坏处,那就是有些细节的规则会造成天然的bug。 比如,在stm32cubeIDE里新建工程的时候,如果勾选了FREERTOS 那么,程序

    2024年02月15日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包