FreeRTOS知识点

一.FreeRTOS系统概念

        FreeRTOS是RTOS(实时操作系统)的其中一种,类似的还有uc/OS,RTX等,区别于linux & window系统;

二.FreeRTOS的基本概念

        1.任务:被称为task,可看做linux系统中的线程,是一个简单的程序。每个任务被赋予一定的优先级,有它自己的一套CPU寄存器的值和自己的栈空间。典型的,每个任务都是一个无限循环,每个任务都处于以下五个状态下:运行态,就绪态,阻塞态(定时解除),挂起态(需被手动解除),被中断态。

        2.任务切换:将当前正在运行任务的当前状态(CPU相应寄存器值的全部内容)保存在任务自己的栈区,然后把下一个要运行的任务的当前状态从该任务的栈中重新装入CPU寄存器,并开始下一个任务的运行,既上下文切换。

        3.堆栈作用:可看做是一个临时的数据寄存、交换的内存区;方便用于保护和恢复调用现场;其中栈由上向下增长,既先定义的放在高地址,后定义的放在低地址;堆由下向上增长;

        4.内核:负者管理各个任务,为每个任务分配CPU的时间,并负者任务之间的通信;调度策略采用支持抢占式和时间片调度,既不同优先级的任务采用抢占式,同一优先级的任务采用时间片调度(允许一个任务运行一个时钟节拍后,滴答定时器中断周期,让出CPU的使用权,让拥有同优先级的下一个任务运行)。

         5. 优先级翻转:共享资源的分配导致优先级低的任务先运行,优先级高的后运行;解决办法采取优先级继承的方法来临时改变任务优先级。高优先级等待低优先级释放信号量,在等待的期间,将低优先级的任务优先级提高到与其相同的优先级,使其不会被中等的优先级抢占(适用于抢占式内核任务调度。)

          6.空闲任务:任务调度器开启后启动的第一个任务,优先级最低,使处理器始终都有任务可以运行,主要用来清除那些删除自己本身任务的task控制块和堆栈的内存空间,可以自定义空闲任务的钩子函数(类似于回调函数),比如使系统进入tickless模式,开启节电模式。

   PS:任何时刻都要保证系统中至少有一个任务在运行,因此绝不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的API函数,比如vTaskDelay()函数,或者其他带有阻塞时间的信号量或队列等操作函数。

        7.任务控制块TCB_t中栈顶指针和当前堆栈栈顶指针区别:指针pxStack指向堆栈的起始位置,任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量。很多刚接触FreeRTOS的人会分不清指针pxTopOfStack和pxStack的区别,这里简单说一下:pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出。

注:指针pxTopOfStack必须位于任务控制块结构体的第一项,指向当前堆栈的栈顶,对于向下增长的堆栈,pxTopOfStack总是指向最后一个入栈的项目         

三.FreeRTOS的中断配置和临界区

        1.Cortex-M内核提供了一个用于中断管理的嵌套向量中断控制器(NVIC),实际上是多个用于管理中断和异常的可编程寄存器所定义的结构体。

        2.使用中断优先级配置寄存器来决定抢占式优先级和响应式优先级的数量;

                    抢占式优先级中断,顾名思义就是一个中断正在运行,抢占式优先级高的会打断抢占式优先级低的中断运行,既中断嵌套。

                    响应式优先级中断,在抢占式相同的情况下,中断同时发生,响应优先级高的;或者一个中断正在ing中,等待该中断处理完才进行下一个;

                    注:抢占式优先级相同的中断之间,没有中断嵌套的概念。

                    #define NVIC_PRIORITYGROUP   4    那STM32的4位优先级就全是抢占优先级

        中断优先级是数值越小,优先级越高;其中PendSV中断和SysTick中断的优先级最低;宏configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIOTITY用来设置FreeRTOS可管理的最大优先级。       

3.中断优先级寄存器用于配置优先级数

        4.临界区指必须完整运行,不能被打断的代码段;任务级临界代码保护,进入API函数:taskENTER_CRITICAL( ),退出API函数taskEXIT_CRIEICAL( );   中断级的临界代码保护API函数。。。。。

        1.进入和退出API函数必须同时成对出现;所谓的进出临界区是通过开关中断实现,将宏configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIOTITY写入到BASEPRI寄存器中,来开关系统可管理的中断;

                  2.挂起调度器vTaskSuspendAll( )进入临界区,只能保护代码不被其他任务打断,但中断可以;因为任务切换(上下文切换)只能在PendSV中断中进行,因此调用taskENTER_CRIEICAL的代码不会被任务和中断打断。

                  3.中断响应时间:关中断的最长时间 + 保护CPU内部寄存器的时间 + 进入中断服务函数的执行时间 + 开始执行中断服务程序(ISR)的第一条指令时间(从中断信号发生,CPU完成当前现场保护,而进入此中断信号对应处理程序入口出的时刻,所经历的时间)  

    注:任务优先级和中断优先级是两个不同的概念;任务优先级数值越大,优先级越高;中断优先级是数值越小,优先级越高;最低的中断(系统滴答定时器中断SysTick)可以打断任何任务。

