Skip to content

3.1 OSAL 的任务调度原理 ​

本节导读

总览页我们已经把 OSAL 的"全景图"看了一遍。这一节我们不满足于"知道",要钻进代码里,把任务调度这条链路逐行走一遍:main() → osal_init_system() → osal_start_system() → osal_run_system()。

放心,读不懂的地方我们都会用大白话翻译。

OSAL 是谁?一句话复习 ​

OSAL(Operating System Abstraction Layer,操作系统抽象层),可以通俗地理解为一个简化版的操作系统——为 Z-Stack 提供内存管理、中断管理和任务调度等基本功能。

生活类比:OSAL 就像小区物业,业主(各协议层)不用自己操心"谁先用水、垃圾怎么分类、电梯什么时候检修",物业全包了。

第一站:程序从 main() 开始 ​

Z-Stack 3.0 的 main() 函数在 ZMain 目录下的 ZMain.c 文件中:

ZMain.c 在工程中的位置

打开它,你会看到一长串初始化代码,就像婚礼前的一长串准备清单——关中断、初始化时钟、检查电压、初始化按键、初始化串口……每一步都有注释说明白:

c
int main(void) {
    osal_int_disable(INTS_ALL);   // 关闭所有中断(先静场)
    HAL_BOARD_INIT();             // 初始化板载资源(时钟等)
    zmain_vdd_check();            // 检查供电电压够不够
    InitBoard(OB_COLD);           // 初始化板载 I/O(按键配成输入)
    HalDriverInit();              // 初始化 HAL 驱动(串口、LED…)
    osal_nv_init(NULL);           // 初始化 NV(芯片内部 FLASH 空间)
    ZMacInit();                   // 初始化 MAC 层
    zmain_ext_addr();             // 确定芯片物理地址
    zgInit();                     // 初始化 NV 中的全局信息
    afInit();                     // 初始化 AF(射频)
    osal_init_system();           // ★ 初始化 OSAL(含任务池)★
    osal_int_enable(INTS_ALL);    // 使能所有中断(重新开麦)
    InitBoard(OB_READY);          // 再初始化一次板载资源
    zmain_dev_info();             // 在屏幕上显示设备信息
    osal_start_system();          // ★ 进入系统轮询(永不返回)★
    return 0;                     // 这句永远不会执行到
}

这一大串里,真正和任务调度有关的只有两行,其他的都是"铺床叠被":

c
osal_init_system();   // ① 初始化 OSAL(包括初始化任务池)
osal_start_system();  // ② 轮询任务池(注意注释:No Return from here!)

osal_start_system() 的注释"No Return from here"翻译过来就是:进了这扇门就别想回头了——程序的生命从此交给 OSAL 的轮询循环。

第二站:osal_init_system() —— 把"物业公司"搭起来 ​

这两个函数的定义在 OSAL 目录下的 OSAL.c 文件中:

OSAL.c 在工程中的位置

osal_init_system() 的代码(已简化)如下:

c
uint8 osal_init_system(void) {
    osal_mem_init();     // 初始化内存分配系统(仓库开业)
    osal_qHead = NULL;   // 初始化消息队列(信箱清空)
    osalTimerInit();     // 初始化 OSAL 定时器(闹钟就位)
    osal_pwrmgr_init();  // 初始化电源管理系统(电表装好)
    osalInitTasks();     // ★ 初始化任务池(各部门报名)★
    return (SUCCESS);
}

每一行都是在给"物业公司"添置设备,其中最关键的是最后那个 osalInitTasks()——任务池初始化,下一节 3.2 我们会专门拆它。

第三站:osal_start_system() —— 永不停歇的流水线 ​

c
void osal_start_system(void) {
    // ……(一些 ICall 相关初始化,先不管)……
    for (;;) {          // 主循环:永不停歇
        osal_run_system();  // ★ 系统轮询调度 ★
    }
}

就一个死循环,翻来覆去地调用 osal_run_system()。OSAL 的"心脏"其实就是这个函数。

第四站:osal_run_system() —— 轮询的核心逻辑 ​

osal_run_system() 的代码(为便于理解已简化):

