4.2.1 任务状态机
4.2.1 任务状态机
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 任务控制块(TCB)基本概念
🎯 学习目标
- 理解 OneOS 中 7 种任务状态的含义
- 掌握任务状态之间的转换规则
- 能够通过状态位掩码判断任务所处的状态组合
一、任务状态定义
OneOS-Lite 定义了 7 种任务状态(外加 EMPTY 空状态),定义在 kernel/include/os_task.h 中:
#define OS_TASK_STATE_EMPTY 0x0000U /* 空状态(未使用) */
#define OS_TASK_STATE_INIT 0x0001U /* 初始化状态 */
#define OS_TASK_STATE_READY 0x0002U /* 就绪状态(等待调度) */
#define OS_TASK_STATE_RUNNING 0x0004U /* 运行状态(当前正在执行) */
#define OS_TASK_STATE_SLEEP 0x0008U /* 睡眠状态(延时等待) */
#define OS_TASK_STATE_BLOCK 0x0010U /* 阻塞状态(等待资源) */
#define OS_TASK_STATE_SUSPEND 0x0020U /* 挂起状态(手动暂停) */
#define OS_TASK_STATE_CLOSE 0x8000U /* 关闭状态(任务结束) */
#define OS_TASK_STATE_MASK 0xFFFFU /* 任务状态掩码 */各状态采用位掩码(bitmask)方式定义,这意味着一个任务可以同时处于多个状态的组合(例如:任务可以在 SLEEP 状态的同时被 SUSPEND)。
各状态详解
| 状态 | 掩码 | 描述 |
|---|---|---|
| EMPTY | 0x0000 | 空状态,TCB 尚未被使用 |
| INIT | 0x0001 | 初始化状态,任务已创建但尚未启动(os_task_create() 调用后) |
| READY | 0x0002 | 就绪状态,任务已准备好运行,等待调度器分配 CPU |
| RUNNING | 0x0004 | 运行状态,任务正在 CPU 上执行(同一时刻只有一个任务处于此状态) |
| SLEEP | 0x0008 | 睡眠状态,任务因延时调用(os_task_tsleep/os_task_msleep)而主动睡眠 |
| BLOCK | 0x0010 | 阻塞状态,任务因等待某个 IPC 资源(信号量、互斥锁、消息队列等)而被阻塞 |
| SUSPEND | 0x0020 | 挂起状态,任务被手动挂起(os_task_suspend()),可与 SLEEP/BLOCK 叠加 |
| CLOSE | 0x8000 | 关闭状态,任务即将被销毁,等待资源回收 |
二、任务状态转换图
下面是完整的任务状态转换图(ASCII 艺术图):
os_task_create()
│
▼
┌──────────────────┐
│ INIT │
│ (0x0001) │
└───────┬──────────┘
│ os_task_startup()
▼
┌──────────────────────────────────────────┐
│ READY │
│ (0x0002) │
└────┬──────────┬──────────┬───────────────┘
│ │ │
os_task_ │ 调度器选中│ │ os_task_suspend()
resume() │ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────────────┐
│ SUSPEND │ │ RUNNING │ │ SUSPEND + READY │
│ (0x0020) │ │ (0x0004) │ │ (0x0022) ←─── 被恢复
└──────┬──────┘ └─┬──┬──┬──┘ └──────────────────┘
│ │ │ │
│ 被抢占 │ │ └── os_task_tsleep/msleep()
│ 或时间片到│ │
▼ │ ▼
┌─────────────┐ │ ┌──────────────────┐
│ SUSPEND + │ │ │ SLEEP │
│ READY │◄─────┘ │ (0x0008) │
│ (0x0022) │ └────────┬─────────┘
└─────────────┘ │ 延时到期
▼
┌──────────────┐
│ READY │
│ (0x0002) │
└──────────────┘
等待 IPC 资源(信号量、互斥锁等):
RUNNING ──os_sem_wait()等──► BLOCK (0x0010)
BLOCK ──资源可用──────────► READY (0x0002)
任务销毁:
RUNNING ──os_task_destroy()──► CLOSE (0x8000) ──► 资源回收 ──► EMPTY
挂起叠加(SUSPEND 可与 SLEEP/BLOCK 组合):
SLEEP ──os_task_suspend()──► SLEEP + SUSPEND (0x0028)
BLOCK ──os_task_suspend()──► BLOCK + SUSPEND (0x0030)三、状态转换规则详解
3.1 INIT → READY
- 触发条件:调用
os_task_startup() - 说明:任务创建后处于 INIT 状态,必须调用
os_task_startup()将其加入就绪队列才能被调度执行
3.2 READY → RUNNING
- 触发条件:调度器选中该任务(它是当前最高优先级的就绪任务)
- 说明:就绪任务被调度器选中后获得 CPU 使用权,状态变为 RUNNING
3.3 RUNNING → READY
- 触发条件:
- 被更高优先级任务抢占
- 时间片用完(同优先级轮转调度)
- 主动调用
os_task_yield()让出 CPU
- 说明:任务失去 CPU 使用权但仍在就绪状态,等待下次调度
3.4 RUNNING → SLEEP
- 触发条件:调用
os_task_tsleep(tick)或os_task_msleep(ms) - 说明:任务主动进入睡眠状态,内核将其从就绪队列移除,加入延时队列。延时到期后自动回到 READY 状态
3.5 RUNNING → BLOCK
- 触发条件:任务尝试获取不可用的 IPC 资源(如信号量 count=0、互斥锁被占用等)
- 说明:任务从就绪队列移除,加入对应 IPC 对象的阻塞等待队列。当资源可用时自动回到 READY 状态
3.6 RUNNING/READY/SLEEP/BLOCK → SUSPEND
- 触发条件:其他任务(或自身)调用
os_task_suspend() - 说明:
- 如果任务在 READY 状态:从就绪队列移除,状态变为
SUSPEND - 如果任务在 SLEEP 状态:保留 SLEEP 状态,叠加
SUSPEND(状态变为SLEEP | SUSPEND) - 如果任务在 BLOCK 状态:保留 BLOCK 状态,叠加
SUSPEND(状态变为BLOCK | SUSPEND) - 如果任务在 RUNNING 状态(自己挂起自己):立即触发调度
- 如果任务在 READY 状态:从就绪队列移除,状态变为
3.7 SUSPEND → READY
- 触发条件:调用
os_task_resume() - 说明:
- 如果任务仅处于
SUSPEND状态:清除 SUSPEND,转为 READY 状态 - 如果任务处于
SLEEP | SUSPEND状态:仅清除 SUSPEND,保留 SLEEP(等延时到期后自动回到 READY) - 如果任务处于
BLOCK | SUSPEND状态:仅清除 SUSPEND,保留 BLOCK(等资源可用后自动回到 READY)
- 如果任务仅处于
3.8 RUNNING → CLOSE
- 触发条件:调用
os_task_destroy()或任务入口函数返回 - 说明:任务进入关闭状态,内核的回收任务(recycle-task)会负责执行清理回调和释放资源
四、状态掩码的组合使用
4.1 位掩码的设计思想
OneOS-Lite 的任务状态字段 state 是一个 uint16_t 类型的位掩码(bitmask),每个状态位使用一个独立的二进制位。这样设计的好处是:
- 可以叠加:一个任务可以同时处于多个状态(如
SLEEP | SUSPEND、BLOCK | SUSPEND) - 判断高效:通过一次位运算(
&)就能判断某个状态是否存在,无需遍历 - 内核源码中大量使用:内核在状态转换时频繁使用
&= ~(清除状态位)和|=(置位状态位)
回顾前面的状态定义(各状态位均为 2 的幂,即一个独立的 bit):
| 宏 | 二进制位 | 十六进制 |
|---|---|---|
OS_TASK_STATE_EMPTY | 0000 0000 0000 0000 | 0x0000 |
OS_TASK_STATE_INIT | 0000 0000 0000 0001 | 0x0001 |
OS_TASK_STATE_READY | 0000 0000 0000 0010 | 0x0002 |
OS_TASK_STATE_RUNNING | 0000 0000 0000 0100 | 0x0004 |
OS_TASK_STATE_SLEEP | 0000 0000 0000 1000 | 0x0008 |
OS_TASK_STATE_BLOCK | 0000 0000 0001 0000 | 0x0010 |
OS_TASK_STATE_SUSPEND | 0000 0000 0010 0000 | 0x0020 |
OS_TASK_STATE_CLOSE | 1000 0000 0000 0000 | 0x8000 |
正因为每个状态占用独立的位,所以 SUSPEND 位可以与 READY / SLEEP / BLOCK 位并存,形成状态组合(如 0x0002 | 0x0020 = 0x0022 表示"就绪 + 挂起")。
4.2 位运算基础
判断状态组合主要用到以下 3 种位运算:
| 运算符 | 含义 | 用途 |
|---|---|---|
&(按位与) | 对应位都为 1 才为 1 | 判断是否包含某个状态位 |
|(按位或) | 对应位有 1 则为 1 | 合并多个状态位 |
~(按位取反) | 0 变 1,1 变 0 | 清除某个状态位(常与 & 联用) |
/* 状态转换时内核内部的典型用法(摘自 os_task.c) */
task->state &= ~OS_TASK_STATE_READY; /* 清除 READY 位 */
task->state |= OS_TASK_STATE_SUSPEND; /* 置位 SUSPEND 位 */4.3 组合状态判断的四种基本写法
获取任务状态:
#include <os_task.h>
uint16_t status;
os_task_id tid = ...; /* 目标任务 ID */
os_task_get_state(tid, &status); /* 将任务状态读入 status */写法一:判断"是否包含"某状态(只要有该位即可)
/* 判断任务是否处于就绪状态(无论是否同时挂起) */
if (status & OS_TASK_STATE_READY)
{
/* 任务就绪 */
}计算过程:
假设 status 的值为 0x0022(即 READY | SUSPEND,二进制 0010 0010),OS_TASK_STATE_READY 为 0x0002(二进制 0000 0010):
status 0x0022 → 0010 0010
& OS_TASK_STATE_READY 0x0002 → 0000 0010
────────────────────────────────────────────
结果 → 0000 0010 (非 0,条件成立)status & OS_TASK_STATE_READY 按位与后只保留了 READY 对应的那一位。由于 READY 位为 1,结果为 0x0002(非 0),条件成立。即使 status 还有其他状态位(如 SUSPEND),也不影响该判断。
再看一个反例:假设 status = 0x0008(纯 SLEEP,二进制 0000 1000):
status 0x0008 → 0000 1000
& OS_TASK_STATE_READY 0x0002 → 0000 0010
────────────────────────────────────────────
结果 → 0000 0000 (= 0,条件不成立)由于 READY 位为 0,结果为 0,条件不成立。注意这里 0x0008 & 0x0002 每一位都不同时为 1,所以结果是 0,而不是两者相减——这正是按位与与算术运算的本质区别。
同理,判断是否被挂起:
/* 判断任务是否被挂起(无论底层处于什么状态) */
if (status & OS_TASK_STATE_SUSPEND)
{
/* 任务被挂起 */
}假设 status = 0x0028(即 SLEEP | SUSPEND,二进制 0010 1000):
status 0x0028 → 0010 1000
& OS_TASK_STATE_SUSPEND 0x0020 → 0010 0000
────────────────────────────────────────────
结果 → 0010 0000 (非 0,条件成立)原理:status & OS_TASK_STATE_READY 只会保留 status 中 READY 对应位的内容。只要 READY 位为 1,结果非 0,条件成立;其他状态位不影响该判断。这用于"只要包含这一状态就算"的场景。
写法二:判断"恰好等于"某个状态(严格匹配,不允许其他位存在)
/* 判断任务是否恰好处于纯挂起状态(仅 SUSPEND,无其他状态位) */
if (status == OS_TASK_STATE_SUSPEND)
{
/* 任务仅处于 SUSPEND,没有任何叠加状态 */
}计算过程:
status 的值 | 二进制 | status == 0x0020? | 说明 |
|---|---|---|---|
0x0020 | 0010 0000 | ✔ 成立 | 纯 SUSPEND |
0x0022 | 0010 0010 | ✘ 不成立 | READY | SUSPEND,多出 READY 位 |
0x0028 | 0010 1000 | ✘ 不成立 | SLEEP | SUSPEND,多出 SLEEP 位 |
再如判断是否恰好处于纯就绪状态:
/* 判断任务是否恰好处于纯就绪状态(仅 READY) */
if (status == OS_TASK_STATE_READY)
{
/* 任务仅处于 READY */
}status 的值 | 二进制 | status == 0x0002? | 说明 |
|---|---|---|---|
0x0002 | 0000 0010 | ✔ 成立 | 纯 READY |
0x0022 | 0010 0010 | ✘ 不成立 | 多出 SUSPEND 位 |
0x0004 | 0000 0100 | ✘ 不成立 | RUNNING(值不同) |
原理:直接比较 status 与目标值是否完全相等。只要存在任何多余的状态位(如 SUSPEND | READY)或值不同(如 RUNNING),判断即不成立。这用于"必须精确等于这一状态"的场景(例如前面状态转换图中 if (OS_TASK_STATE_SUSPEND == task->state) 的判断)。
写法三:判断"同时包含"多个状态(要求这些位全为 1)
/* 判断任务是否同时处于"睡眠 + 挂起"状态 */
if ((status & (OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND)) ==
(OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND))
{
/* 任务同时处于 SLEEP 和 SUSPEND */
}计算过程:
先计算掩码 OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND:
OS_TASK_STATE_SLEEP 0x0008 → 0000 1000
| OS_TASK_STATE_SUSPEND 0x0020 → 0010 0000
────────────────────────────────────────────
掩码 0x0028 → 0010 1000假设 status = 0x0030(即 BLOCK | SUSPEND,二进制 0011 0000)。用掩码过滤后:
status 0x0030 → 0011 0000
& 掩码 0x0028 → 0010 1000
────────────────────────────────────────────
结果 → 0010 0000此时 结果 0x0020 ≠ 掩码 0x0028,条件不成立——因为任务在 BLOCK 而非 SLEEP,SLEEP 位为 0。
再假设 status = 0x0038(即 SLEEP | BLOCK | SUSPEND,二进制 0011 1000,任务同时被阻塞、睡眠又被挂起):
status 0x0038 → 0011 1000
& 掩码 0x0028 → 0010 1000
────────────────────────────────────────────
结果 → 0010 1000 (= 掩码 0x0028,条件成立)因为 SLEEP 位和 SUSPEND 位都为 1,过滤后结果恰好等于掩码 0x0028,条件成立。即使任务还多出 BLOCK 等其他状态位,也不影响该判断。
同样地,判断"阻塞 + 挂起":
/* 判断任务是否同时处于"阻塞 + 挂起"状态 */
if ((status & (OS_TASK_STATE_BLOCK | OS_TASK_STATE_SUSPEND)) ==
(OS_TASK_STATE_BLOCK | OS_TASK_STATE_SUSPEND))
{
/* 任务同时处于 BLOCK 和 SUSPEND */
}掩码 OS_TASK_STATE_BLOCK | OS_TASK_STATE_SUSPEND:
OS_TASK_STATE_BLOCK 0x0010 → 0001 0000
| OS_TASK_STATE_SUSPEND 0x0020 → 0010 0000
────────────────────────────────────────────
掩码 0x0030 → 0011 0000假设 status = 0x0030(BLOCK | SUSPEND):
status 0x0030 → 0011 0000
& 掩码 0x0030 → 0011 0000
────────────────────────────────────────────
结果 → 0011 0000 (= 掩码 0x0030,条件成立)原理:先用掩码 OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND(即 0x0028)把无关位"过滤"掉,再判断过滤结果是否恰好等于目标组合。只要 SLEEP 和 SUSPEND 两个位都为 1,结果就是 0x0028,条件成立。此写法不关心是否还有其他状态位。
写法四:判断"不包含"某状态(用于排除)
/* 判断任务是否没有被挂起 */
if ((status & OS_TASK_STATE_SUSPEND) == 0)
{
/* 任务未被挂起 */
}计算过程:
假设 status = 0x0008(纯 SLEEP,二进制 0000 1000):
status 0x0008 → 0000 1000
& OS_TASK_STATE_SUSPEND 0x0020 → 0010 0000
────────────────────────────────────────────
结果 → 0000 0000 (= 0,条件成立:未挂起)因为 SUSPEND 位为 0,结果为 0,条件成立。若 status = 0x0028(SLEEP | SUSPEND):
status 0x0028 → 0010 1000
& OS_TASK_STATE_SUSPEND 0x0020 → 0010 0000
────────────────────────────────────────────
结果 → 0010 0000 (非 0,条件不成立:已挂起)原理:status & OS_TASK_STATE_SUSPEND 为 0 说明 SUSPEND 位为 0,即任务没有被挂起。内核中 os_task_resume() 就是用这种方式判断任务是否需要恢复(见 os_task.c 中 if ((task->state & OS_TASK_STATE_SUSPEND) == 0))。
4.4 完整示例:一个任务运行信息打印函数
综合运用上述写法,实现一个输出任务所处状态的函数:
#include <os_task.h>
#include <stdio.h>
void show_task_state(os_task_id tid)
{
uint16_t status;
os_task_get_state(tid, &status);
printf("Task status = 0x%04X\n", status);
/* 写法一:判断是否包含某状态 */
if (status & OS_TASK_STATE_INIT)
printf(" - INIT\n");
if (status & OS_TASK_STATE_READY)
printf(" - READY\n");
if (status & OS_TASK_STATE_RUNNING)
printf(" - RUNNING\n");
if (status & OS_TASK_STATE_SLEEP)
printf(" - SLEEP\n");
if (status & OS_TASK_STATE_BLOCK)
printf(" - BLOCK\n");
if (status & OS_TASK_STATE_SUSPEND)
printf(" - SUSPEND\n");
if (status & OS_TASK_STATE_CLOSE)
printf(" - CLOSE\n");
/* 写法三:判断组合状态 */
if ((status & (OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND)) ==
(OS_TASK_STATE_SLEEP | OS_TASK_STATE_SUSPEND))
printf(" -> 睡眠中被挂起(SLEEP | SUSPEND)\n");
if ((status & (OS_TASK_STATE_BLOCK | OS_TASK_STATE_SUSPEND)) ==
(OS_TASK_STATE_BLOCK | OS_TASK_STATE_SUSPEND))
printf(" -> 阻塞中被挂起(BLOCK | SUSPEND)\n");
}4.5 常见状态组合速查表
通过 os_task_get_state() 查询到 status 后,常见的状态组合及其含义如下:
status 十六进制 | 二进制 | 含义 |
|---|---|---|
0x0001 | 0001 | INIT:已创建未启动 |
0x0002 | 0010 | READY:就绪待调度 |
0x0004 | 0100 | RUNNING:正在运行 |
0x0008 | 1000 | SLEEP:延时睡眠 |
0x0010 | 0001 0000 | BLOCK:等待资源阻塞 |
0x0020 | 0010 0000 | SUSPEND:被挂起 |
0x0022 | 0010 0010 | READY | SUSPEND:就绪中被挂起 |
0x0028 | 0010 1000 | SLEEP | SUSPEND:睡眠中被挂起 |
0x0030 | 0011 0000 | BLOCK | SUSPEND:阻塞中被挂起 |
0x8000 | 1000 ... | CLOSE:关闭待回收 |
注意:
os_task_get_state()内部对 RUNNING 状态做了特殊处理——如果查询的是当前正在运行的任务,返回的status会是OS_TASK_STATE_RUNNING(0x0004),而不是内部实际的 READY 状态位。这是因为 RUNNING 任务在调度器内部的实际状态位是 READY(它仍在就绪队列中以便被再次调度),但为了方便外部用户查询,API 做了转换(见os_task.c中*status = OS_TASK_STATE_RUNNING;的逻辑)。因此在实际判断时:
- 若要判断"当前任务是否正在运行",应使用
status & OS_TASK_STATE_RUNNING;- 若使用
os_task_get_state()查询其他任务,得到的才是其真实的内部状态位(READY/SLEEP/BLOCK/SUSPEND 等),此时status中不会出现 RUNNING 位。
📝 本节小结
OneOS 任务状态机采用位掩码设计,定义了 7 种状态(INIT、READY、RUNNING、SLEEP、BLOCK、SUSPEND、CLOSE)。状态之间通过特定的 API 调用触发转换,SUSPEND 状态可以与其他状态叠加。理解任务状态机是掌握任务管理的基础,后续各节将详细介绍每个状态转换的具体 API 用法。