用了好久的FreeRTOS以前只是知道,如果在中断服务程序中调用某一些FreeRTOS的API函数时需要注意,如果有ISR版本的一定要调用末尾带ISR的函数,并且中断服务程序要调用freeRTOS的API接口则中断优先级不能高于配置宏(configMAX_SYSCALL_INTERRUPT_PRIORITY)的值这又是为什么呢? 刚好今天受台风只能在家里窝着,所以就想着趁有时间看看这一部分的内容,研究一下为什么,那么废话不多说开干。

找了几个函数简化一些安全检查的内容再把一些宏函数替换后对比观察了下内容如下:

TickType_t xTaskGetTickCount( void )
{
TickType_t xTicks;
{
xTicks = xTickCount;
}
return xTicks;
}
/*-----------------------------------------------------------*/ TickType_t xTaskGetTickCountFromISR( void )
{
TickType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); uxSavedInterruptStatus = portTICK_TYPE_SET_INTERRUPT_MASK_FROM_ISR();
{
xReturn = xTickCount;
}
portTICK_TYPE_CLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); return xReturn;
}

其中的函数portASSERT_IF_INTERRUPT_PRIORITY_INVALID 是一个和具体平台和配置相关的的宏,如果配置了configAssert 宏则这个宏函数指向vPortValidateInterruptPriority这个函数。这个函数也是一个和具体平台相关的函数所以他实现在port.c中。解析如下

void vPortValidateInterruptPriority( void )
{
uint32_t ulCurrentInterrupt;
uint8_t ucCurrentPriority;
//参考内核指南,这个命令是获取当前的中断号
ulCurrentInterrupt = vPortGetIPSR();
/*portFIRST_USER_INTERRUPT_NUMBER 是一个和芯片相关的用户中断号
在M3、M4的芯片上就是15以后是外部中断的中断号所以这里配置成16
判断是不是在外部中断中调用的API函数,如果是执行if里的内容*/
if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
{
/*根据中断服务函数的中断号获取当前中断的优先级设置*/
ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
/*如果当前执行的中断优先级数字小于配置ucMaxSysCallPriority这个值实际上是
configMAX_SYSCALL_INTERRUPT_PRIORITY,也就是当前中断优先级高于配置最高优先级
断言将会失败,程序将停止在这里*/
configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
}
/*当前中断优先级分组组大于配置分组(也就是表示抢占优先级的位数少于配置)则断言失败,程序停止*/
configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
}

这个函数的执行过程大致是(Cortex-m3架构)

  1. 获取当前活跃的中断号
  2. 判断是否是内核外的的异常---中断,如果是外部的中断则获取当前中断的优先级
  3. 如果当前中断的优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY所表示的优先级的话则此处断言失败。

这也就解释了为什么不能在优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY宏的中断中调用系统API函数了。但是为什么freeRTOS实现的时候需要增加这一段代码呢,这才是本文想要探究的问题。。。。。。

  为了探究上面的问题这里选择了同一个API函数任务挂起后的恢复的普通版本和ISR的版本进行对比。

普通版本(不可以在中断中调用)

