3.2 任务初始化与事件处理
本节导读
上一节我们看到了轮询循环:它拿 idx 去查 tasksEvents,查到了就调 tasksArr[idx]。但有两个问题还没回答:
tasksEvents、tasksArr这些"名单"是谁排的? —— 答案是osalInitTasks()。- 处理函数收到事件后,具体怎么干活的? —— 这一节我们把事件处理函数的"标准模板"拆开看。
原站本节内容已失效,本站按 Z-Stack 3.0 源码与教学实践重写,电路与例程以 GEC2530 开发板 + SampleSwitch 工程为准。
一、任务池是怎么"出生"的:osalInitTasks()
回想上一节,osal_init_system() 里最后一行是 osalInitTasks()。这个函数定义在 App 目录下的 OSAL_SampleSw.c 文件中:

void osalInitTasks(void) {
uint8 taskID = 0;
// ① 先给任务池"买地皮":申请 tasksEvents 数组
tasksEvents = (uint16 *)osal_mem_alloc(sizeof(uint16) * tasksCnt);
osal_memset(tasksEvents, 0, (sizeof(uint16) * tasksCnt));
// ② 逐层初始化任务,每初始化一层,taskID 加 1
macTaskInit(taskID++); // MAC 层任务
nwk_init(taskID++); // 网络层任务
gp_Init(taskID++); // 绿能 GP 层任务
Hal_Init(taskID++); // 硬件适配层任务
MT_TaskInit(taskID++); // MT 任务(调试用)
APS_Init(taskID++); // APS 层任务
ZDApp_Init(taskID++); // ZDO 任务
zcl_Init(taskID++); // ZCL 层任务
bdb_Init(taskID++); // BDB 层任务
zclSampleSw_Init(taskID++); // ★ 应用层任务(SampleSwitch)★
}它在干什么?两件事:
- 申请任务池内存:
tasksEvents数组就是任务池,先给它腾好地方、全部清零; - 逐层注册任务:Z-Stack 的每一层都来"报名",报一个名,
taskID就 +1,于是每层拿到一个独一无二的编号。
生活类比:这就是新公司开业,各部门来前台报到——MAC 部、网络部、HAL 部……前台给每个部门发一个工号(taskID),并把工号记在"待办登记表"(tasksEvents)上。
二、任务与处理函数的"电话本":tasksArr[]
光有"待办登记表"还不够——轮询查到某个部门有事,得知道该通知谁。这个"电话本"就是 tasksArr[]:
const pTaskEventHandlerFn tasksArr[] = {
macEventLoop, // 第 0 号:MAC 层事件处理函数
nwk_event_loop, // 第 1 号:网络层事件处理函数
gp_event_loop, // 第 2 号:GP 层事件处理函数
Hal_ProcessEvent, // 第 3 号:HAL 事件处理函数
MT_ProcessEvent, // 第 4 号:MT 事件处理函数
APS_event_loop, // 第 5 号:APS 事件处理函数
ZDApp_event_loop, // 第 6 号:ZDO 事件处理函数
zcl_event_loop, // 第 7 号:ZCL 事件处理函数
bdb_event_loop, // 第 8 号:BDB 事件处理函数
zclSampleSw_event_loop, // 第 9 号:应用层事件处理函数
};关键:tasksEvents 和 tasksArr 按下标一一对应。
tasksEvents[idx]非 0 → 第 idx 号任务有事;- 就调用
tasksArr[idx]→ 第 idx 号任务的处理函数。
生活类比:
tasksArr就是前台桌上的部门电话本——查到 3 号部门有工单,就拨 3 号分机;查到 9 号部门有事,就拨 9 号分机。编号对上号,一个萝卜一个坑。
三、事件处理函数的标准模板
每个事件处理函数签名都统一:
uint16 zclSampleSw_event_loop(byte task_id, uint16 events);
// ↑ 返回没处理完的事件 ↑ 任务号 ↑ 事件集合SampleSwitch 应用层的事件处理函数长这样(已简化):
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
(void)task_id; // 本例不用 task_id,消除编译警告
// ① 先处理"用户自定义事件"
if (events & SAMPLEAPP_TEST_EVT) { // 有 TEST 事件?
printf("Hello World!\r\n"); // 干活
return (events ^ SAMPLEAPP_TEST_EVT); // 清掉已处理的,返回剩余
}
// ② 再处理"系统消息事件"
if (events & SYS_EVENT_MSG) { // 有系统消息?
// ……读取消息、处理消息、释放消息……
return (events ^ SYS_EVENT_MSG); // 清掉,返回剩余
}
// ③ 其他没识别的事件:直接丢弃
return 0;
}模板套路就是三个步骤:判断 → 处理 → 清除返回。
| 步骤 | 代码 | 说明 |
|---|---|---|
| 判断 | events & SAMPLEAPP_TEST_EVT | 用按位与 & 看事件集合里有没有这个事件 |
| 处理 | printf(...) | 干该干的事 |
| 清除返回 | return (events ^ SAMPLEAPP_TEST_EVT) | 用按位异或 ^ 把已处理位清零,返回"还没处理完的" |
⚠️ 为什么返回"没处理完的"而不直接返回 0? 因为事件可能"一次来好几个"。比如
events = 0x0041(同时有 TEST 事件和另一个事件),处理完 TEST 后,另一个还没处理——必须把它返回去,OSAL 会|=写回任务池,下一轮继续处理。
四、事件从哪里来?三个"进货渠道"
任务池里的"待办事件"不会凭空出现,主要有三个来源:
| 来源 | 谁产生 | 例子 |
|---|---|---|
| 定时器 | osal_start_timerEx() | 3 秒后触发 SAMPLEAPP_TEST_EVT |
| 系统消息 | osal_msg_send() | 其他任务发来一封信(消息) |
| 外设中断 | HAL 按键/串口 | 你按了一下 S3 按键 |
生活类比:事件 = 工单。工单可能是闹钟响了(定时器)、同事递来一张纸条(消息)、或者有人按了门铃(外设)。无论哪种,最终都会写进对应部门的"待办登记表"(tasksEvents),等着秘书(轮询)来查。
五、小结
| 问题 | 答案 |
|---|---|
| 任务池谁创建的? | osalInitTasks():申请 tasksEvents 数组 + 逐层注册任务 |
| 任务编号怎么来的? | 每注册一层 taskID++,从 0 开始递增 |
| 怎么找到处理函数? | tasksArr[idx] 电话本,与任务池按下标一一对应 |
| 处理函数模板? | 判断(&)→ 处理 → 清除返回(^) |
| 为什么返回未处理事件? | 事件可能多个一起来,没处理完的下轮接着干 |
| 事件从哪来? | 定时器 / 系统消息 / 外设中断 |
一句话总结:osalInitTasks() 让各层任务"报名"并排好号,tasksArr[] 把任务号和处理函数对上;处理函数按"判断 → 处理 → 清除返回"的模板干活——这就是 OSAL 世界里"任务"和"事件"的完整生命周期。
内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·3.2 节(原站已失效),本站按 Z-Stack 3.0 源码与教学实践重写;原站链接