位图技术在嵌入式任务管理中的应用
介绍位图(Bitmap)在嵌入式任务管理中的原理、实现与典型应用模式。
一、背景与动机
嵌入式系统中常有多种「布尔标志」需求:
- 待处理任务:事件到达时需标记「待执行」,周期任务检查后驱动
- 功能开关/抑制:某些场景下需临时关闭部分功能
- 错误状态:各类错误或异常标记
- 运行状态:模块是否已初始化、是否就绪等
- 调试开关:运行检查、调试输出等
若每种标志用 bool 或 enum 单独变量,字段会增多、接口分散;若用数组或结构体,则占用大、操作不便。位图 将多个布尔标志 packed 进一个整数,每个 bit 表示一种状态:空间紧凑、接口统一、批量判断方便。
二、位图原理
2.1 基本思想
用一个整数(如 uint32_t)的各个 bit 表示一组布尔值:bit 0 对应标志 0,bit 1 对应标志 1,以此类推。置位、清零、检查都通过位运算完成,O(1)。
2.2 基础实现
c
typedef uint32_t bitmap_t;
#define bitmap_zero 0x0u
#define bitmap_v(bitpos) (0x1u << (bitpos))
#define bitmap_set(b, mask) do { (b) |= (mask); } while (0)
#define bitmap_clr(b, mask) do { (b) &= (~(mask)); } while (0)
#define bitmap_chk(b, mask) ((b) & (mask))
| 宏 | 作用 |
|---|---|
bitmap_zero |
空位图,所有位为 0 |
bitmap_v(n) |
生成第 n 位的掩码(如 bitmap_v(0) = 0x01) |
bitmap_set |
置位 |
bitmap_clr |
清零 |
bitmap_chk |
检查是否置位(非零即真) |
2.3 容量
bitmap_t 为 uint32_t 时,单个位图最多表示 32 个独立标志。用 uint64_t 可扩展到 64 个;用数组可扩展到任意位数。
2.4 组合掩码
多个标志可合并为一个掩码,一次操作多个位:
c
#define JOB_A bitmap_v(0)
#define JOB_B bitmap_v(1)
#define JOB_AB (JOB_A | JOB_B) /* 组合掩码 */
bitmap_set(curJobs, JOB_AB); /* 同时置位 A 和 B */
bitmap_clr(curJobs, JOB_AB); /* 同时清零 A 和 B */
if (bitmap_chk(curJobs, JOB_A)) /* 检查 A 是否置位 */
...
三、典型应用场景
3.1 Job 调度位图
语义:表示当前有哪些 Job 待执行。多个 Job 可同时置位。
c
#define JOB_NONE bitmap_zero
#define JOB_SYNC bitmap_v(0)
#define JOB_CMD bitmap_v(1)
#define JOB_UPGRADE bitmap_v(2)
bitmap_t curJobs;
void on_event(void) {
bitmap_set(curJobs, JOB_SYNC); /* 事件回调:置位 */
}
void main_proc(void) {
if (bitmap_chk(curJobs, JOB_SYNC)) {
if (do_sync_proc() == DONE)
bitmap_clr(curJobs, JOB_SYNC); /* 周期 Proc:完成后清零 */
}
}
int can_shutdown(void) {
return curJobs == JOB_NONE; /* 批量检查:是否全部完成 */
}
3.2 功能抑制位图
语义 :当前被抑制的功能。业务逻辑通过 bitmap_chk 判断是否执行。
3.3 错误/运行/调试位图
语义:错误状态、运行标志、调试开关等,用法同上:Set/Clr/Chk。
3.4 与事件-周期分离的配合
位图天然适合「事件与周期分离」:事件回调只做 Set,周期任务里 Chk 并执行逻辑,完成后 Clr。这样回调快速返回,周期任务按固定顺序检查,逻辑集中、时序可控。
3.5 完整示例
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
/* ============================================================================
* 第一部分:位图基础操作宏定义
* ============================================================================ */
typedef uint32_t bitmap_t;
#define BITMAP_ZERO 0x0u
#define BITMAP_V(n) (0x1u << (n))
#define BITMAP_SET(b, m) do { (b) |= (m); } while(0)
#define BITMAP_CLR(b, m) do { (b) &= (~(m)); } while(0)
#define BITMAP_CHK(b, m) ((b) & (m))
#define BITMAP_IS_ZERO(b) ((b) == BITMAP_ZERO)
/* ============================================================================
* 第二部分:业务相关位掩码定义
* ============================================================================ */
/* 任务位掩码定义 */
#define TASK_NONE BITMAP_ZERO
#define TASK_SYNC BITMAP_V(0) /* 同步任务 */
#define TASK_CMD BITMAP_V(1) /* 命令处理任务 */
#define TASK_UPGRADE BITMAP_V(2) /* 升级任务 */
#define TASK_DIAG BITMAP_V(3) /* 诊断任务 */
#define TASK_LOG BITMAP_V(4) /* 日志任务 */
#define TASK_CALIB BITMAP_V(5) /* 校准任务 */
#define TASK_ALL (TASK_SYNC | TASK_CMD | TASK_UPGRADE | TASK_DIAG | \
TASK_LOG | TASK_CALIB)
/* 全局任务位图 */
static volatile bitmap_t g_pending_tasks = BITMAP_ZERO;
/* ============================================================================
* 第三部分:业务层接口封装
* ============================================================================ */
/* 任务状态查询 */
#define Task_Is_Pending(task) BITMAP_CHK(g_pending_tasks, task)
#define Task_Is_None() BITMAP_IS_ZERO(g_pending_tasks)
/* 任务激活(事件回调中使用) */
#define Task_Activate(task) BITMAP_SET(g_pending_tasks, task)
/* 任务完成(任务处理完成后调用) */
#define Task_Complete(task) BITMAP_CLR(g_pending_tasks, task)
/* ============================================================================
* 第四部分:模拟业务函数
* ============================================================================ */
static int do_sync_processing(void)
{
printf(" [执行] 同步处理...\n");
return 0; /* 返回0表示完成 */
}
static int do_command_processing(void)
{
printf(" [执行] 命令处理...\n");
return 0;
}
static int do_upgrade_processing(void)
{
printf(" [执行] 升级任务...\n");
return 0;
}
static int do_diagnostics(void)
{
printf(" [执行] 诊断任务...\n");
return 0;
}
static int do_logging(void)
{
printf(" [执行] 日志任务...\n");
return 0;
}
static int do_calibration(void)
{
printf(" [执行] 校准任务...\n");
return 0;
}
/* ============================================================================
* 第五部分:任务调度器
* ============================================================================ */
void task_scheduler(void)
{
printf("\n========== 任务调度器开始 ==========\n");
/* 按优先级顺序检查并处理任务 */
/* 1. 同步任务(最高优先级) */
if (Task_Is_Pending(TASK_SYNC)) {
printf("-> 处理同步任务\n");
if (do_sync_processing() == 0) {
Task_Complete(TASK_SYNC);
printf(" 同步任务完成,已清除\n");
}
}
/* 2. 命令处理任务 */
if (Task_Is_Pending(TASK_CMD)) {
printf("-> 处理命令任务\n");
if (do_command_processing() == 0) {
Task_Complete(TASK_CMD);
printf(" 命令任务完成,已清除\n");
}
}
/* 3. 升级任务 */
if (Task_Is_Pending(TASK_UPGRADE)) {
printf("-> 处理升级任务\n");
if (do_upgrade_processing() == 0) {
Task_Complete(TASK_UPGRADE);
printf(" 升级任务完成,已清除\n");
}
}
/* 4. 诊断任务 */
if (Task_Is_Pending(TASK_DIAG)) {
printf("-> 处理诊断任务\n");
if (do_diagnostics() == 0) {
Task_Complete(TASK_DIAG);
printf(" 诊断任务完成,已清除\n");
}
}
/* 5. 日志任务 */
if (Task_Is_Pending(TASK_LOG)) {
printf("-> 处理日志任务\n");
if (do_logging() == 0) {
Task_Complete(TASK_LOG);
printf(" 日志任务完成,已清除\n");
}
}
/* 6. 校准任务(最低优先级) */
if (Task_Is_Pending(TASK_CALIB)) {
printf("-> 处理校准任务\n");
if (do_calibration() == 0) {
Task_Complete(TASK_CALIB);
printf(" 校准任务完成,已清除\n");
}
}
/* 检查是否所有任务都完成 */
if (Task_Is_None()) {
printf("========== 所有任务处理完成 ==========\n");
} else {
printf("-------- 未处理的任务: 0x%08x --------\n", g_pending_tasks);
}
}
/* ============================================================================
* 第六部分:模拟事件回调(中断或事件驱动场景)
* ============================================================================ */
void on_can_receive_event(void)
{
/* 模拟CAN收到同步请求 */
printf("[事件] CAN同步请求到达\n");
Task_Activate(TASK_SYNC);
}
void on_command_received(void)
{
/* 模拟收到应用命令 */
printf("[事件] 收到命令\n");
Task_Activate(TASK_CMD);
}
void on_upgrade_request(void)
{
/* 模拟收到升级请求 */
printf("[事件] 收到升级请求\n");
Task_Activate(TASK_UPGRADE);
}
void on_diagnostic_trigger(void)
{
/* 模拟触发诊断 */
printf("[事件] 触发诊断\n");
Task_Activate(TASK_DIAG);
}
void on_log_trigger(void)
{
/* 模拟触发日志 */
printf("[事件] 触发日志\n");
Task_Activate(TASK_LOG);
}
void on_calibration_trigger(void)
{
/* 模拟触发校准 */
printf("[事件] 触发校准\n");
Task_Activate(TASK_CALIB);
}
/* ============================================================================
* 第七部分:主函数演示
* ============================================================================ */
int main(void)
{
printf("======================================\n");
printf(" 嵌入式位图任务管理系统演示\n");
printf("======================================\n");
/* 初始化:清除所有任务标志 */
g_pending_tasks = BITMAP_ZERO;
printf("初始化: 清除所有任务标志\n");
printf("初始状态: 0x%08x\n", g_pending_tasks);
/* 场景1:多个事件同时触发 */
printf("\n----- 场景1: 多个事件同时触发 -----\n");
on_can_receive_event(); /* 触发同步 */
on_command_received(); /* 触发命令 */
on_upgrade_request(); /* 触发升级 */
printf("设置标志后: 0x%08x\n", g_pending_tasks);
task_scheduler();
/* 场景2:连续触发多个任务 */
printf("\n----- 场景2: 连续触发多个任务 -----\n");
on_diagnostic_trigger(); /* 触发诊断 */
on_log_trigger(); /* 触发日志 */
on_calibration_trigger(); /* 触发校准 */
printf("设置标志后: 0x%08x\n", g_pending_tasks);
task_scheduler();
/* 场景3:检查特定任务状态 */
printf("\n----- 场景3: 查询特定任务状态 -----\n");
on_command_received(); /* 再次触发命令 */
printf("命令任务挂起: %s\n", Task_Is_Pending(TASK_CMD) ? "是" : "否");
printf("升级任务挂起: %s\n", Task_Is_Pending(TASK_UPGRADE) ? "是" : "否");
printf("当前任务位图: 0x%08x\n", g_pending_tasks);
task_scheduler();
/* 场景4:判断是否可以进入低功耗 */
printf("\n----- 场景4: 检查是否可以休眠 -----\n");
on_can_receive_event(); /* 再触发一个任务 */
printf("当前任务: 0x%08x\n", g_pending_tasks);
printf("是否可以休眠: %s\n", Task_Is_None() ? "是" : "否");
task_scheduler();
printf("\n======================================\n");
printf(" 演示结束\n");
printf("======================================\n");
return 0;
}
四、封装模式
4.1 定义位掩码
为每个标志定义宏,避免魔法数字:
c
#define JOB_NONE bitmap_zero
#define JOB_A bitmap_v(0)
#define JOB_B bitmap_v(1)
#define JOB_C bitmap_v(2)
4.2 封装操作宏
将底层 bitmap_set/clr/chk 和具体位图变量封装成业务接口:
c
#define JobSet(job) bitmap_set(gJobs, job)
#define JobClr(job) bitmap_clr(gJobs, job)
#define JobChk(job) bitmap_chk(gJobs, job)
业务代码只调用 JobSet/JobClr/JobChk,不直接操作 gJobs。
五、与其它方案对比
| 方案 | 32 个标志的空间 | 批量检查(如「是否全部完成」) |
|---|---|---|
| 32 个 bool | 32 字节 | 需逐项判断 |
| 32 个 enum | 32--128 字节 | 不适合同存多种状态 |
| 单个 uint32_t 位图 | 4 字节 | bitmap == 0 即可 |
位图在空间 和批量判断上优势明显。
六、流程示意
资源释放检查
bitmap == 0
允许 Shutdown
周期 Proc
是
是
否
BitmapChk
置位?
执行逻辑
完成?
BitmapClr
跳过
事件回调
收到事件
BitmapSet
七、移植与扩展
- 扩展位数 :
uint64_t或uint32_t[N]数组,需实现多字版本的 set/clr/chk 及「是否全零」判断。 - 并发 :多任务访问同一
bitmap_t时,需加锁或使用原子操作(如__sync_or_and_fetch)。 - 初始化 :启动时将所有位图置为
bitmap_zero。 - 命名 :掩码可用
xxx_v(n),操作可用xxx_Set/Clr/Chk等,保持统一。
八、使用注意
- 位号范围 :
bitmap_v(n)中 n 应在 [0, 31](uint32_t),否则未定义或需自己保证不溢出。 - 掩码用常量 :建议用宏定义掩码,避免运行时变量做移位导致未定义行为(如
bitmap_v(x)中 x ≥ 32)。 - chk 的返回值 :
bitmap_chk返回的是与运算结果,非 0 即真;若需严格 0/1,可写!!bitmap_chk(b, mask)。 - 多任务:多任务访问同一位图时须加锁或原子操作,避免竞态。
九、适用与不适用
| 适用 | 不适用 |
|---|---|
| 大量独立布尔标志、需批量判断 | 每个标志需多状态(如 0/1/2) |
| 事件置位、周期检查并清零 | 需要 FIFO 或优先级队列 |
| 资源紧张、追求紧凑 | 标志数量很少且不扩展,用 bool 即可 |
位图思想在 Linux 内核(CPU 掩码、IRQ 掩码等)及各类嵌入式系统中广泛使用,是高效表示布尔集合的通用手段。