void vTaskResume ( TaskHandle_t xTaskToResume )
{
TCB_t* const pxTCB = ( TCB_t* ) xTaskToResume; /* It does not make sense to resume the calling task. */
configASSERT ( xTaskToResume ); /* The parameter cannot be NULL as it is impossible to resume the
currently executing task. */
if ( ( pxTCB != NULL ) && ( pxTCB != pxCurrentTCB ) )
{
/*
基于cortex-m3 内核实际上是调用的是vPortRaiseBASEPRI 它增加了临界区的嵌套深度的统计变量,
并设置basepri寄存器的值为configMAX_SYSCALL_INTERRUPT_PRIORITY 这实际上就是屏蔽了中断优先级低于这个值
的所有中断。
*/
taskENTER_CRITICAL();
{
if ( prvTaskIsTaskSuspended ( pxTCB ) != pdFALSE )
{
traceTASK_RESUME ( pxTCB ); /* The ready list can be accessed even if the scheduler is
suspended because this is inside a critical section. */
/* 将当前任务从之前的状态的list中删除 */
( void ) uxListRemove ( & ( pxTCB->xStateListItem ) );
/* 将当前任务加入就绪状态的list中,就绪状态的任务将会被调度器调度 */
prvAddTaskToReadyList ( pxTCB ); /*
A higher priority task may have just been resumed.
*/
if ( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/* This yield may not cause the task just resumed to run,
but will leave the lists in the correct state for the
next yield. */
/*
恢复的进程的优先级高于当前任务的优先级,则调用portYIELD()
这个接口的实现就是悬起pensv中断(行为等同systick的中断干的事情)
然后在没有任何中断的时候就会切换任务。
*/
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*
基于cortex-m3 内核实际上是调用的是vPortExitCritical 减小临界区的嵌套,如果临界区嵌套已经为0了则
设置basepri寄存器的值为0这实际上就是取消了对任何中断的屏蔽。
*/
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

中断版本

BaseType_t xTaskResumeFromISR ( TaskHandle_t xTaskToResume )
{
BaseType_t xYieldRequired = pdFALSE;
TCB_t* const pxTCB = ( TCB_t* ) xTaskToResume;
UBaseType_t uxSavedInterruptStatus; configASSERT ( xTaskToResume ); /*
上面分析过了,这里把注释翻译一下,这里是和普通版的第一处区别。
支持中断嵌套的RTOS端口具有最大系统调用(或最大API调用)中断优先级的概念。
即使RTOS内核处于关键区域,超过最大系统调用优先级的中断也会永久启用,但无法
对FreeRTOS API函数进行任何调用。如果在FreeRTOSConfig.h中定义了configASSERT(),
则如果从已分配了高于配置的最大系统调用优先级的优先级的中断调用FreeRTOS API函数,
则portASSERT_IF_INTERRUPT_PRIORITY_INVALID()将导致断言失败。只能从已被分配了
最大系统调用中断优先级或在逻辑上低于优先级的中断中调用以FromISR结尾的FreeRTOS函数。
*/
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
/*
实际调用的是ulPortRaiseBASEPRI 他和vPortRaiseBASEPRI 的区别就是,这个接口会返回
basepri寄存器之前配置值,以用来进行恢复中断屏蔽的设置并不会操作临界区嵌套深度
*/
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if ( prvTaskIsTaskSuspended ( pxTCB ) != pdFALSE )
{
traceTASK_RESUME_FROM_ISR ( pxTCB ); /* Check the ready lists can be accessed. */
if ( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Ready lists can be accessed so move the task from the
suspended list to the ready list directly. */
if ( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/*
这里的处理就是和普通版的函数的区别的第二个位置,
*/
xYieldRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*
同样是将当前唤醒的进程从对应状态的列表中移除,然后将其加入到就绪任务列表中
*/
( void ) uxListRemove ( & ( pxTCB->xStateListItem ) );
prvAddTaskToReadyList ( pxTCB );
}
else
{
/* The delayed or ready lists cannot be accessed so the task
is held in the pending ready list until the scheduler is unsuspended. */
/*
如果任务调度器挂起了,则不允许操作就绪列表,则将当前唤醒进程加入到xPendingReadyList
*/
vListInsertEnd ( & ( xPendingReadyList ), & ( pxTCB->xEventListItem ) );
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*
恢复之前的中断掩蔽配置,并不会操作临界区嵌套深度
*/
portCLEAR_INTERRUPT_MASK_FROM_ISR ( uxSavedInterruptStatus );
/*
返回师傅需要进行一次任务调度
*/
return xYieldRequired;
}

对比两个函数的实现方式不同的地方有

1、中断下的接口不操作临界区嵌套深度,仅设置中断屏蔽避免中断嵌套。

2、中断下的接口在遇到需要任务调度的情况下的处理不是引起一次任务调度,而是将是否需要进行任务调度的状态返回。实际使用如下:

/* Resume the suspended task. */
xYieldRequired = xTaskResumeFromISR( xHandle );
if( xYieldRequired == pdTRUE )
{
/* A context switch should now be performed so the ISR returns directly to
the resumed task. This is because the resumed task had a priority that was
equal to or higher than the task that is currently in the Running state.
NOTE: The syntax required to perform a context switch from an ISR varies
from port to port, and from compiler to compiler. Check the documentation and
examples for the port being used to find the syntax required by your
application. It is likely that this if() statement can be replaced by a
single call to portYIELD_FROM_ISR() [or portEND_SWITCHING_ISR()] using
xYieldRequired as the macro parameter:
portYIELD_FROM_ISR( xYieldRequired );*/
portYIELD_FROM_ISR();
}

其中 portYIELD_FROM_ISR 的实现和具体的平台硬件相关,在cortex-m3上的实现就是直接使用普通版本的portYIELD()。除此之外通过上述的注释我们也能明白freeRTOS不允许临界区无法屏蔽的中断调用系统API。

ISR结尾的函数不会触发任务调度,而是返回需不需要任务调度,这是为什么呢????这就需要去看看,任务调度函数的实现了,这里就语言论述了。

任务切换由可悬起中断服务函数(PendSV)处理,这是一个汇编函数,其中主要做的事情是将,任务当前的上下文以PSP进行压栈(保存现场),然后调用vTaskSwitchContext,找到就绪的其他任务(优先级相同或者高于当前任务)。然后从新的任务堆栈中出栈(偷梁换柱-操作系统的巧妙之处),在中断返回时处理器会按寄存器中保存的返回地址返回,然后就跑到新的任务上下文了。这个过程通常是由滴答定时器,按照固定周期触发的,具体的触发过程由滴答定时器中断服务函数置起中断标志位,然后在滴答定时器中断处理完成后PendSV中断就会执行,进而任务切换,用一个图来说明一下这个过程吧。但是我想不通一点,M3的内核是支持中断咬尾操作的,所以应该是下图的这样的一个过程。

但是当任务A在运行用到了寄存器R8,但是当滴答定时器中断来临时一部分寄存器被硬件自动压栈保存(不包括R8)①,然后开始执行滴答定时器中断服务函数,一堆处理完成后悬起PendSV中断,这时当滴答定时器准备退出时难道会按中断咬尾的方式直接进行PendSV中断服务函数。从源码看出来PendSV服务函数,会将剩下的寄存器R4-R11等继续入栈(用psp)这里栈指针没有问题,但是,R4-R11可能已经在滴答定时器中断服务程序中被使用过,所以他不是之前的任务堆栈的状态,这样压栈没有问题吗??哎想不通,难道这里不会按中断咬尾处理,先出栈再进PendSV中断,希望知道的人指导一下,不胜感激。。。。(2019-08-10)。后来我找来了吃灰已久的开发板移植了FreeRTOS进行仿真测试,测试发现实际的效果就是后面的处理方式(没有发生中断咬尾)具体原因是什么尚不得知,如果有知道的大佬麻烦留言指导下。(2019-08-17 14:34:43)这个问题先放下继续探究主题。这里的问题其实是自己理解的不透彻导致的,具体的中断服务程序的压栈工作是硬件做了固定的部分(仅压栈一定会使用的寄存器从而减少硬件保存现场的时间),然后之前疑惑的高组寄存器的现场保存的问题是由编译器决定的,因为编译器如何翻译C代码的中断服务函数是自己清楚的,所以能明确自己是否使用了高组寄存器从而决定是否要进行必要的压栈(编译器处理的软件行为),所以前面的问题就不攻自破了,至于说是否发生了中断咬尾操作也就没有任何区别了。

总结

  如果在中断中不调用ISR结尾系统API函数而使用普通版本的API接口会发生什么,为什么不能这么使用?通过查看多个类似的ISR结尾的函数和普通版本的区别发现就是普通API接口函数会增加临界区的嵌套且可能会直接调用portYIELD()以触发一次任务调度。 但是如果在中断中调用普通版本的API则可能出现问题,多方查询发现这两种解释是最可能的答案,一是为了保证系统的实时性避免高优先级中断被系统调用掩蔽从而响应延迟;其次是在普通任务中调用的portYIELD函数和中断中调用的portYIELD函数的实现不同因此需要区别对待,但是在cortex-m3上两者实现是相同的。以上两种答案好像都无法合理解释我心中的疑虑,最后找到资料有这样一句“freeRTOS支持中断嵌套,低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数,而优先级高于这个值的中断则可以像前后台系统下一样正常运行,但是这些中断函数不能调用系统API函数”然后豁然开朗,因为操作系统为了保证操作系统内核的运行稳定性,保证API 执行重要的过程都是原子操作,这样就不会存在系统运行紊乱,比如如果在中断一个可控的中断中进行插入链表的操作,但是又一个优先级优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断发生了并且调用了系统API这样他就有可能打破低优先级中断的链表操作导致内核数据的毁坏,此时系统的运行就会出现紊乱。因此需要将系统的API相关重要操作“原子化“,从而避免系统核心数据操作紊乱。FreeRTOS是支持中断嵌套的,但是低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断之间不会嵌套以保证系统API的操作的”原子性“。简单分析两种情况下的嵌套:

1、先发生了中断优先级为“中等”且低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断M然后发生中断优先级为“高等”且高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断H,这里的中断响应过程大致是发生中断M时的处理过程和前后台中断过程相同,由硬件自动入栈,然后中断执行在这过程中又来了中断H,此时使用MSP将M的执行现场保存(这里保存的有可能还有任务的现场数据---因为有可能M中断仅使用除硬件自动入栈的寄存器外的Rx所以M中断保存现场时仅保存了Rx,进程却使用了Ry但是中断M未使用所以此时的Ry在M中是不需要保存的,但是因为H中断会用到Ry所以H中断会压栈Ry到MSP的栈中此时的Ry是发生M前的任务现场数据)然后开始运行H中断,H完成执行后POP数据到M中断后接着运行M中断服务程序(此时也恢复了Ry)。

2、先发生了中断优先级为“中等”的中断M然后发生中断优先级为“高等”的中断H(M和H都高configMAX_SYSCALL_INTERRUPT_PRIORITY)此时的嵌套和前后台系统下的情况相同。

如果中断的优先级比configMAX_SYSCALL_INTERRUPT_PRIORITY高,则这些中断可以直接触发不会被RTOS延时,如果优先级比其低,则有可能被RTOS延时。

2020-11-28 17:50:03 修改

 

探究为什么FreeRTOS 有些API不能在中断服务函数中调用,转而需要调用带ISR的版本的更多相关文章

  1. 在中断服务函数中使用FreeRTOS系统延时函数vTaskDelay导致看门狗复位的情况

    @2019-04-09 [问题] 控制程序工作一段时间异常重启 [分析] 经定位分析重启原因为看门狗复位导致 [解决] 经排查发现在中断服务函数中使用了FreeRTOS的系统时延函数vTaskDela ...

  2. STM32是如何进入中断服务函数xxx_IRQHandler的

    今天在看stm32的中断,一时间不理解stm32主函数是如何进入中断函数的,按C编程的理解,会有个特定的入口之类的,但是看demo过程中没有发现入口. 以串口中断服务函数void USART1_IRQ ...

  3. request_irq() | 注册中断服务函数【转】

    本文转载自:http://blog.csdn.net/wealoong/article/details/7566546#t0 参考  : ARM Linux 中断机制分析.pdf linux-2.6. ...

  4. stm32中断服务函数

    你打开stm32的启动文件,例如startup_stm32f10x_hd.s 里面有很多中断跳转的入口.用白话说就是固件库帮你写好了发生什么中断时跳转到哪里,这些名字是一个函数名,你要把这些函数写出来 ...

  5. freeertos中关于PendSV中断服务函数的解析

    __asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskS ...

  6. request_irq() | 注册中断服务

    一.中断注册方法 在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义: int request_irq(unsigned int ...

  7. FreeRTOS 临界段和开关中断

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 临界段代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断.为确保临界段代码的执行不被中断 ...

  8. FreeRTOS系列第20篇---FreeRTOS信号量API函数

    FreeRTOS的信号量包括二进制信号量.计数信号量.相互排斥信号量(以后简称相互排斥量)和递归相互排斥信号量(以后简称递归相互排斥量).我们能够把相互排斥量和递归相互排斥量看成特殊的信号量. 信号量 ...

  9. FreeRTOS操作系统,在按键中断函数中恢复被挂起的任务,程序卡死的原因和解决办法

    出现问题场景:       作为刚接触FreeRTOS实时操作系统的菜鸟,我在练习一个程序功能:按键3按下,将LED闪烁的任务挂起:按键4按下,将LED闪烁的任务恢复到就绪.按键使用外部中断.恢复就绪 ...

随机推荐

  1. JVM重新认识(一)oop-klass模型--HSDB使用验证

    一:oop-kclass模型 思考:我们平时写的java类编译成.class文件,JVM加载.class文件,那么加载.class文件之后在JVM中就是oop-kclass(C++)模型形式存在的. ...

  2. Web自动化测试python环境中安装 --selenium安装、火狐和火狐驱动版本、谷歌和谷歌驱动版本、测试

    一.安装selenium Windows命令行(cmd)输入pip install selenium(无须指定版本默认最新)或 pip install selenium==3.141.0(可指定版本) ...

  3. leaving sockets open which can trigger a ResourceWarning in some # cases, and look like a memory leak in others

    # -*- coding: utf-8 -*- """ requests.api ~~~~~~~~~~~~ This module implements the Requ ...

  4. UVA11694 Gokigen Naname题解

    目录 写在前面 Solution Code 写在前面 UVA的题需要自己读入一个 \(T\) 组数据,别被样例给迷惑了 Solution 每个格子只有两种填法且 \(n \le 7\),暴力搜索两种填 ...

  5. 洛谷 p3391

    题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1, ...

  6. python -m http.server 搭建一个简易web下载服务器

    在打vulnhub靶场的时候遇到的一个问题 目录 一.进到需要发送的安装包目录 二.开启http服务 三.访问服务器 一.进到需要发送的安装包目录 比如设置一个专门发送,传输的文件的文件夹,cmd命令 ...

  7. 手机用itunes安装更新系统

    1.[Shift+更新]:仅对固件进行更新,保留现有资料和已经安装的程序.但是已经越狱的iPhone或iPad禁止使用此方法!否则有后遗症!没有越狱的iPhone或iPad则可以直接使用此方式.2.[ ...

  8. (10)Linux挂载详解

    1.在 Linux 看来,任何硬件设备也都是文件,它们各有自己的一套文件系统(文件目录结构). 因此产生的问题是,当在 Linux 系统中使用这些硬件设备时,只有将Linux本身的文件目录与硬件设备的 ...

  9. Java复习整理 day01

    练习代码: 1 //这条语句说明这个Java文件在demo的包下 2 package demo1; 3 /** 4 * 5 * @author 王兴平 6 * 这个是第一个hello world 案例 ...

  10. 小白搭建WNMP详细教程---PHP安装与设置

    php的安装请参考WAMP中PHP的安装教程https://www.cnblogs.com/missbye/p/12049925.html 需要注意的是,我们下载的PHP版本要下载Non Thread ...