7.任务的定义与任务切换的实现
任务通俗的讲就是一件需要完成的事情,在多任务系统里指的就是一个独立的函数。
在裸机系统中,全局变量、局部变量、返回地址等都放在栈里,栈是单片机 RAM 里一段连续的内存空间,栈的大小一般在启动文件或链接脚本里指定,由 _main 初始化。
多任务系统中,每个任务是独立互不干扰的,所以每个任务都分配独立的栈空间。
任务控制块相当于任务的身份证,里面存有任务的所有信息,如任务的栈指针,任务名称,任务形参等。
任务控制块类型声明:
1 2 3 4 5 6 7 8
| typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; ListItem_t xStateListItem; StackType_t *pxStack; char pcTaskName[configMAX_TASK_NAME_LEN]; } tskTCB; typedef tskTCB TCB_t;
|
1.实现任务创建函数
任务的栈,任务的函数实体,任务的控制块,最终需要联系起来才能由系统进行统一调度。
联系的工作由任务创建函数 xTackCreateStatic() 来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #if (configSUPPORT_STATIC_ALLOCATION == 1) TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, const char* const pcName, const uint32_t ulStackDepth, void * const pvParameters, StackType_t *const puxStackBuffer, TCB_t * const pxTaskBuffer) { TCB_t * pxNewTCB; TaskHandle_t xReturn; if((pxTaskBuffer != NULL) && (puxStackBuffer != NULL)) { pxNewTCB = (TCB_t *)pxTaskBuffer; pxNewTCB->pxStack = (StackType_t *) puxStackBuffer; prvInitialiseNewTask(pxTaskCode, pcName, ulStackDepth, pvParameters, &xReturn, pxNewTCB); } else { xReturn = NULL; } return xReturn; } #endif
|
其中 prvInitialiseNewTask() 函数的实现如下。
其中,关于字节对齐
1 2 3 4
| unsigned int cal_align(unsigned int n, unsigned align) { return ((n + align - 1) & (~(align - 1))); }
|
① (align - 1):表示对齐所需的对齐位,如:
- 2字节对齐位为1;
- 4字节对齐位为11;
- 8字节对齐位为111;
- 16字节对齐位为1111;
- 。。。
② (n + align - 1):表示 n 补齐对齐所需数据。
③ &(~(align - 1)):表示去除由于补齐造成的多余数据。
其中 prvInitialiseNewTask() 中需要初始化任务栈,函数 pxPortInitialiseStack() 实现如下
2.实现就绪列表
任务就绪列表是一个 List_t 类型的数组。
1
| List_t pxReadyTasksLists[configMAX_PRIORITIES];
|
数组的下标对应了任务的优先级,同一优先级的任务统一插入到就绪列表的同一条链表中。
初始化任务就绪列表 prvInitialiseTaskLists() 就是将链表数组中每个元素初始化。
将任务插入到就绪列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| prvInitialiseTaskLists();
Task1_Handle = xTaskCreateStatic( (TaskFunction_t)Task1_Entry, (char *)"Task1", (uint32_t)TASK1_STACK_SIZE, (void*)NULL, (StackType_t*)Task1Stack, (TCB_t*)&Task1TCB);
vListInsertEnd( &(pxReadyTasksLists[1]), &(((TCB_t*)(&Task1TCB))->xStateListItem));
Task2_Handle = xTaskCreateStatic( (TaskFunction_t)Task2_Entry, (char*)"Task2", (uint32_t)TASK2_STACK_SIZE, (void*)NULL, (StackType_t*)Task2Stack, (TCB_t*)&Task2TCB);
vListInsertEnd( &(pxReadyTasksLists[2]), &(((TCB_t*)(&Task2TCB))->xStateListItem));
|
3.实现任务调度器
调度器是操作系统的核心,主要功能是实现任务的切换。
启动调度器:
vTaskStartScheduler()
xPortStartScheduler()
启动第一个任务:
prvStartFirstTask()
SVC中断,该函数开始真正启动第一个任务:
vPortSVCHandler()
任务切换,触发PendSV中断:
taskYIELD()
PendSV中断服务函数是真正实现任务切换的地方:
xPortPendSVHandler()