摘要:
1、任务调度:osal采用一个链表结构来管理协议栈各层相应任务。相关操作函数有,添加任务到链表中;获取下一个活动任务;根据taskID查找下一个任务。osal采用轮询任务调度队列(任务链表),通过两个函数:调度程序主循环函数和设置事件发生标志函数。
2、时间管理:通过为事件设置超时等待时间,一旦等待时间结束,便为对应任务设置事件发生标志,从而达到对事件进行延时处理目的。
3、原语通信:请求响应原语操作:一旦调用了下层相关函数后,就立即返回。下层处理函数在操作结束后,将结果以消息的形式发送到上层并产生一个系统事件,调度程序发现这个事件后就会调用相应的事件处理函数对它进行处理。两个相关函数:向目标任务发送消息的函数;消息提取函数。
一、操作系统介绍
现有的嵌入式操作系统可以分为两类,即通用的多任务操作系统(General—purpose Multi-tasking OS)和事件驱动的操作系统(Event-driven OS)。前者能够很好地支持多任务或者多线程,但是会随着内部任务切换频率的增加而产生很大的开销,这类操作系统有:uC/OS-II、嵌入式Linux、WinCE等。后者支持数据流的高效并发,并且考虑了系统的低功耗要求,在功耗、运行开销等方面具有优势。典型的代表如TinyOSl291。
目前TinyOS操作系统支持的平台有ATMEL公司的AVR系列、TI公司的MSP430系列。由于TinyOS操作系统还没有对Chipcon公司(才知道TI把它收购了)提供CC2430开发平台提供支持,因此,要在CC2430开发平台上使用TinyOS系统来开发Zigbee协议栈软件,就必须首先对TinyOS进行移植。灰常麻烦……
因此Chipcon公司为自己设计的ZStack协议栈中提供了一个名为操作系统抽象层OSAL的协议栈调度程序。
//-------------------------------------------------------------------------------------
二、下面分析下这个协议栈调度程序(OSAL)的调度机制。
三部分:1、任务调度
2、时间管理
3、原语通信
(一)任务调度
//每层任务=对应事件处理函数
//任务链表,任务按优先级插入
ZigBee协议栈中的每一层都有很多原语操作要执行,因此对于整个协议栈来说,就会有很多并发操作要执行。协议栈的每一层都设计了一个事件处理函数,用来处理与这一层操作相关的各种事件。这些事件处理函数可以看成是与协议栈每一层相对应的任务,由ZigBee协议栈中调度程序OSAL来进行管理。这样,对于协议栈来说,无论何时发生了何种事件,我们都可以通过调度协议栈相应层的任务,即事件处理函数来进行处理。这样,整个协议栈便会按照时间顺序有条不紊的运行。
ZigBee协议栈的实时性要求并不高,因此在设计任务调度程序时,OSAL只采用了轮询任务调度队列的方法来进行任务调度管理。
OSAL采用一个链表结构来管理协议栈各层相应的任务。链表中的每一项是一个结构体,用来记录链表中相关任务的基本信息。链表的建立是按照任务优先级从高到低的顺序进行插入的。优先级高的任务将被插入到优先级低的任务前面。如果俩任务优先级相同,则按照时间顺序加入到链表中。那么这个任务链表在系统启动的时候建立,一旦建立后便一直存在于事个系统运行的过程中,直到系统关闭或硬件复位才被销毁。
链表中的每一项数据结构声明:
typedef void (*pTaskInitFn)(unsigned char task_id) ; //指向任务初始化函数
typedef void (*pTaskEventHandlerFn)(usigned char task_id unsigned short event_flag); //指向事件处理函数
typedef struct osalTaskRec
{
struct osalTaskRec *next; //指向链表中下一个结构体
pTaskInitFn pfnInit; //指向相关层任务初始化函数
pTaskEventHandlerFn pfnEventProcessor; //指向相关层事件处理函数
byte taskID; //对应当前任务ID
byte taskPriority; //当前任务优先级
uint16 events; //需要被处理的事件,0表示没有要被处理事件
} osalTaskRec_t; //链表中的每一项数据结构
//-------------------------------------------------------------------------------------
对于pfnInit,是指向相关层任务初始化函数的指针,比如:
网络层中,任务初始化函数为nwkInit(),用来对网络层相关数据进行初始化操作。函数声明为
extern void nwkInit(byte task_id);
//task_id表示为网络层任务分配的唯一任务号
//-------------------------------------------------------------------------------------
对于 pfnEventProcessor是指向协议栈相关层的事件处理函数指针。比如:
网络层中,事件处理函数nwk_event_loop(),用来处理与网络层相关的各种事件。函数声明为
extern void nwk_event_loop(byte task_id, uint16 event_flag);
//event_flag标志需要在网络层处理的事件
//-------------------------------------------------------------------------------------
上面记录的是链表中的每一项数据结构,与任务链表有关的主要操作有:添加任务到列表中;获取下一个活动任务;根据taskID值查找相应的任务。
(1)在任务管理列表中添加任务
这个函数遍历整个任务队列链表,并按照优先级的高低将优先级高的任务插入到优先级低的任务前面;否则,就将任务插入到链表的尾部。在这个过程中,将为每个任务分配一个唯一的任务号。函数声明为:
Extern osalTaskAdd(pTaskInitFn pfnInit,pTaskEventHandleFn pfnEventProcessor,byte taskPriorty);
(2)获取下一个活动任务
这个函数将根据osalTaskRec_t结构中的events标记来获取任务队列中下一个要执行的任务。函数声明:
Extern osalTaskRec_t *osalNextActiveTask(void) ;
(3)根据taskID查找任务
这个函数将根据任务列表在建立过程中为协议栈中每个任务分配的任务号,来查找对应任务。函数声明:
Extern osalTaskRec_t *osalFindTask(byte taskID);
当任务链表建立成功后,系统便开始运行。如果在系统运行的过程中有事件发生,系统就会通过调用相应的任务,即事件处理函数,对所发生的事件进行相应处理。在整个运行过程中,调度程序(OSAL)始终不停地轮询任务队列链表,以发现需要处理的事件。这个过程涉及两个函数操作:
1、调度程序主循环函数
2、设置事件发生标志函数
(1)系统主循环
这个函数始终不停地轮询队列链表,来处理系统发生的各种事件。函数声明和部分实现如下:
extern void osal_start_system(void);
//无限循环
for(; ; )
{
activeTask=osalNextActiveTask();
if(activeTask)
{
StoreDisableInts;
events=activeTask—>events;
activeTask—>events=0;
if(events!=0)
{
(activeTask—>pfnEventProcessor)(activeTask—>taskID,events);
RestoreInts;
}
}
}
(2)设置事件发生标志
当协议栈中有任何事件发生时,我们可以通过设置osalTaskRec_t结构中的events来标记有事件发生,以便主循环函数能够及时加以处理。函数声明如下:
extern byte osal_set_event(byte task_id,uint16 event_flag);
(二)时间管理
协议栈中的每层都会有很多不同的事件发生,这些事件发生的时间顺序各不相同。很多时候,事件并不要求立即得到处理,而是经过一定的时间后再进行处理。OSAL调度程序设计了与时间管理相关的函数,用来各种不同的要被处理的事件。
对事件进行时间管理,OSAL也采用了链表的方式进行,有时发生一个要被处理的事件,就启动一个逻辑上的定时器,并将此定时器添加到链表当中。利用硬件定时器作为时间操作的基本单元。设置时间操作的最小精度为1ms,每1ms硬件定时器便产生一个时间中断,在时间中断处理程序中去更新定时器链表。每次更新,就将链表中的每一项时间计数减1,如果发现定时器链表中有某一表项时间计数已减到0,则将这个定时器从链表中删除,并设置相应的事件标志。这样任务调度程序便可以根据事件标志进行相应的事件处理。具体参见关于“系统时钟”的记录。
时间管理函数:
extern byte osal_start_timer(byte task_id, uint16 event_id, uint16 timeout_value);
这个函数为事件event_id设置超时等待时间timeout_value。一旦等待结束,便为task_id所对应的任务设置相应的事件发生标记,再对事件进行相应处理。
(三)原语通信
(原语只是一个理论层面上的术语,描述了服务层次的关系,以及两个通信的N用户和它们相连的N层(子层)对待协议实体之间的关系。初学时总是想不通原语跟协议栈的代码有什么关系,后来才了解了原语只是规范里面的一个术语,反映到协议栈代码里就是一个个具体的函数了!例如我们可以看到很多原语是以request,confirm等为后缀的,到了程序里面就是相应的request请求函数,confirm确认函数了。)
对请求(request)、响应(response)原语可以直接使用函数调用来实现
对确认(confirm)、指示(indication)原语则需采用间接处理机制来完成
一个原语的操作往往需要逐层调用下层函数并根据下层返回的结果来进行进一步的操作。在这种情况下,一个原主的操作从发起到完成需要很长时间。因此,如果让程序一直等待下层返回的结果再进一步处理,会使微处理器大部分时间处于循环等待之中,无法及时处理其它请求。
因此,与请求、响应原语操作相对应的函数,一旦调用了下层相关函数后,就立即返回。下层处理函数在操作结束后,将结果以消息的形式发送到上层并产生一个系统事件,调度程序发现这个事件后就会调用相应的事件处理函数对它进行处理。(调用就返回,而不管函数有没有处理完成。当函数处理完成后将结果以消息的形式发送到上层产生一个系统事件)。
OSAL调度程序用两个相关的函数来完成这个过程:
1、向目标任务发送消息的函数
这个函数主要用来将原语操作结果以消息的形式往上层任务发送,并产生一个系统事件来通知调度程序。函数声明如下:
extern byte osal_msg_send(byte destination_task,byte *msg_ptr,byte len);
参数destination_task是目标任务的任务号,参数指针msg_ptr指向要被发送的消息,参数len为消息长度
2、消息提取函数
这个消息用来从内存空间中提取相应的消息。其中消息结构和函数声明如下:
typedef struct
{
byte task_id;
byte dst_task_id;
byte send_len;
}osal_msg_rec_header_t;
typedef struct
{
osal_msg_rec_header_t hdr;
byte *msg_ptr;
}osal_msg_received_t; //消息结构(上面结构体包含在里面)
extern osal_msg_received_t osal_rcvd; //全局变量
extern osal_msg_received_t *osal_msg_receive(byte task_id); //接收任务
这个函数返回一个指向osal_msg_received结构的指针,通过msg_ptr这个指针就可以提取出所需要的信息。(有一个例子,是以MAC层返回原语MCPS_DATA.confirm所对应的函数为例,下次再说明)
说明:本文作者所记录,错误处还请高手指点,本人随时更新,转载请注明出处。
本文《OSAL调度机制》主要内容参考厦门大学闫沫的《zigbee协议栈分析与设计》
参考资料:《zigbee技术基础及案例分析》;《zigbee协议栈分析与设计》;