This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
uCOS-II 中的一个重要组成部分就是 任务,因为大多数嵌入式设备的处理器能力有限,所以大多的 RTOS 都采用了多进程/单线程模型;这就意味着 OS 需要通过在不同的任务之间切换,而如何创建、管理任务就成为了一个重要的组成部分。
任务基础
uCOS-II 的任务通常就是一个无限的循环,就像我们在裸机编程时使用的循环一样:
1 | void task(void* pdata) { |
那么问题就来了,既然每个任务都是无穷循环,如何从一个任务跳转到另一个任务中呢?
不知道大家是否还记得 Cortex-M3 中的 PC
指针,只要我们更改这个寄存器的值,就可以改变处理器的流水线,因为这个指针永远指向下一条指令的地址;事实上 uCOS-II 也是通过该方法在不同的任务之间切换实现时间片切分的,这部分我们之后再说。
需要注意的是,在 uCOS-II 中定义的任务函数的参数一定要是 void*
类型的,这个指针其实是一个万金油,有编程基础的朋友应该知道我们可以将其转换为任意类型的指针,这样某一个固定的函数配合不同的参数就可以实现不同的功能。
任务状态
uCOS-II 的任务有几种状态:
- 休眠态:简单地理解为任务的代码已经被编译,但不受操作系统调度
- 就绪态:任务已经准备运行,但是有更高级别的任务正在运行,它只能等待
- 运行态:任务得到了 CPU 的使用权,正在运行
- 挂起态:任务在等待某一个信号源/某一事件的发生,这是任务代码向操作系统请求的等待
- 中断态:任务执行过程中 ISR 抢夺了 CPU 的使用权
实际上整个操作系统的框架就像是一个FSM,OS通过对任务的状态转换实现调度。
任务优先级
对于 uCOS-II 来说,最多可以管理 64 个任务,每一个任务都需要对应一个独一无二的优先级;那么相应的,任务的编号 - 这里可以类比为其他 OS 的 PID,就可以看成是该任务的优先级。
事实上我们也许不需要那么多的任务,这个任务数量可以在 os_cfg.h
文件中通过宏去定义:
1 |
注意,当我们确定了最大任务数量之后,建议不要使用优先级为:0 - 4、OS_LOWEST_PRIO - OS_LOWEST_PRIO - 3 这些优先级,原因是他们已经/有可能被系统的某系关键服务所占用,这个我们在后面会说到。
在系统调度器工作时,它会从所有的任务中选取处于就绪态且 优先级最高 的任务进行运行,所以在安排多个任务时,需要仔细考虑他们的优先级安排。
任务堆栈
在多任务的 OS 中还有一个很重要的事情就是区分不同任务的堆栈:
- 当前正在运行任务 A,任务 B 处于就绪态,并且 Priority 高于任务 A
- 在下一次任务调度时,任务 B 抢夺了任务 A 的时间片
- 如果这个时候不更新
SP
指针,任务 B 很有可能会在任务 A 刚刚操作过的内存区域进行复写,于是任务 A 的数据丢失
而在 uCOS-II 中可以静态/动态的创建堆栈;只是在动态堆栈创建时需要注意内存碎片的问题,并且在 uCOS 系统中提供了一个 OSTaskStkChk
函数用以检查任务是继续要的堆栈大小,这些问题我们放在之后内存管理的时候再讲。
需要注意的是,这里的堆栈并不能实现类似“进程内存隔离”这样的功能,它存在的目的仅仅是为了让不同任务操作的内存区域隔离开。
任务控制块 TCB
有了上面的这些概念之后我们就可以介绍最重要的一个概念:任务控制块 TCB。
事实上,所有关于任务的信息,包括上面提到的优先级、堆栈、任务代码地址、以及后面我们要接触的消息队列等等,这些信息都存储在任务控制块里;在创建任务时,OS 会为每一个任务创建一个 TCB,并且这些 TCB 将组成一个双联表的数据结构。
当发生任务调度时,系统会检查这个 TCB 组成的双链表,用以判断接下来需要运行的任务,并且为任务恢复上下文;在现在这个阶段,我们只介绍 TCB 结构体中最简单的一些成员变量,后期在介绍其他功能时,我们在向其中添加相对应的成员值。
1 | typedef struct os_tcb { |
其实最基础的 TCB 组成就是这些:
OSTCBNext, OSTCBPrev
- 用以组成双链表OSTCBStckPtr
- 存储任务栈底OSTCBDly
- 保存任务延迟时间OSTCBStat
- 任务状态OSTCBPrio
- 任务优先级
实际应用中,我们还会用到 TCB 的其他信息,这也体现出了 uCOS 良好的可剪裁性与可拓展性。
Comments