深入FreeRTOS内核
目录
FreeRTOS基础
- 见下图,下图有可能不是最新的, 最新文件见FreeRTOS.xmind
- 互斥量、互斥锁,本来的概念确实是:谁上锁就得由谁解锁。** (放到xmind中)**
但是FreeRTOS并没有实现这点,只是要求程序员按照这样的惯例写代码。韦东山freeRTOS系列教程之【第七章】互斥量(mutex)_悦己之作,方能悦人的技术博客_51CTO博客 - vTaskStartScheduler调用后,才开始调度
- FreeRTOS操作系统支持三种调度方式:抢占式调度(Pre-emptive),时间片调度(time slice , 同优先级任务才会是使用时间片调度)和合作式调度(co-operative, 使用合作调度器时,只有在Running状态任务进入Blocked状态,或者Running状态任务通过调用taskYIELD()显式让步(手动请求重新调度)时才会发生任务切换。)。实际应用主要是抢占式调度和时间片调度结合的调度方法,合作式调度用到的很少。
学习总结
任务创建
FreeRTOS的任务(其实就是线程)创建, 会创建在堆区划分一块空间, 这块空间用来放置TCB和任务栈空间 , 栈空间就是申请的栈大小, 这个TCB就是一个结构体, 用于记录优先级,运行时间,运行状态等信息。 结构体定义如下
/*
* Task control block. A task control block (TCB) is allocated for each task,
* and stores task state information, including a pointer to the task's context
* (the task's run time environment, including register values)
*/
typedef struct tskTaskControlBlock
{
/* 栈顶<元素>指针,注意与 pxEndOfStack 的区别。 必须是结构体的第一个成员. */
volatile StackType_t * pxTopOfStack;
/* MPU 模块设置,必须是结构体的第二个成员 */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings;
#endif
ListItem_t xStateListItem; /* 列表项指示任务当前的状态 (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /* 用于在事件列表中. */
UBaseType_t uxPriority; /* 任务优先级(0为优先级最低). */
StackType_t * pxStack; /* 栈底指针,指向栈开始处. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任务名 */
/* 如果栈向上生长,则定义栈顶指针 */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack;
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /* 保存临界区的栈深度. 在函数vTaskEnterCritical() 和 vTaskExitCritical()中会维护这个值*/
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*TCB的编号,每次增加一个task,TCB的编号就会增加. */
UBaseType_t uxTaskNumber; /*task的编号,其值和uxTCBNumber相等,可用于调试追踪. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*上一次分配给该任务的优先级,用于优先级继承机制. */
UBaseType_t uxMutexesHeld; /* 持有的信号量 */
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
/* 存储一些私有的变量 */
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
/* task 运行的总时间 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter;
#endif
/* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html for additional information. */
#if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent;
#endif
/* 任务通知相关变量 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/* FreeRTOS.h 中有关于 tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE 的详细注释。主要的作用是决定task创建的资源是动态还是静态分配内存. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /* 标记stack 和 TCB分配方式,在删除任务时,根据此标志来对应释放内存 */
#endif
/* delay 终止标志 */
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
/* error number*/
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
这里有必要跟linux进行区分下, 在linux中(从 Linux 内核的角度来说,其实它并没有线程的概念。Linux 把所有线程都当做进程来实现,它将线程和进程不加区分的统一到了 task_struct 中。线程仅仅被视为一个与其他进程共享某些资源的进程,而是否共享地址空间几乎是进程和 Linux 中所谓线程的唯一区别), 另外线程的栈是从进程地址空间mmap一段独有的地址(堆和栈之间有一段空间是用于mmap的), 另外在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先进程用户空间中的栈,而是一个单独内核空间的栈,这个称作进程内核栈。进程内核栈在进程创建的时候
内存管理
FreeRtos的堆区分配方法共有4种, 第一种, 我的理解就是一个数组, 从从未分配过的地方开始进行分配, 无法被释放,没有内存释放, 好处是可确定性, 且没有碎片。例子: 已经分配 ABC , 如果要分配D, 则从C后面分配D, 变成ABCD。 并且分配后都无法释放, 因为没有释放函数
第二种方法,已经被heap4.c取代了, 了解下就好。 有释放函数, 分配时, 从最开始的地址开始找, 找到一个空闲块可以容纳就放。 例子, 一开始ABD,后面B释放了, 变为A空格D, 这个要分配C, C比B小, 则从一开始找, 找到了B能放下, 则就变为AC(B-C)D 。 实现上, 就是用一个链表来记录空闲块, 一开始链表只有一个(块大小为分配的堆总大小), 对于AC(B-C)D , 则变为 两个元素的链表, B-C是一个, D后面又是一个。
第三种方法,其实就是调用malloc和free, 具体实现由编译器来实现, 这里仅仅只是封装了一下malloc和free,并确保线程安全
第四种方法,heap_4.c, 就是在free一块空闲内存时, 如果左和右相邻 有空闲块, 则把空闲块合为一个, 方法为将当前空想块插入链表时,查看左边和右边空闲块地址是否相邻,是的话,合并就可以了。
第五种方法, heap_5.c , 用的就是heap4.c的方法, 但是支持不相邻的堆区, 类似于支持多个堆区
FreeRTOS的调度分为三种, 抢占式时间片(就是谁优先级高谁执行,相同优先级则轮流执行), 抢占式无时间片(谁优先级高谁执行, 相同优先级则谁先谁执行),合作模式(非抢占, 高优先级不抢占,等低优先级执行完或者阻塞后再轮到高优先级)
软件定时器
- FreeRTOS 通过一个prvTimerTask任务(也叫守护任务Daemon)管理软定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。prvTimerTask任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h 中的宏定义configUSE_TIMERS设置为1 ,将相关代码编译进来,才能正常使用软件定时器相关功能
- 指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息, 在回调函数中调用任何会阻塞任务的 API 函数
- 硬件定时器的定时精度与系统时钟的周期有关,一般系统利用SysTick作为软件定时器的基础时钟,系统节拍配置为FreeRTOSConfig.h中的
configTICK_RATE_HZ
,默认是1000,那么系统的时钟节拍周期就为1ms - FreeRTOS 提供的软件定时器支持单次模式和周期模式
LiteOS、RT-thread、FreeRTOS、uCOS II的区别
一、freeRTOS比uCOS II优胜的地方:
1。内核ROM和耗费RAM都比uCOS 小,特别是RAM。 这在单片机里面是稀缺资源,uCOS至少要5K以上, 而freeOS用2~3K也可以跑的很好。
2。freeRTOS 可以用协程(Co-routine)(目前官方已经废弃了,不再维护),减少RAM消耗(共用STACK)。uCOS只能用任务(TASK,每个任务有一个独立的STACK)。
3。freeRTOS 可以有优先度一样的任务,这些任务是按时间片来轮流处理,uCOSII 每个任务都只有一个独一无二的优先级。因此,理论上讲,freeRTOS 可以管理超过64个任务,而uCOS只能管理64个。
4。freeRTOS 是在商业上免费应用。uCOS在商业上的应用是要付钱的。
二、freeRTOS 不如uCOS的地方:
1。比uSOS简单,任务间通讯freeRTOS只支持Queque, Semaphores, Mutex。 uCOS除这些外,还支持Flag, MailBox.
2。uCOS的支持比freeRTOS 多。除操作系统外,freeRTOS只支持TCPIP, uCOS则有大量外延支持,比如FS, USB, GUI, CAN等的支持
3。uCOS可靠性更高,而且耐优化,freeRTOS 在我设置成中等优化的时候,就会出问题。