深度解剖~ FreeRtos阅读笔记2 任务创建

来源:互联网 发布:qq秀照片秀网络繁忙 编辑:程序博客网 时间:2024/06/10 09:42

2.任务创建

开始源码阅读,这里我使用了TI cc3200 cortex m4的学习板,由TI提供移植后的freertos。

xTaskGenericCreate 函数用来创建一个新任务,在调度器启动前和启动后都可以创建。Freertos在调度器启动后至少会有一个任务在运行,即使开发者不去创建任务。源码中使用TCB结构体存放一个任务的所有信息,控制了某个TCB便是控制一个任务的运行状态。一个TCB将会根据需要在不同链表内重复插入和移除,比如等待被调度的任务插在readylist中,被taskDelay等函数睡过的任务则乖乖的放入xDelayedTaskList1或xDelayedTaskList2中,这两个链表有点意思,它们解决了systick变量溢出的问题,不过今天它不是主角,以后再细细说来。

先贴一些结构体图片(//亿图画的,绘画水平勉强能看。。。)

(TCB)

一个TCB中有两个链表节点,链表本身和节点并不完全相同,链表只保留了几个指针,其它元素根本使用不到,所以被叫做mini_list_item:


(LIST)


看清楚了任务及链表结构体组成,开始主流程分析。

(任务创建流程图)

2.1 prvAllocateTCBAndStack函数: 创建TCB 

Freertos在堆上分配一块TCB大小的空间,分配成功后便给任务分配一块任务栈空间,大小由开发者参数决定。注意栈分配时具有判断:

vPortMallocAligned( x,puxStackBuffer)(((puxStackBuffer)==NULL)?(pvPortMalloc((x))):(puxStackBuffer ))

表示开发者可以自己给出任务栈地址,也可以填入NULL选择让freertos分配(分配大小为栈深度*StackType_t)


2.2 prvInitialiseTCBVariables 、prvInitialiseTaskLists:负责初始化

栈指针对齐后便开始初始化该TCB上的一些链表节点prvInitialiseTCBVariables()。

如果是第一次创建任务还会初始化所有链表prvInitialiseTaskLists()。这些链表变量是task.c中的全局变量。链表中的一些值如MAX_DELAY一般会充当哨兵,当某个节点要按照升序或降序插入时查到该值freertos就认为这条链表已经遍历到尾部或头部了。

初始化后一些指针会做更改,还是贴一张图吧,有图有真相:



(链表及TCB初始化后)

不止这些,task.c中的全局变量还有很多,估计这些全局变量将会成为学习的难点。换我老师的话他肯定会说:恶心死人家了~。嗯 他是个男的。

2.3  pxPortInitialiseStack 执行”压栈

看源码时这里困扰了我好久,真是玩手游的时间都用上了,任务都没做!。还好葵花宝典上有答案,神书!引用M3权威指南上一句翻译:响应异常的第一个步骤是保存现场,硬件自动压栈,压栈后内存分布


再对比看下pxPortInitialiseStack源码:

对比下两者动作,这个函数是对任务栈进行了一些处理,并且是模仿异(中断)常发生时所产生的动作。为什么一定要模仿异常进行压栈,首先扯一下freertos任务调度工作的大致流程:

当一个任务在运行时,还有一个内部定时器(systick)在一直计数,它的计数值和时钟频率比值可以看成为时间片。时间片到,中断产生,中断里进行上下文切换也就是pxReadyTasksLists中的任务被依次调度。硬件进入中断时便会自动压栈,不需要我们处理。中断处理完成后到中断返回时硬件还会自动出栈,还原进入异常前的状态。进中断时压入的那些寄存器值都被一一出栈 如:PC、R0、等寄存器。这样pxPortInitialiseStack函数就好理解了,它先对新创建的任务进行手动压栈,还多包括了R4-R11,那么在调度中断结束后这些手动压入的值将被自动出栈,进而使新任务运行起来。

PC位置是传入的任务主程序句柄地址,也就是我们要任务执行的主要程序,LR(返回寄存器)的位置是prvTaskExitError函数地址,这个函数里是一个for死循环加错误信息打印,也就是一个任务永远不应从它的主程序中跳出,如果跳出则进入prvTaskExitError函数打印错误。一般任务句柄里都会用for(;;)把它写死永远循环执行,需要退出时要将该任务delete掉。


2.4插入链表

一个TCB创建并初始化完成后便开始插入readylist等待被调度。Readylist是一个数组,优先级最大数决定它的大小,一个TCB在插入时是按照优先级作为索引插入的,这里说TCB插入不太准确,应该是TCB上的链表节点插入链表。

举个栗子,第一个任务插入空链表时的状况:


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

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


简略图:

链表将节点依次连接,组成TCB链,调度器运行时会按照需要遍历链表进而控制任务。

链表头部都带有index元素,一开始它指向链表本身,所以我们上面创建的任务都像是在尾插,事实上调度器运行起来时新节点插入的位置由index决定。

图解:

调度开始后index开始遍历readylist,它指向第一个TCB时,第一个TCB得到cpu资源开始运行,变为:

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


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


原创粉丝点击