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
//待完成

其中,关于字节对齐

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() 实现如下

1
//待完成

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();  //初始化就绪列表

//创建任务1
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));

//创建任务2
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()