本文总结了FreeRTOS使用过程中的一些重要知识点和易混淆点,总结的比较全,相信能帮到你
FreeRTOS是硬实时还是软实时
RTOS叫做实时操作系统,他也分硬实时和软实时,一般带任务抢占都是硬实时,能在规定的时间内完成某个动作,比如freertos,可能有些RTOS属于软实时,目前暂不知道,
软实时对时间没有严格要求,像TOOS,典型的就是Linux和windows,任务一多就会卡一点,能用但是用户体验差
static和动态创建任务,有什么不一样?
static方式用到的内存是一个全局静态的数组传入到任务创建的API里
动态的则是API内部通过malloc的方式分配,如果删除任务当时会记下标志,并在空闲任务里释放资源
注意:创建的内存空间主要有任务堆栈和任务控制块TCB_t(固定大小的结构体变量)
中断中执行的API(带ISR的)和普通的API有啥区别?
部分是有区别的,像队列的写入和读取,普通的API是有阻塞时间的,但是中断中是没有这个参数的,因为中断里面不能阻塞
而本身就没有阻塞的像任务切换上下文API taskYIELD()和portYIELD_FROM_ISR()就是一样的
空闲任务的作用
优先级为0,在开启任务调度器时自动创建
- 释放被删除任务的任务堆栈和任务控制块内存
- 运行用户设置的空闲任务钩子函数
- 判断是否开启低功耗tickless模式,如果开启,可以作相应的处理
SysTick中断和PenSV中断的作用
SysTick中断由configTICK_RATE_HZ配置,比如1000就是1ms中断一次,建议值大于等于1ms,可以设置小于1ms,但是效率会降低,因为任务切换也需要时时间
PendSV中断主要的作用就是保护现场把任务切换的临时变量存储在任务堆栈并获取下一个要运行的任务,并将pxCurrentTCB更新为这个要运行的任务。产生中断的来源就是Systick中断和调用任务切换的APItaskYIELD()
RTOS的中断管理
RTOS系统会接管MCU的一些中断,会通过configMAX_SYSCALL_INTERRUPT_PRIORITY(底层是BASEPRI寄存器)配置大于这个数值的优先级由RTOS接管,调用RTOS关闭中断的API是可以关闭的,反之则关闭不了
并且RTOS用到的SysTick和PendSV中断是优先级最低的中断
列表、列表项和任务控制块TCB_T
任务控制块里面关于列表相关的成员有以下几个:
StackType_t *pxTopOfStack; // 任务堆栈栈顶
StackType_t *pxEndOfStack; // 任务堆栈栈底
StackType_t *pxStack; // 任务堆栈起始地址
ListItem_t xStateListItem; // 状态列表项
ListItem_t xEventListItem; // 事件列表项
列表List_t里面重要的成员:
uxNumberOfItems // 记录列表中列表项的数量
pxIndex // 当前列表项索引号,用于遍历列表
xListEnd // 迷你列表项,相当于链表的最后一个节点
列表项ListItem_t的重要成员:
xItemValue // 列表项的值
pxNext // 指向下一个列表项
pxPrevious // 指向前一个列表项
pvOwner // 记录此链表归谁所有,指向任务控制块
pvContainer // 记录列表项归哪个列表
RTOS中会有很多列表,比如就绪态列表、阻塞态列表、挂起态列表,并且还细分不同优先级,列表相当于链表的头,列表项相当于链表的节点,并且是个双向链表,这样列表就可以遍历链表找到需要运行的列表项,根据列表项的pvOwner成员就能找到任务控制块,再根据任务控制块的*pxStack找到任务堆栈地址,这样就能切换任务了
高优先级的任务需要执行会马上中断低优先级任务吗?什么时候任务会马上切换
以下这些情况会马上切换任务,不会等到时间片结束
- 本调用任务阻塞的API vTaskDelay,函数内部会执行强制上下文切换 (阻塞任务API之后的函数不会执行)
- 本任务手动挂起vTaskSuspend,函数内部会执行强制上下文切换 (挂起任务API之后的函数不会执行)
- 删除本任务vTaskDelete(),函数内部会执行强制上下文切换 (删除任务API之后的函数不会执行)
- 显式调用切换上下文API taskYIELD()
以下这些情况会等时间片结束后在进行切换另一个任务
- 相同优先级任务并且没有调用阻塞API会轮训时间片
- 有更高优先级的任务需要运行,也需要在systick的中断中进行切换
二值信号量和互斥信号量的区别
FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量
二值信号量、计数型信号量、任务通知、队列:一般用于任务间或者中断和任务间的同步
互斥信号量:一般用于互斥访问
二值信号量:和只有一个队列项的队列是一样的,只有满和空两种状态
计数信号量:和队列效果是一样的,只不过不关心队列里的数值,可以代表某个信号被触发了多次,相应的动作就会执行多次,不像二值信号量会丢掉触发多次的信息
互斥信号量:和二值信号量的区别就是用优先级继承的方式解决了高优先级的任务被低优先级任务无限期阻塞(优先级翻转)的问题。
优先级翻转:是当低优先级获得二值信号量的资源时,高优先级的任务需要执行,这时高优先级的任务虽然得到的执行,但是需要用到互斥的资源,就会被阻塞直到低优先级的任务释放二值信号量的资源时才会执行,出现了优先级翻转的情况,这里的重点是低优先级的任务在执行的过程中可能还会被其他高优先级的任务打断,导致低优先级任务想要释放二值信号量的时间很长
优先级继承:为了缓解优先级翻转的情况,注意是缓解也不是完全解决,这时使用互斥信号量,里面带有优先级继承,就是当高优先级的任务执行时把拥有互斥信号量资源的低优先级任务优先级提高成和高优先级一样,这样低优先级任务由于优先级提高了就会优先得到执行,释放互斥信号量的时间就会加快,想要执行的高优先级任务任务也会得到更快的响应
互斥信号量也要注意以下两点:
● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
任务堆栈里面都存储什么?堆栈到底是栈还是堆
ARM 有两个栈指针PSP(进程堆栈指针)和MSP(主堆栈指针)
在裸机状态下,程序只调用MSP,会在IDE中配置heap和stack的大小,其中heap就是存储动态分配内存的,stack存储局部变量,全局变量存储在静态区内存
在使用FreeRtos状态下则是用到了PSP,rtos不会使用MSP,所以IDE里面的heap和stack可以配置很小,这里主要就是存放中断函数和中断嵌套。一退出中断就会切换成PSP
那rtos的任务堆栈和任务控制块用到哪块内存呢?
答:如果是动态分配内存会从heap上分配,如果是静态分配内存则一般是数组也就是静态区,这里不要固化heap不能存储局部变量,全局变量不能存储局部变量等信息,因为内存都是一样的只不过是人为分配的不一样
为啥把任务堆栈一般叫任务栈?
这里容易让人误解任务栈是存储在内存栈上的,其实如果动态分配是创建在堆上的,因为任务也相当于一个大的函数,所以函数内部的东西大家一般叫任务栈
那任务堆栈都存储哪些内容呢?
- 函数的嵌套调用:
- 函数局部变量。
- 函数形参,一般情况下函数的形参是直接使用的 CPU 寄存器,不需要使用栈空间,但是这个函数中如果还嵌套了一个函数的话,这个存储了函数形参的 CPU 寄存器内容是要入栈的。
- 函数返回地址,arm中一般函数的返回地址是专门保存到 LR(LinkRegister)寄存器中的,如果这个函数里面还调用了一个函数的话,这个存储了函数返回地址的 LR 寄存器内容是要入栈的。
- 函数内部的状态保存操作也需要额外的栈空间。
- 任务切换,任务切换时所有的寄存器都需要入栈。
- ARM 在任务执行过程中,如果发生中断:
- M3 内核的 MCU 有 8 个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈。
- M4 内核的 MCU 有 8 个通用寄存器和 18 个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。
如何查看任务堆栈的使用情况?
当某个任务基本编写完成,需要优化任务的栈,从而做到不浪费,可以使用 UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask ) 函数来监测任务栈使用情况,这个函数的返回值表示任务剩余可用栈空间(单位是word)
堆栈的增长方向?
堆:生长方向是向上的,也就是向着内存地址增加的方向。通常我们在画内存四区图时,堆的开口是向上的。
栈:它的生长方式是向下的,是向着内存地址减小的方向增长。栈的开口是向下的,上面的底部是栈底,下面的开口是栈顶。
在内存中,“堆”和“栈”共用全部的自由空间,只不过各自的起始地址和增长方向不同,它们之间并没有一个固定的界限,如果在运行时,“堆”和 “栈”增长到发生了相互覆盖时,称为“栈堆冲突”,系统崩溃。
rtos的调度原理
rtos会按不同状态和优先级创建不同的列表,比如就绪态列表、阻塞态列表、挂起态列表,并且还细分不同优先级,并且都是双向的,列表相当于双向列表的头,列表项相当于双向链表的节点
创建任务时会创建任务堆栈和任务控制块TCB_t,任务控制块里面就有列表项成员和对应的任务堆栈,这样就能查找下一个运行的任务
当调度器打开时,调度器会在systick中断或者调用任务切换的API(任务创建、任务删除、任务挂起、显式调用)里面查找就绪列表最高优先级的任务,相同优先级看阻塞时间,长的优先,相同优先级会轮流时间片执行,所以说任务抢占也要等到时间片结束,不是马上切换高优先级任务
当有任务删除时,会把任务控制块中的删除标志置一,在空闲任务中把任务堆栈和任务控制块回收
调度器怎么被挂起?
/* 关闭中断,相当有把RTOS能控制的中断全部关闭(包括Systick和PendSV中断),KW36实测定时器中断也会被中断 */
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
__asm volatile( "dsb" );
__asm volatile( "isb" );
}
/*-----------------------------------------------------------*/
/* 使能中断 */
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}