四.FreerRTOS任务切换

        FreeRTOS是通过PendSV异常来处理上下文切换;切换发生的场合:1.执行了一个系统调用(调用了任务切换函数,taskYIELD( ),或者调用了该函数的函数);2.系统滴答定时器(SysTick)中断,中断服务函数中,增加时钟计数器xTickCount的值成功后,向ICSR的bit28写入1挂起PendSV来启动PendSV中断,并在PendSV中断服务函数中进行任务切换。也就是说,每次SysTick滴答定时器都会触发任务切换,有优先级高的就切换或者切换到同一优先级的其它任务,使其运行一个时间片(滴答定时器的定时周期)。

五.时间管理

    task中调用延时函数,任务进入阻塞态,并进行任务切换,直到延时完成,任务重新进入进入就绪态。

            1.函数vTaskDelay( const TickType_t  xTicksToDelay)  //参数为要延时的时间节拍数,既滴答定时的心跳次数;延时为相对模式,从该延迟函数调用出开始计数;

            2.函数vTaskDelayUntil( const TickType_t  xTicksToDelay ),延时为绝对时间,从任务开始唤醒时的xCountTick计数,此函数能使任务按一定的频率运行。

六.任务与任务、任务与中断之间的通信

            1.队列:主要用于实现信息的传递,主要是值传递。

            2.信号量:二值信号量、计数型信号量、互斥信号量(一定程度上解决优先级翻转的问题),递归信号量(信号量时基于队列实现,队列长度为1,队列项为0),主要用于资源的资源管理和任务同步。

            3.事件标志组:主要用于单个任务与多个任务或中断进行同步,为一对多的关系(信号量只能用于一对一)

            4.任务通知:可以用来取代消息队列,信号量,事件标志组等这些信息,是一个强大的功能。

七、FreeRTOS的实现

FreeRTOS作为开源的轻量级实时性操作系统, 实现了基本的实时调度、 信号量、 队列和存储管理,而且免费!
FreeRTOS的实现主要由list.c、 queue.c、 croutine.c和tasks.c 4个文件组成。

 list.c 是一个链表的实现, 主要供给内核调度器使用;

queue.c 是一个队列的实现, 支持中断环境和信号量控制;

croutine.c 和task.c是两种任务的组织实现。 对于croutine, 各任务共享同一个堆栈, 使RAM的需求进一步缩小,但也正因如此, 他的使用受到相对严格的限制。而task则是传统的实现,各任务使用各自的堆栈,支持完全的抢占式调度。


FreeRTOS的主要功能可以归结为以下几点:
1) 优先级调度、 相同优先级任务的轮转调度,同时可设成可剥夺内核或不可剥夺内核
2) 任务可选择是否共享堆栈(co-routines & tasks), 并且没有任务数限制
3) 消息队列, 二值信号量,计数信号量, 递归互斥体
4) 时间管理
5) 内存管理 

 

各文件关键部分的实现:
1、 PORTMACRO.H宏定义部分
1) 定义编译器相关的各种数据类型
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE unsigned portLONG
#define portBASE_TYPE long
2) 架构相关的定义
Cortex-M3的堆栈增长方向为高地址向低地址增长
#define portSTACK_GROWTH ( -1 )
每毫秒的心跳次数
#define portTICK_RATE_MS ( ( portTickType ) 1000 / configTICK_RATE_HZ )
访问SRAM的字节对齐
#define portBYTE_ALIGNMENT 8
3) 定义用户主动引起内核调度的2个函数
强制上下文切换, 用在任务环境中调用
#define portYIELD() vPortYieldFromISR()
强制上下文切换, 用在中断处理环境中调用
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired ) vPortYieldFromISR()
4) 定义临界区的管理函数
中断允许和关闭
#define portDISABLE_INTERRUPTS() vPortSetInterruptMask()
#define portENABLE_INTERRUPTS() vPortClearInterruptMask()
临界区进入和退出
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
用于在中断环境的中断允许和关闭
#define portSET_INTERRUPT_MASK_FROM_ISR() 0;vPortSetInterruptMask()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortClearInterruptMask();(void)x
2、 PORT.C C文件
1) 堆栈初始化
2) 启动任务调度 

3) 主动释放mcu使用权 

4) 心跳时钟处理函数 

Implementation of functions defined in portable.h for the ARM CM3 port

3、 HEAP_4. C文件

Heap_2.c 并不会把相邻的空闲块合并成一个更大的内存块,所以会产生内存碎片——如果分配和释放的总是相同大小的内存块,则内存碎片就不会成为一个问题 。

Heap_4.c就是改进了这一点。理论上不会产生内存碎片。
 

你可能感兴趣的:(FreeRTOS)