2.FREERTOS任务创建、内核链表初始化
硬件环境:cortex m4
FreeRTOS版本:v8.0.1
今天开始阅读freertos,阅读同时做下笔记,等哪天碰到移植问题再翻出来看看。
2.1 任务、链表结构体
源码中使用tskTCB来存储一个任务的所有信息,xLIST存储内核链表数据。一个系统最基本的功能是它的任务调度,在任务切换时最重要的则是内核链表,用图描述下这两个结构体,这样看起来比代码更清晰。(TCB中有省略成员)

(TCB结构体)
-------------------------------------------------分割线----------------------------------------------------

(LIST结构体)
一个TCB中包含了两个xLIST_ITEM作为链表节点,操作xLIST_ITEM中的指针指向既为控制一个任务进出某个链表。相比xLIST_ITEM,在xLIST中使用了精简版的结点xMINI_LIST_ITEM。
2.2 xTaskGeneEricCreate 任务创建流程分析
xTaskGeneEricCreate 函数用来创建一个新任务,在调度器启动前和启动后都可以创建。
Freertos在调度器启动后至少会有一个任务(IDLE)处于准备调度状态,即使开发者不去创建自己的任务。
xTaskGeneEricCreate源码流程:(拖动可以放大图片)

(任务创建流程图)
2.2.1 prvAllocateTCBAndStack 分配空间
Freertos使用pvPortMalloc在堆上分配一块TCB大小的内存空间,分配成功后还要使用pvPortMalloc分配一块内存,当做任务运行所需要的栈空间。这些空间直到任务被删除时才会得到释放。
在栈分配时有参数判断,若创建任务时有传入的栈地址则放弃分配。
分配的栈内存总大小为栈深度(传入参数)与栈宽度乘积。
2.2.2 prvInitialiseTCBVariables 执行初始化
prvInitialiseTCBVariables函数中主要执行了任务名字拷贝、优先级保存、两个链表节点初始化。
下图表示TCB中节点和链表初始化后指针指向

(链表及TCB初始化后)
2.2.3 pxPortInitialiseStack 执行”压栈”
pxPortInitialiseStack函数执行的代码非常奇特,一开始完全无法理解,还好在葵花宝典找到了da案,神书!引用M3权威指南上一句翻译:响应异常的第一个步骤是保存现场,硬件自动压栈,压栈后内存分布:

再对比看下pxPortInitialiseStack源码:

对比下两者动作,这个函数是对任务栈进行了一些处理,并且是模仿异(中断)常发生时所产生的动作。为什么一定要模仿异常进行压栈,首先扯一下freertos任务调度工作的大致流程:
当一个任务在运行时,还有一个内部定时器(systick)在一直计数,它的计数值和时钟频率比值可以看成为时间片。时间片到,中断产生,中断里进行上下文切换也就是pxReadyTasksLists中的任务被依次调度。硬件进入中断时便会自动压栈,不需要我们处理。中断处理完成后到中断返回时硬件还会自动出栈,还原进入异常前的状态。进中断时压入的那些寄存器值都被一一出栈 如:PC、R0、等寄存器。这样pxPortInitialiseStack函数就好理解了,它先对新创建的任务进行手动压栈,还多包括了R4-R11,那么在调度中断结束后这些手动压入的值将被自动出栈,进而使新任务运行起来。
PC位置是传入的任务主程序句柄地址,也就是我们要任务执行的主要程序,LR(返回寄存器)的位置是prvTaskExitError函数地址,这个函数里是一个for死循环加错误信息打印,也就是一个任务永远不应从它的主程序中跳出,如果跳出则进入prvTaskExitError函数打印错误。一般任务句柄里都会用for(;;)把它写死永远循环执行,需要退出时要将该任务delete掉。
2.3 pxReadyTasksLists链表
一个TCB创建并初始化完成后便开始插入pxReadyTasksLists等待被调度。pxReadyTasksLists链表是一个数组,优先级最大数决定它的大小。一个处于空闲状态的TCB(准备好被调度)在插入时是an照优先级作为索引插入的,这里说TCB插入不太准确,应该是TCB上的链表节点插入链表。
举个栗子,第一个任务插入空链表时的状况:

看着有些凌乱的话再来张大意图:

如果此时又有一个相同优先级任务创建,链表变为:

简略图:

链表将节点依次连接,组成TCB链,调度器运行时会an照需要遍历链表进而控制任务。
链表头部都带有index元素,一开始它指向链表本身,所以我们上面创建的任务都像是在尾插,事实上调度器运行起来时新节点插入的位置由index决定。
图解:
调度开始后index开始遍历readylist,它指向第一个TCB时,第一个TCB得到cpu资源开始运行,变为:

注意红色线条变化,此时如果动态创建了一个优先级相同的任务TCB3,应该把它插在哪里?如果插在TCB后面那对于TCB2来说是不公平的,因为人家排队等待cpu的时间肯定比TCB3长,其实仔细考虑下插在链表头部或尾部都是不规律的,只有利用index。Freertos将其插在TCB前面,以保证是当前链表最后一个得到cpu资源的位置:

新TCB进入链表,任务创建流程就快结束了。在程序尾部有些优先级判断,如果创建的任务比当前运行的任务优先级要高则使能PendSV中断。如果调度器是停止的则直接更改当前TCB指针。