一、任务
在裸机系统中,系统的主体就是main()函数里面顺序执行的无限循环,CPU按照循序完成哥哥操作。
在多任务系统中,根据功能的不同,把整个系统分割成一个独立的且无法返回的函数。
二、创建任务
在多任务系统中,每个任务都是独立的,互不干扰的,所以要为每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。
2.1 创建任务栈
1 #define TASK1_STACK_SIZE 128 (2)
2 StackType_t Task1Stack[TASK1_STACK_SIZE]; (1)
3
4 #define TASK2_STACK_SIZE 128
5 StackType_t Task2Stack[TASK2_STACK_SIZE];
2.2 定义任务函数
main.c
1 /* 软件延时 */
2 void delay (uint32_t count)
3 {
4 for (; count!=0; count--);
5 }
6 /* 任务 1 */
7 void Task1_Entry( void *p_arg ) (1)
8 {
9 for ( ;; )
10 {
11 flag1 = 1;
12 delay( 100 );
13 flag1 = 0;
14 delay( 100 );
15 }
16 }
17
18 /* 任务 2 */
19 void Task2_Entry( void *p_arg ) (2)
20 {
21 for ( ;; )
22 {
23 flag2 = 1;
24 delay( 100 );
25 flag2 = 0;
26 delay( 100 );
27 }
28 }
2.3 定义任务控制块
在多任务系统中,任务的执行是由系统调度的。系统为了顺利的调度任务,为每个任务都额外定义了一个任务控制块,这个任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等。有了这个任务控制块之后,以后系统对任务的全部操作都可以通过这个任务控制块来实现。
1 typedef struct tskTaskControlBlock
2 {
3 volatile StackType_t *pxTopOfStack; /* 栈顶 */ (1)
4
5 ListItem_t xStateListItem; /* 任务节点 */ (2)
6
7 StackType_t *pxStack; /* 任务栈起始地址 */ (3)
8 /* 任务名称,字符串形式 */(4)
9 char pcTaskName[ configMAX_TASK_NAME_LEN ];
10 } tskTCB;
2.4 实现任务创建函数
任务的栈,任务的函数实体,任务的控制块最终需要联系起来才能由系统进行统一调度。那么这个联系的工作就由任务创建函数 xTaskCreateStatic()来实现。
任务的创建有两种方法,一种是使用动态创建,一种是使用静态创建。动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放。静态创建时,任务控制块和栈的内存需要事先定义好,是静态的内 存 , 任 务 删 除 时 , 内 存 不 能 释 放 。 目 前 我 们 以 静 态 创 建 为 例 来 讲 解 ,configSUPPORT_STATIC_ALLOCATION 在 FreeRTOSConfig.h 中定义,我们配置为 1。
静态创建:
xTaskCreateStatic()函数 任务创建函数
prvInitialiseNewTask()函数 创建新任务函数
三、实现就绪列表
3.1 定义就绪列表
任务创建好之后,我们需要把任务添加到就绪列表里面,表示任务已经就绪,系统随时可以调度。
1 /* 任务就绪列表 */
2 List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
就绪列表实际上就是一个 List_t 类型的数组,数组的大小由决定最 大 任 务 优 先 级 的 宏 configMAX_PRIORITIES 决 定 , configMAX_PRIORITIES 在FreeRTOSConfig.h 中默认定义为 5,最大支持 256 个优先级。数组的下标对应了任务的优先级,同一优先级的任务统一插入到就绪列表的同一条链表中。
3.2 就绪列表初始化
就绪列表初始化的工作在函数prvInitialiseTaskLists()里面实现
将任务插入到就绪列表:任务控制块里面有一个 xStateListItem 成员,数据类型为 ListItem_t,我们将任务插入到就绪列表里面,就是通过将任务控制块的 xStateListItem 这个节点插入到就绪列表中来实现的。如果把就绪列表比作是晾衣架,任务是衣服,那 xStateListItem 就是晾衣架上面的钩子,每个任务都自带晾衣架钩子,就是为了把自己挂在各种不同的链表中。
1 /* 初始化与任务相关的列表,如就绪列表 */
2 prvInitialiseTaskLists();
3
4 Task1_Handle = /* 任务句柄 */
5 xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
6 (char *)"Task1", /* 任务名称,字符串形式 */
7 (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
8 (void *) NULL, /* 任务形参 */
9 (StackType_t *)Task1Stack, /* 任务栈起始地址 */
10 (TCB_t *)&Task1TCB ); /* 任务控制块 */
11
12 /* 将任务添加到就绪列表 */
13 vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
15
16 Task2_Handle = /* 任务句柄 */
17 xTaskCreateStatic( (TaskFunction_t)Task2_Entry, /* 任务入口 */
18 (char *)"Task2", /* 任务名称,字符串形式 */
19 (uint32_t)TASK2_STACK_SIZE , /* 任务栈大小,单位为字 */
20 (void *) NULL, /* 任务形参 */
21 (StackType_t *)Task2Stack, /* 任务栈起始地址 */
22 (TCB_t *)&Task2TCB ); /* 任务控制块 */
23 /* 将任务添加到就绪列表 */
24 vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
由于这里是静态创建,所以任务的优先级是数组的下标。
四、实现调度器
调度器是操作系统的核心,其主要功能就是实现任务的切换,即从就绪列表里面找到优先级最高的任务,然后去执行该任务。
4.1 启动调度器
调度器的启动由 vTaskStartScheduler()函数来完成。
4.2 任务切换
任务切换就是在就绪列表中寻找优先级最高的就绪任务,然后去执行该任务。但是目
前我们还不支持优先级,仅实现两个任务轮流切换,任务切换函数 taskYIELD()具体实现。