任务创建
任务概念
什么是任务
任务函数没有返回值,并且参数是指针类型(很少使用,基本可以不管)
void entry_task(void *pvParameters)
{
/* 任务主体,无限循环且不能返回值 */
while(1)
{
/* 任务主题代码 */
}
}
任务的实现过程
定义任务栈
下面的任务分别定义的任务栈大小为:128和64,展通空间大小为1284字节和644字节
//任务堆栈大小
#define TASK1_STK_SIZE 128
StackType_t Task1Stack[TASK1_STK_SIZE];
//任务堆栈大小
#define TASK2_STK_SIZE 64
StackType_t Task2Stack[TASK2_STK_SIZE];
定义任务函数
//任务1函数
void tsak1_task(void *pvParameters)
{
while(1)
{
LED1 = 0;
vTaskDelay(500);
LED1 = 1;
vTaskDelay(500);
}
}
//任务2函数
void tsak2_task(void *pvParameters)
{
while(1)
{
LED2 = 0;
vTaskDelay(1000);
LED2 = 1;
vTaskDelay(1000);
}
}
vTaskDelay()为FreeRTOS提供的延时函数,在FreeRTOSConfig.h文件中进行了时钟节拍的配置,如下
//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度
#define configTICK_RATE_HZ (( TickType_t )1000)
任务控制块
人物的参数可以通过任务控制块指定
typedef struct tskTaskControlBlock
{
// 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
volatile StackType_t *pxTopOfStack; /* 栈顶指针 */
// MPU相关暂时不讨论
#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
// 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook 函数调用
#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
// 支持NEWLIB的一个变量
#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;
列表和列表项
-
列表
listFIRST_LIST_INTEGRITY_CHECK_VALUE和istSECOND_LIST_INTEGRITY_CHECK_VALUE用来检查列表的完整性,默认不开启;如果要开启需要将配置文件中的宏设置为1,开启之后分别会添加一个整型变量值1和2,初始化时会写入一个特殊的值
typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VALUE /* 列表内有效列表项个数 */ configLIST_VOLATILE UBaseType_t uxNumberOfItems; /* 记录当前列表项索引号,用于遍历列表 */ ListItem_t * configLIST_VOLATILE pxIndex; /* 列表中最后一个列表项,表示列表结束 */ MiniListItem_t xListEnd; listSECOND_LIST_INTEGRITY_CHECK_VALUE } List_t;
-
列表项
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE类似于列表中的两个参数,需要在配置文件中使能才能开启对应的功能——检查列表项完整性
struct xLIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 列表项值 */ configLIST_VOLATILE TickType_t xItemValue; /* 指向下一个列表项值 */ struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* 指向上一个列表项值 */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* 当前列表项的拥有者 */ void * pvOwner; /* 当前列表项归属的列表,指向就绪列表 */ void * configLIST_VOLATILE pvContainer; listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE }; typedef struct xLIST_ITEM ListItem_t;
-
迷你列表项
基本和列表项相似,知识功能相对精简,没有对当前列表项拥有者的记录和当前列表项的归属的列表的记录
struct xMINI_LIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 列表项值 */ configLIST_VOLATILE TickType_t xItemValue; /* 指向下一个列表项值 */ struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* 指向上一个列表项值 */ struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE }; typedef struct xMINI_LIST_ITEM MiniListItem_t;
列表与列表项初始化
-
列表初始化
void vListInitialise( List_t * const pxList ) { /* 此时列表中只有一个列表项xListEnd,索引地址指向尾节点 */ pxList->pxIndex = ( ListItem_t * ) &(pxList->xListEnd ); /* xListEnd列表项值初始化为portMAX_DELAY(portmacro.h中定义的宏)*/ pxList->xListEnd.xItemValue = portMAX_DELAY; /* 初始化xListEnd的pxNext变量,此时指向自身 */ pxList->xListEnd.pxNext = ( ListItem_t * ) &(pxList->xListEnd ); /* 初始化xListEnd的pxPrevious变量,此时指向自身 */ pxList->xListEnd.pxPrevious = ( ListItem_t * ) &(pxList->xListEnd ); /* 当前有效的列表项个数为0,没有算xListEnd */ pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /* 列表完整性检查,需要配置文件中设置有效才能启动 */ listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); }
列表的初始化流程如下:
-
列表项初始化
列表项根据实际使用情况来初始化,所以其初始化函数很短。
void vListInitialiseItem( ListItem_t * const pxItem ) { /* 列表项初始化为不归属任何任务列表所有 */ pxItem->pvContainer = NULL; /* 列表项完整性检查,需要配置文件中设置有效才能启动 */ listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ); listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ); }
-
列表项插入
将指定列表项插入到列表中,通过vListInsert()函数完成列表项插入
void vListInsert( List_t * const pxList, //列表项要插入的列表 ListItem_t * const pxNewListItem) //要插入的列表项 { ListItem_t *pxIterator; /* 获取要插入的列表项值 */ const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; /* 检测列表和列表项的完整性,检测是否值被改变了 */ listTEST_LIST_INTEGRITY( pxList ); listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); /* 获取列表项的位置,就是获取列表的最末尾;如获取到的列表项值为最大时,直接插入到尾部 */ if( xValueOfInsertion == portMAX_DELAY ) { pxIterator = pxList->xListEnd.pxPrevious; } else{ /* 从尾节点开始遍历,与下个节点的Value值进行比较,当要插入Value值大于遍历列表项Value时,就获得插入位置(按升序方式插入) */ for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) { //空循环,什么也不做 } } /* 更新后面节点信息 (前后指针进行赋值) */ pxNewListItem->pxNext = pxIterator->pxNext; pxNewListItem->pxNext->pxPrevious = pxNewListItem; /* 更新前面节点信息(前后指针进行赋值) */ pxNewListItem->pxPrevious = pxIterator; pxIterator->pxNext = pxNewListItem; /* 把列表项归属于当前的列表 */ pxNewListItem->pvContainer = ( void * ) pxList; /* 有效列表项数量进行累加 */ ( pxList->uxNumberOfItems )++; }
-
列表项的末尾插入
void vListInsertEnd( List_t * const pxList, //列表项要插入的列表 ListItem_t * const pxNewListItem ) //要插入的列表项 { /* 获取当前列表索引值 */ ListItem_t * const pxIndex = pxList->pxIndex; /* 检测列表和列表项的完整性,检测是否值被改变了 */ listTEST_LIST_INTEGRITY( pxList ); listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); /* 插入到索引值之前,先进行尾部更新 */ pxNewListItem->pxNext = pxIndex; pxNewListItem->pxPrevious = pxIndex->pxPrevious; /* 再进行头部更新 */ pxIndex->pxPrevious->pxNext = pxNewListItem; pxIndex->pxPrevious = pxNewListItem; /* 列表项归属于列表 */ pxNewListItem->pvContainer = ( void * ) pxList; /* 更新列表项数目 */ ( pxList->uxNumberOfItems )++; }
-
列表项删除
从列表中删除指定的列表项,通过uxListRemove()函数来完成
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) { /* 获取列表项所在的列表地址 */ List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer; /* 将要删除的列表项的前后两个列表项进行连接 */ pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; /* Only used during decision coverage testing. */ mtCOVERAGE_TEST_DELAY(); /* 索引是否需要更新 */ if( pxList->pxIndex == pxItemToRemove ) { pxList->pxIndex = pxItemToRemove->pxPrevious; } else { mtCOVERAGE_TEST_MARKER(); } /* 被删除列表项的成员变量pvContainer清零 */ pxItemToRemove->pvContainer = NULL; /* 有效列表项数量减一 */ ( pxList->uxNumberOfItems )--; /* 返回当前列表的有效列表项数量 */ return pxList->uxNumberOfItems; }
-
列表的遍历
List_t中的成员变量pxIndex是用来遍历列表的,FreeRTOS使用如下函数(宏)来完成列表的遍历,每调用一次这个函数,列表的pxIndex变量就会指向下一个列表项,并返回这个列表项的pvOwner变量值
/* pxTCB用来保存pxIndex所指向的列表项的pvOwner, pxList表示要遍历的列表 */ #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ { \ /* 首先获取当前列表 */ List_t * const pxConstList = ( pxList ); \ /* 列表的pxIndex变量指向下一个列表项 */ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ /* 如果pxIndex指向了列表的xListEnd成员变量,表示到了列表末尾 */ if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \ { \ /* 此时就跳过xListEnd,pxIndex再次指向列表头的列表项,这样就完成了一次列表遍历 */ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ } \ /* 获取当前pxIndex指向的所有者(其实就是任务控制块)*/ ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ }
任务创建函数
任务创建由函数xTaskCreate()完成
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
/* 仅能使用字符或字符串 */
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
具体使用范例
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
/* 任务创建 */
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char * )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void * )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t * )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开始任务调度
任务初始化函数分析
函数prvInitialiseNewTask()用于完成对任务的初始化
static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t *pxNewTCB,
const MemoryRegion_t * const xRegions)
任务堆栈初始化函数分析
任务初始化函数中会通过pxPortInitialiseStack()对任务堆栈初始化
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
/* Save code space by skipping register initialisation. */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
/* A save method is being used that requires each task to maintain its
own exec return value. */
pxTopOfStack--;
*pxTopOfStack = portINITIAL_EXEC_RETURN;
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:XPSR、R15(PC)、R14(LR)、R12、R3 ~ RO、R11 ~ R14。
创建任务到完成就绪列表
列表数组pxReadyTasksLists[]就是任务就绪列表,数组大小为configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数prvAddNewTaskToReadyList()来完成
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )//正在运行任务块为NULL,表示没有任务运行
{
/* There are no other tasks, or all the other tasks are in
the suspended state - make this the current task. */
pxCurrentTCB = pxNewTCB;//将新任务的任务控制块赋值给当前运行任务块
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* This is the first task to be created so do the preliminary
initialisation required. We will not recover if this call
fails, but we will report the failure. */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* If the scheduler is not already running, make this task the
current task if it is the highest priority task to be created
so far. */
if( xSchedulerRunning == pdFALSE )
{//新任务优先级比正在运行优先级高
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++;//任务控制块编号 + 1
#if ( configUSE_TRACE_FACILITY == 1 )
{
/* Add a counter into the TCB for tracing only. */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
prvAddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
/* If the created task is of a higher priority than the current task
then it should run now. */
//新任务优先级比正在运行优先级高,任务切换
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}