uCOS 原理 - 任务概览

tech

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
2
3
4
5
void task(void* pdata) {
while (true) {
/* Working code */
}
}

那么问题就来了,既然每个任务都是无穷循环,如何从一个任务跳转到另一个任务中呢?

不知道大家是否还记得 Cortex-M3 中的 PC 指针,只要我们更改这个寄存器的值,就可以改变处理器的流水线,因为这个指针永远指向下一条指令的地址;事实上 uCOS-II 也是通过该方法在不同的任务之间切换实现时间片切分的,这部分我们之后再说。

需要注意的是,在 uCOS-II 中定义的任务函数的参数一定要是 void* 类型的,这个指针其实是一个万金油,有编程基础的朋友应该知道我们可以将其转换为任意类型的指针,这样某一个固定的函数配合不同的参数就可以实现不同的功能。

任务状态

uCOS-II 的任务有几种状态:

  • 休眠态:简单地理解为任务的代码已经被编译,但不受操作系统调度
  • 就绪态:任务已经准备运行,但是有更高级别的任务正在运行,它只能等待
  • 运行态:任务得到了 CPU 的使用权,正在运行
  • 挂起态:任务在等待某一个信号源/某一事件的发生,这是任务代码向操作系统请求的等待
  • 中断态:任务执行过程中 ISR 抢夺了 CPU 的使用权

实际上整个操作系统的框架就像是一个FSM,OS通过对任务的状态转换实现调度。

任务优先级

对于 uCOS-II 来说,最多可以管理 64 个任务,每一个任务都需要对应一个独一无二的优先级;那么相应的,任务的编号 - 这里可以类比为其他 OS 的 PID,就可以看成是该任务的优先级。

事实上我们也许不需要那么多的任务,这个任务数量可以在 os_cfg.h 文件中通过宏去定义:

1
#define OS_LOWEST_PRIO           20u

注意,当我们确定了最大任务数量之后,建议不要使用优先级为: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
2
3
4
5
6
7
8
9
10
11
12
typedef struct os_tcb {

struct os_tcb *OSTCBNext;
struct os_tcb *OSTCBPrev;

OS_STK *OSTCBStckPtr;

INT16U OSTCBDly;
INT8U OSTCBStat;
INT8U OSTCBPrio;

} OS_TCB;

其实最基础的 TCB 组成就是这些:

  • OSTCBNext, OSTCBPrev - 用以组成双链表
  • OSTCBStckPtr - 存储任务栈底
  • OSTCBDly - 保存任务延迟时间
  • OSTCBStat - 任务状态
  • OSTCBPrio - 任务优先级

实际应用中,我们还会用到 TCB 的其他信息,这也体现出了 uCOS 良好的可剪裁性与可拓展性。

Author: 桂小方

Permalink: https://init.blog/ucos-task-preview/

文章许可协议:

如果你觉得文章对你有帮助,可以 支持我

Comments