1,任务
在裸机系统中,系统的主体就是 main() 函数里面顺序执行的无限循环, 在这个无限循环中CPU按照顺序完成各种操作。在多任务系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这种函数我们称为任务。
2,创建任务
(1)定义任务栈:
在裸机系统中,如果有全局变量,有函数调用,在中断发生时,全局变量以及函数内部信息怎么存放,我们并不关心,因为硬件会自动操作,中断产生就保存函数内部信息,中断返回就将保存的信息恢复到ARM的相关寄存器里,但是在裸机系统里面,程序是按顺序执行的,在函数内部运行时发生中断,硬件会自动保存函数数据,执行完中断回就返回到这个函数内部继续运行,但是在RTOS中,我们常常是在中断执行完后跳到另一个函数,这时硬件就做不到了,所以就需要我们用代码手动操作寄存器,自己定义函数栈,在任务切换时手动保存信息和恢复信息,从而跳入另一个任务。
定义任务栈就是定义一个大数组作为函数的栈:
#define TASK_STACK_SIZE 128
StackType_t TaskStack[Task_STACK_SIZE];
(StackType_t)在portmacro.h为uint32_t.
(2)定义任务函数:
定义任务函数就是写实现自己想要的功能的函数,比如点亮一个led
void led_on (void)
{
LED_on();
}
(3)定义任务控制块(TCB):
在task.c声明了控制块的结构体:
这里面主要用到就 pxTopOfStack(栈顶指针),pxStack任务栈起始地址,就是前面为函数创建的数组的首地址,xStateListItem(节点链表),pcTaskName[ configMAX_TASK_NAME_LEN ]任务名(可以随便给任务函数起名字)configMAX_TASK_NAME_LEN是一个宏,后面用来判断任务名长度不能超过一个值。
最后定义TCB_t 。
总结一下,TCB的功能就是记录任务函数的栈顶,栈底,等于划分了函数栈大小,然后记录了函数名称,内嵌一个节点,(注意,ARM栈是从上往下生长的,所以栈底在上面栈顶在下面)。
(4)实现创建函数:
这里实现静态任务创建,静态和动态的区别就是静态需要我们像上面一样创建一个数组当函数栈,而动态是动态申请一个栈,可以删除释放空间。
静态创建如下:
1,pxTaskCode指向任务函数。2,pcName任务名字。3,ulStackDepth栈深度,就是栈大小。4,pvParameters任务的形参,一般用不着设为null,(这个是放在R0寄存器里的,R0寄存器后面主要用于更新PSP指针以及用其地址来保存任务数据)。5,puxStackBuffer任务栈底指针。6,pxTaskBuffer任务控制块指针,指向TCB。
这里面用到了prvInitialiseNewTask函数,详细可以去task.c查看:
这个函数里面又调用了(port.c里)pxPortInitialiseStack函数:
一段话概况prvInitialiseNewTask函数作用(具体代码可以去看官方源码),把TCB栈顶指针根据栈大小往高地址偏移成为栈底,然后根据做8字节对齐然后传给pxPortInitialiseStack函数,pxPortInitialiseStack函数根据这个地址,把函数信息保存在栈中(入栈),指针也随保存信息而移动,最后成为栈顶指针,并返回保存给TCB的栈顶指针,这样一个任务的栈就创建完成了,最后把任务句柄指向TCB:
一定要把这几个函数联合起来看,不然会被绕晕。
3,将任务插入就序列表
在task.c定义了就绪列表,其实就是根节点,configMAX_PRIORITIES决定了优先级,有几个优先级就有几个链表,同优先级在同一个链表中。
就绪列表初始化,就是把几个根节点初始化一下。
插入,就是把任务TCB内嵌的节点根据优先级用排序插入对应链表,利用以下函数插入进行了:
好了,总结一下,想要搞清楚任务是如何创建的,需要我们去看看官方源码,并且先搞清楚各个函数的功能,栈怎么创建,怎么变化,怎么存储数据的,最后把几个函数联系起来看,就能捋清楚任务创建过程了。