1. 引言
经过第一节的移植,我们已经拿到了一个可以用的工程。 经过第二三节的基础知识,我们对基本的数据结构,列表、队列这些也有了一个了解。
接下来就可以单步跟踪了,看一下系统是怎么运行的。
使用FreeRTOS,首先要新建一个任务,我们可以单步跟一下它的运行流程,在调试之前,还是要先把最重要的几个结构体整理一下。
(本文初版为2020.4.12,FreeRTOS的代码版本为FreeRTOS Kernel V10.3.1)
2. 重要结构体
2.1 TCB
做FreeRTOS肯定首先要把大名鼎鼎的TCB干掉。
直接上源码先
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack;
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings;
#endif
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
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;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
#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
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
typedef tskTCB TCB_t;
一些可选功能先跳过,抽出其中几个关键的member说一下。
{
volatile StackType_t *pxTopOfStack;
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t *pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
- pxStack : 每个任务都有一个栈帧空间,pxStack 指的就是这个空间的起始地址(低地址)。(malloc开出来的地址)
- pxTopOfStack:栈顶的位置。随着入栈出栈操作,栈顶会一直变。
- pxEndOfStack:栈空间的结尾,如果栈向上生长,就是栈帧空间最大的地址,如果栈向下,就是栈帧空间最小的地址,也就是pxStack,所以portSTACK_GROWTH > 0时才会有这个member,不然重复。
- xStateListItem:状态列表,表明当前系统的状态。挂在链表中是方便来找,如果是用一个变量来表明,每次还得遍历一遍来找,效率低,链表方便。
- xEventListItem:事件列表
- uxPriority : 优先级。FreeRTOS的优先级是越大越优先。最大值是在头文件里配置的,配置一个合适的值就好,不然太大也浪费。因为每个优先级其实也会申请空间来存列表。
2.2 xList
列表的结构体在 列表 里介绍了。
使用起来基本如下图:
网上找了一张图,侵删。
有些列表是有序的。要用vListInsert插入,按照value值组成一个升序,在有序列表中,xListEnd.pxNext指向的是头,xListEnd.pxPrevious指向的是尾巴。在真实使用时,要根据顺序来做判断的。
有些链表是无序的。vListInsertEnd插入,只是表示一个集合。无序列表中,具体到FreeRTOS中的轮询列表,它不标记逻辑头尾,因为vListInsertEnd()插入时并不是根据xListEnd,而是pxIndex
3. 任务创建流程
任务创建主要使用xTaskCreate接口来创建,流程十分简介。
- 开空间
任务需要管理 和 运行,任务管理需要TCB控制块,任务运行需要任务堆栈。
所以xTaskCreate的开头就是为这两部分需求开空间。在具体代码中会根据栈的生长方向来做一个malloc顺序的调整,主要就是为了如果栈溢出,不至于覆盖到TCB结构体。这里也是比较细节的。
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );负责给TCB模块开空间。
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );负责给堆栈开空间,这个开空间的控制参数usStackDepth 是从函数入参传进来的。 - TCB初始化
接下来,就是对TCB结构体的参数一堆初始化,调用prvInitialiseNewTask接口来进行。这里面主要是对TCB结构体成员进行初始化,包括栈空间地址pxTopOfStack的赋值,任务名pcTaskName的赋值等。
- pxTopOfStack,对pxTopOfStack赋值是根据栈生长方向来计算。根据栈空间的起始地址(malloc开的低地址)和整个栈的大小,来判断pxTopOfStack在栈空间最低还是最高位置(另外做一下字节对齐)
- pcTaskName,这里通过对每个字符的赋值来填充pcTaskName,在最后添加’\0’结束符。而没有用strcpy这种,减少对库的依赖。
- uxPriority,优先级直接用配置值
- xStateListItem,使用vListInitialiseItem接口初始化状态列表。
- xEventListItem,使用vListInitialiseItem接口初始化事件列表。
然后初始化栈,栈第一次是空的,虽然是第一次用,但是要把栈改成一种“从任务切换出来”的样子,这样下次任务切换回来的时候,就不用针对第一次使用这个任务的栈做处理了。
- 加入就绪列表
最后把任务先加入到ready列表中(prvAddNewTaskToReadyList),等待调度。
基本流程图如下
4. 参考链接
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)