c
void osal_run_system(void) {
    uint8 idx = 0;

    osalTimeUpdate();     // 更新时间,整理出到期的任务
    Hal_ProcessPoll();    // 硬件适配层中断查询

    do {
        if (tasksEvents[idx]) {  // 这个任务有待处理的事件?
            break;               // 有!锁定它,跳出循环
        }
    } while (++idx < tasksCnt);  // 没有?检查下一个任务

    if (idx < tasksCnt) {        // 找到了需要处理的任务
        uint16 events;
        HAL_ENTER_CRITICAL_SECTION();  // 关中断(保护现场)
        events = tasksEvents[idx];     // 取出待处理事件
        tasksEvents[idx] = 0;          // 清空任务中的事件
        HAL_EXIT_CRITICAL_SECTION();   // 开中断

        events = (tasksArr[idx])(idx, events);  // ★ 调事件处理函数 ★

        HAL_ENTER_CRITICAL_SECTION();
        tasksEvents[idx] |= events;   // 没处理完的事件放回去
        HAL_EXIT_CRITICAL_SECTION();
    }
}

重点看这个 do-while 循环,它是整个任务池轮询的精髓:

c
do {
    if (tasksEvents[idx]) {  // 有任务要处理?
        break;               // 有,跳出
    }
} while (++idx < tasksCnt);  // 没有,看下一个

do-while 循环:逐行走读 ​

行在干什么大白话
idx = 0从第 0 号任务开始查从名单第一个人开始点名
if (tasksEvents[idx])这个任务的"待办箱"里有没有事问他"你有事吗?"
有 → break锁定这个任务,跳出循环"你有事,就你了!"
没有 → ++idx换个任务继续查"没事?下一个!"
while (idx < tasksCnt)没查完就继续名单还没点完就接着点

两个关键数组:

  • tasksEvents:一个 uint16 数组,每个元素代表一个任务,里面用二进制位"装"着该任务待处理的事件——这就是任务池;
  • tasksCnt:任务池的大小,也就是一共有多少个任务。

循环结束后的判断:

  • 如果整个任务池都翻遍了也没找到事做,idx 会一路加到 >= tasksCnt,说明大家都在摸鱼;
  • 如果 idx < tasksCnt,说明第 idx 号任务有活干,tasksEvents[idx] 就是它的待处理事件。

生活类比:这就是秘书挨个看每个部门的工单箱——看到第一个有待办工单的部门,就敲门叫它处理。谁排前面谁先被照顾,优先级就是这么来的。

事件处理完,为什么要"放回去"? ​

注意这段:

c
events = (tasksArr[idx])(idx, events);   // 调用事件处理函数
tasksEvents[idx] |= events;              // 把"没处理完"的事件放回去

事件处理函数会返回一个值:处理完的事件会被清掉(用异或 ^),没处理完的会原样返回。OSAL 把没处理完的部分 |= 写回任务池,下轮轮询接着处理——就像秘书提醒"这事你还没办完,明天继续"。

任务与事件:任务里装着一堆"待办" ​

每个任务里可能包含一系列待处理的事情,这些待处理的事情通俗地称为事件(Event)。

例如应用层任务中可以包含:打开 LED 灯、关闭窗户、打开空调——这 3 个就是 3 个事件。

tasksEvents 的每个元素(一个 uint16 变量)代表一个任务,并且用二进制位储存了这个任务包含的事件集合。

一个 16 位的变量怎么"同时装下多个事件"?答案是用独热码(one-hot code):每个事件独占一个位,事件来了就把对应位拨到 1,多个事件一起来就多个位一起亮——细节我们在总览页已经讲过,这里不再重复。

小结 ​

函数职责一句话
main()各种初始化 + 两行关键调用婚礼前的一长串准备清单
osal_init_system()初始化内存/队列/定时器/电源 + 任务池把物业公司搭起来
osal_start_system()for(;;) 死循环调 osal_run_system()流水线开动,永不停止
osal_run_system()do-while 轮询任务池 + 调处理函数秘书逐个查工单箱

一句话总结:OSAL 的任务调度 = 初始化任务池 + 死循环轮询;轮询的核心是一个 do-while 循环,逐个检查 tasksEvents 数组,找到第一个"有事件"的任务就交给它的处理函数。谁排前面谁先干活——就这么简单。


内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·3.1 节,经本站整理优化以便于理解;原站链接

aioteer · 物联网专业课程群

💬AI 伴学配置