OSAL的事件驱动框架
OSAL的事件驱动框架一、任务的管理和调度1.1 任务结构体和任务链表1.2 事件和任务事件编码的注意事项事件编码的典型流程系统消息事件
1.3 源码链接地址
二、消息的管理机制三、定时器的实现四、内存管理
OSAL的事件驱动框架
OSAL(Operating System Abstraction Layer)是一种基于事件驱动的框架,常见于嵌入式系统(如Zigbee协议栈Z-Stack)。OSAL即操作系统抽象层,其核心目标是通过事件管理实现多任务协作,避免复杂的实时操作系统(RTOS)依赖。在OSAL中实现了多任务的管理,任务之间的通知,消息队列和内存管理。
事件(Event):表示任务需要处理的特定动作或状态变化(如传感器数据到达、定时器到期、消息接收等)。嵌入式系统中,外设(如传感器、通信模块)或内部逻辑(如定时器)产生的动作通常是异步的。事件机制通过这些异步动作触发任务执行,而无需任务主动轮询状态。
任务(Task):是一个独立的功能模块,负责处理特定的事件集合,任务通过事件触发的方式运行。例如一个开关灯的任务,可以通过按键事件来触发开关灯。
一、任务的管理和调度
OSAL的事件驱动框架,采用的是协作式调度机制,即多个任务在同一个系统中轮询运行,但任务之间不会主动抢占 CPU,而是需要任务自身主动让出 CPU,以便其他任务执行。任务的执行由事件触发激活,没有事件时,任务处于休眠状态。
任务的处理化流程如下图所示:
其中osal_start_system的处理流程图下图所示:
相对应的代码如下:
void osal_start_system(void)
{
uint16 events;
uint16 retEvents;
while(1)
{
TaskActive = osalNextActiveTask();
if(TaskActive)
{
HAL_ENTER_CRITICAL_SECTION();
events = TaskActive->events;
// Clear the Events for this task
TaskActive->events = 0;
HAL_EXIT_CRITICAL_SECTION();
if(events != 0)
{
// Call the task to process the event(s)
if(TaskActive->pfnEventProcessor)
{
retEvents = (TaskActive->pfnEventProcessor)(TaskActive->taskID, events);
// Add back unprocessed events to the current task
HAL_ENTER_CRITICAL_SECTION();
TaskActive->events |= retEvents;
HAL_EXIT_CRITICAL_SECTION();
}
}
}
}
}
1.1 任务结构体和任务链表
任务的声明如下:
/**
* @brief 任务链表
*/
typedef struct OSALTaskREC
{
struct OSALTaskREC *next;
pTaskInitFn pfnInit; //任务初始化函数指针
pTaskEventHandlerFn pfnEventProcessor; //任务事件处理函数指针
uint8 taskID; //任务ID
uint8 taskPriority; //任务优先级
uint16 events; //任务事件
} OsalTadkREC_t;
其中,taskID用于任务身份唯一标识,events为该激活该任务所对应的事件。taskPriority为任务的优先级,区别于抢占式调度,在OSAL系统中,优先级高的任务会被插入到链表头结点中,当事件触发时,高优先级的任务会最先被轮询到。
以新建三个优先级不同的任务为例,首先调用osal_add_Task,新增任务1,优先级为2;然后新增任务2,它的优先级为1,最后增加任务3,设置它的优先级为3。则最终形成的任务链表示意图如下所示:
在这个任务链表示意图中,用绿色数字标注出了新增三个任务时,分配到的任务ID。任务ID是根据调用osal_add_Task函数的顺序,自动累计的,同时用于后续区分和查找任务。
查找任务的函数如下:
OsalTadkREC_t *osalFindTask(uint8 taskID)
{
OsalTadkREC_t *TaskSech;
TaskSech = TaskHead;
while(TaskSech)
{
if(TaskSech->taskID == taskID)
{
return (TaskSech);
}
TaskSech = TaskSech->next;
}
return ((OsalTadkREC_t *)NULL);
}
遍历任务链表,根据TaskID返回所对应的任务。
1.2 事件和任务
一个任务对应着多个不同的事件,OSAL中为了方便区分不同的事件,任务的事件编码方式采用 位掩码(Bitmask) 机制,通过将不同的事件类型映射到一个整数变量(通常为uint16类型)的二进制位上,实现事件的轻量化表示、触发和管理。这种设计高效且资源友好,尤其适合嵌入式系统。
位掩码表示:
每个事件对应一个独立的二进制位,通过置位(1)或清零(0)表示事件触发与否。
事件类型定义:使用16位的无符号整数(uint16),最多支持16种事件(从第0位到第15位)。
事件编码的注意事项
1.避免事件位冲突
每个任务独立管理自己的事件位,不同任务的事件定义可以重复(因为事件变量是任务独立的)。同一任务内的事件位必须唯一,避免位冲突。
#define SAMPLE_EVENT_DATA_READY 0x0001 // 数据就绪 (二进制:0000 0000 0000 0001)
#define SAMPLE_EVENT_TIMEOUT 0x0002 // 发送失败 (二进制:0000 0000 0000 0010)
2. 事件优先级
OSAL默认按轮询顺序处理事件,无内置优先级。
uint16 Task_ProcessEvent(...) {
// 按照判断顺序,轮询处理
if (events & SAMPLE_EVENT_DATA_READY) { ... }
else if (events & SAMPLE_EVENT_TIMEOUT) { ... }
}
3.复合事件
支持一次性触发多个事件(通过位或操作|):
// 同时触发"数据就绪"和"发送失败"事件
osal_set_event(task_id, SAMPLE_EVENT_DATA_READY | SAMPLE_EVENT_SEND_FAILED);
事件编码的典型流程
(1) 定义事件类型
在头文件中明确定义任务的专属事件:
#define SAMPLE_EVENT_DATA_READY 0x0001 // 数据就绪 (二进制:0000 0000 0000 0001)
#define SAMPLE_EVENT_TIMEOUT 0x0002 // 超时 (二进制:0000 0000 0000 0010)
(2) 触发事件
在外部逻辑(如中断、其他任务)中触发事件:
// 触发任务的"数据就绪"和"超时"事件
osal_set_event(sampleTaskID, SAMPLE_EVENT_DATA_READY | SAMPLE_EVENT_TIMEOUT);
(3) 处理事件
在任务的事件处理函数中依次处理事件:
uint16 SampleTask_ProcessEvent(uint8 task_id, uint16 events) {
if (events & SAMPLE_EVENT_DATA_READY) {
process_data(); // 处理数据就绪事件
events ^= SAMPLE_EVENT_DATA_READY; // 清除事件
}
if (events & SAMPLE_EVENT_TIMEOUT) {
handle_timeout(); // 处理超时事件
events ^= SAMPLE_EVENT_TIMEOUT;
}
return events; // 返回剩余未处理的事件(若有)
}
系统消息事件
为了不同任务之间能够发送和接收消息,进行数据通信,特别定义SYS_EVENT_MSG通常占用最高位(如0x8000),表示系统消息事件,用户自定义事件应避免使用该位。通常情况下,把系统消息事件的处理放在其他事件处理之前。
uint16 SampleTask_ProcessEvent(uint8 task_id, uint16 events) {
if (events & SYS_EVENT_MSG) { // 处理系统消息事件
afIncomingMSGPacket_t *msg = (afIncomingMSGPacket_t *)osal_msg_receive(task_id); // 接收消息
if (msg != NULL) {
if (msg->hdr.event == AF_INCOMING_MSG_CMD) {
handle_zigbee_message(msg->data); // 处理消息
}
osal_msg_deallocate(msg); // 销毁消息
}
events ^= SYS_EVENT_MSG;
}
if (events & SAMPLE_EVENT_DATA_READY) {
process_data(); // 处理数据就绪事件
events ^= SAMPLE_EVENT_DATA_READY; // 清除事件
}
if (events & SAMPLE_EVENT_TIMEOUT) {
handle_timeout(); // 处理超时事件
events ^= SAMPLE_EVENT_TIMEOUT;
}
return events; // 返回剩余未处理的事件(若有)
}
同样的,消息的发送函数中也是要调用 osal_set_event(task_id, SYS_EVENT_MSG);
事件驱动任务的运行机制,当有事件到来时,才去激活任务,执行任务处理流程。
在前面提到,osalNextActiveTask函数会依次检查当前的任务链表中的每一个任务,是否有事件发生。如果有就根据任务链表排列顺序,依次执行任务,任务处理完事件后,需显式清除对应事件位,避免重复触发,直到所有的事件都处理完毕。
OsalTadkREC_t *osalNextActiveTask(void)
{
OsalTadkREC_t *TaskSech;
// Start at the beginning
TaskSech = TaskHead;
// When found or not
while(TaskSech)
{
if(TaskSech->events)
{
// task is highest priority that is ready
return TaskSech;
}
TaskSech = TaskSech->next;
}
return NULL;
}
在没有事件发生的时候,任务不会被激活。当有事件发生时,调用 osal_set_event(byte task_id, uint16 event_flag) 来激活任务,event_flag是指当前所发生的事件标志位。osal_set_event给task_id所对应的任务的events值赋值。
uint8 osal_set_event(byte task_id, uint16 event_flag)
{
OsalTadkREC_t *srchTask;
srchTask = osalFindTask(task_id);
if(srchTask)
{
// Hold off interrupts
HAL_ENTER_CRITICAL_SECTION();
// Stuff the event bit(s)
srchTask->events |= event_flag;
// Release interrupts
HAL_EXIT_CRITICAL_SECTION();
}
else
return (INVALID_TASK);
return (ZSUCCESS);
}
而 osal_set_event(byte task_id, uint16 event_flag) 则是清除事件标志位:
uint8 osal_clear_event(uint8 task_id, uint16 event_flag)
{
OsalTadkREC_t *srchTask;
srchTask = osalFindTask(task_id);
if(srchTask)
{
// Hold off interrupts
HAL_ENTER_CRITICAL_SECTION();
// Stuff the event bit(s)
srchTask->events &= ~event_flag; // 等价于srchTask->events ^= event_flag;
// Release interrupts
HAL_EXIT_CRITICAL_SECTION();
}
else
return (INVALID_TASK);
return (ZSUCCESS);
}
1.3 源码链接地址
OSAL github 链接地址 stm32 osal keil工程链接地址如下: stm32 osal 测试工程链接
二、消息的管理机制
三、定时器的实现
四、内存管理