zigbee中OSAL事件传递机制和消息传递机制
⼀、概述
OSAL (Operating System Abstraction Layer),翻译为“操作系统抽象层”。 OSAL 就是⼀种⽀持多任务运⾏的系统资源分配机制。OSAL与标准的操作系统还是有很⼤的区别的。简单⽽⾔, OSAL 实现了类似操作系统的某些功能,但并不能称之为真正意义上的操作系统。
⼆、OSAL任务运⾏⽅式
我们以TI1.2.1的 BLE 协议栈中的SimpleBLEPeripheral为例,分析⼀下 OSAL 。其中有⼀个simpleBLEPeripheral.c⽂件,⾥⾯有2个⽐较重要的函数:SimpleBLEPeripheral_Init和SimpleBLEPeripheral_ProcessEvent。SimpleBLEPeripheral_Init是任务的初始化函数,⽽
SimpleBLEPeripheral_ProcessEvent则负责处理传递给此任务的事件。
⼤概浏览⼀下SimpleBLEPeripheral_ProcessEvent这个函数,我们可以发现,此函数的主要功能是判断由参数传递的事件类型,然后执⾏相应的事件处理函数。由此,可以推断出 BLE 协议栈应⽤程序的运⾏机制如下图所⽰:
当有⼀个事件发⽣的时候, OSAL 负责将此事件分配给能够处理此事件的任务,然后此任务判断事件的类型,调⽤相应的事件处理程序进⾏处理。
明⽩了这个问题,新的问题⼜摆在了我们的⾯前: OSAL 是如何传递事件给任务的。
三、OSAL的事件传递机制
在试图弄清楚这个问题之前,我们需要弄清楚另外⼀个⼗分基础⽽重要的问题。那就是如何向我们的应⽤程序中添加⼀个任务。
我们先来看看simpleBLEPeripheral.c是如何添加任务的。
我们打开OSAL_SimpleBLEPeripheral.c⽂件。这⾥我们可以到⼀个很重要的数组tasksArr和⼀个同样很重要的函数osalInitTasks。
TaskArr这个数组⾥存放了所有任务的事件处理函数的地址,在这⾥事件处理函数就代表了任务本⾝,也就是说事件处理函数标识了与其对应的任务。
osalInitTasks是 OSAL 的任务初始化函数,所有任务的初始化⼯作都在这⾥⾯完成,并且⾃动给每个任务分配⼀个ID。
要添加新任务,我们需要编写新任务的事件处理函数和初始化函数,然后将事件处理函数的地址加⼊此数组。然后在osalInitTasks中调⽤此任务的初始化函数。在此例中,我们此前提到过的SimpleBLEPeripheral_ProcessEvent这个函数被添加到了数组的末尾,SimpleBLEPeripheral_Init 这个函数在osalInitTasks中被调⽤。
值得注意的是,TaskArr数组⾥各任务函数的排列顺序要与osalInitTasks函数中调⽤各任务初始化函数的顺序必须⼀致,只有这样才能够保证每个任务能够通过初始化函数接收到正确的任务ID。
另外,为了保存任务初始化函数所接收的任务ID,我们需要给每⼀个任务定义⼀个全局变量来保存这个ID。在SimpleBLEPeripheral中SimpleBLEPeripheral.c中定义了⼀个全局变量SimpleBLEPeripheral_TaskID ;并且在SimpleBLEPeripheral_Init函数中进⾏了赋值
{
SimpleBLEPeripheral_TaskID = task_id;
}
这条语句将分配给SimpleBLEPeripheral的任务ID保存了下来。
到此,我们就给应⽤程序中完整的添加了⼀个任务。
我们回到 OSAL 如何将事件分配给任务这个问题上来,在OSAL_SimpleBLEPeripheral.c这个⽂件中,在定义TaskArr这个数组之后,⼜定义了两个全局变量。
tasksCnt这个变量保存了当前的任务个数。
tasksEvents是⼀个指向数组的指针,此数组保存了当前任务的状态。在任务初始化函数中做了如下操作
{
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
}
/*osal_mem_alloc()为当前OSAL中的各任务分配存储空间(实际上是⼀个任务数组),
函数返回指向任务缓冲区的指针,因此tasksEvents指向该任务数组(任务队列).注意
tasksEvents和后⾯谈到的tasksArr[]⾥的顺序是⼀⼀对应的, tasksArr[ ]中的第i个
事件处理函数对应于tasksEvents中的第i个任务的事件.*/
/*osal_memset()把开辟的内存全部设置为0;sizeof( uint16 )是2个字节,即⼀个任务的长度(任务函数同样是uint16定义),乘以任务数量tasksCnt,即全部内存空间*/我们可以看出所有任务的状态都被初始化为0。代表了当前任务没有需要响应的事件。
紧接着,我们来到了main()函数。此SimpleBLEPeripheral_Main.c⽂件中。略过许多对当前来说并⾮重要的语句,我们先来看osal_init_system()这个函数。在此函数中,osalInitTasks()被调⽤,从⽽tasksEvents中的所有内容被初始化为0。
之后,在main()函数中,我们进⼊了osal_start_system()函数,此函数为⼀个死循环,在这个循环中,完成了所有的事件分配。
⾸先我们来看这样⼀段代码:
{
do
{
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt);
}
当tasksEvents这个数组中的某个元素不为0,即代表此任务有事件需要相应,事件类型取决于这个元素的值。这个do-while循环会选出当前优先级最⾼的需要响应的任务,
{
events = (tasksArr[idx])( idx, events );
}
此语句调⽤tasksArr数组⾥⾯相应的事件处理函数来响应事件。如果我们新添加的任务有了需要响应的事件,那么此任务的事件处理程序将会被调⽤。
就这样,OSAL 就将需要响应的事件传递给了对应的任务处理函数进⾏处理。
附:详解events = (tasksArr[idx])( idx, events );
(tasksArr[idx])( idx, events )是⼀个函数指针数组。那么什么是函数指针数组呢?顾名思义,函数指针数组是⼀个数组,数组中存放的元素类型是函数的指针。表达式举例:char(*p[])(int i) ;对于这个表达式我们从语法上解释为,p是⼀个数组变量名,数组变量类型是char(*)(int i),存放元素的类型是:char(int i)函数的指针。
tasksArr[idx]就是⼀个函数指针数组,⾥⾯存储的就是函数的指针。
const pTaskEventHandlerFn tasksArr[] =
{
LL_ProcessEvent, // task 0
Hal_ProcessEvent, // task 1
HCI_ProcessEvent, // task 2
#if defined ( OSAL_CBTIMER_NUM_TASKS )
OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ), // task 3
#endif
L2CAP_ProcessEvent, // task 4
GAP_ProcessEvent, // task 5
GATT_ProcessEvent, // task 6
SM_ProcessEvent, // task 7
GAPRole_ProcessEvent, // task 8
GAPBondMgr_ProcessEvent, // task 9
GATTServApp_ProcessEvent, // task 10
SimpleBLEPeripheral_ProcessEvent // task 11
};
假设idx=11;;那么events = (tasksArr[11])( 11, events );;也就是调⽤了SimpleBLEPeripheral_ProcessEvent这个函数,其中传⼊的参数就
是(11,events);也就是调⽤了events = (tasksArr[11])( 11, events );其实就是执⾏了SimpleBLEPeripheral_ProcessEvent(11,events);
四、事件的捕获
不过接下来就有了更加深⼊的问题了,事件是如何被捕获的?直观⼀些来说就是,tasksEvents这个数组⾥的元素是什么时候被设定为⾮零数,来表⽰有事件需要处理的?为了详细的说明这个过程,我将以SimpleBLEPeripheral这个例程中响应按键的过程来进⾏说明。其他的事件虽然稍有差别,却是⼤同⼩异。
按键在我们的应⽤⾥⾯应该属于硬件资源,所以OSAL理应为我们提供使⽤和管理这些硬件的服务。稍微留意⼀下我们之前说过的tasksArr这样⼀个数组,它保存了所有任务的事件处理函数。我们从中发现了⼀个很重要的信息:Hal_ProcessEvent。HAL(Hardware Abstraction Layer)翻译为“硬件抽象层”。许多⼈在这⾥经常把将 BLE 的硬件抽象层与物理层混为⼀谈。在这⾥,我们应该将 BLE 的硬件抽象层
与物理层区分开来。硬件抽象层所包含的范围是我们当前硬件电路上⾯所有对于系统可⽤的设备资源。⽽物理层则是针对⽆线通信⽽⾔,它所包含的仅限于⽀持⽆线通讯的硬件设备。
通过这个重要的信息,我们可以得出这样⼀个结论: OSAL 将硬件的管理也作为⼀个任务来处理。那么我们很⾃然的去寻Hal_ProcessEvent 这个事件处理函数,看看它究竟是如何管理硬件资源的。
在“HAL\Commn\ hal_drivers.c”这个⽂件中,我们到了这个函数。我们直接分析与按键有关的⼀部分。
{
if (events & HAL_KEY_EVENT)
{
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
/* Check for keys */
HalKeyPoll();
/
* if interrupt disabled, do next polling */
if (!Hal_KeyIntEnable)
{
osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
}
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT;
}
}
在事件处理函数接收到HAL_KEY_EVENT这样⼀个事件后,⾸先执⾏HalKeyPoll()函数。由于这个例程的按键采⽤查询的⽅法获取,所以是禁⽌中断的,于是表达式(!Hal_KeyIntEnable)的值为真。那么osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100)得以执⾏。osal_start_timerEx这是⼀个很常
⽤的函数,它在这⾥的功能是经过100毫秒后,向Hal_TaskID这个ID所标⽰的任务(也就是其本⾝)发送⼀个HAL_KEY_EVENT事件。这样以来,每经过100毫秒,Hal_ProcessEvent这个事件处理函数都会⾄少执⾏⼀次来处理HAL_KEY_EVENT事件。也就是说每隔100毫秒都会执⾏HalKeyPoll()函数。
那么我们来看看HalKeyPoll函数到底在搞什么⿁!
代码中给的注释为:
/* Check for keys */
HalKeyPoll();
于是我们推断这个函数的作⽤是检查当前的按键情况。进⼊函数⼀看,果不其然。虽然这个函数很长很复杂,不过凭借着⾮凡的聪明才智,我们还是⼗分清楚的明⽩了,经过⼀系列的if语句和赋值语句,在接近函数末尾的地⽅, keys变量(在函数起始位置定义的)获得了当前按键的状态。最后,有⼀个⼗分重要的函数调⽤。
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
pHalKeyProcessFunction这个函数指针指向了哪个函数我们现在依然不清楚,但是为了我们有个清晰⽽不间断的思路,我在这⾥先告诉⼤家。在这⾥调⽤的是
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
这个函数。此函数在“OnBoard .c”⽂件中可以到。在这个函数中,⼜调⽤了
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
在这个函数中,按键的状态信息被封装到了⼀个消息结构体中(对于消息,我们稍后再说)。最后有⼀个极其重要的函数被调⽤了。
osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
与前⾯的pHalKeyProcessFunction相同,我先直接告诉⼤家registeredKeysTaskID所指⽰的任务正是我们需要响应按键的SimpleBLEPeripheral这个任务。
那么也就是说,在这⾥我们向SimpleBLEPeripheral发送了⼀个附带按键信息的消息。在osal_msg_send函数中
osal_set_event( destination_task, SYS_EVENT_MSG );
被调⽤,它在这⾥的作⽤是设置destination_task这个任务的事件为SYS_EVENT_MSG。⽽这个destinat
ion_task正式由osal_msg_send这个函数通过参数传递⽽来的,它也指⽰的是SimpleBLEPeripheral这个任务。在osal_set_event这个函数中,有这样⼀个语句:
{
tasksEvents[task_id] |= event_flag;
}
⾄此,刚才所提到的问题得到了解决。我们再将这个过程整理⼀遍。
⾸先,OSAL专门建⽴了⼀个任务来对硬件资源进⾏管理,这个任务的事件处理函数是Hal_ProcessEvent。在这个函数中通过调
⽤osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);这个函数使得每隔100毫秒就会执⾏⼀次HalKeyPoll()函数。HalKeyPoll()获取当前按键的状态,并且通过调⽤OnBoard_KeyCallback函数向SimpleBLEPeripheral任务发送⼀个按键消息,并且设置tasksEvents中SimpleBLEPeripheral所对应的值为⾮零。如此,当main函数⾥这样⼀段代码
{
do
{
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt);
}
执⾏了以后,SimpleBLEPeripheral这个任务就会被挑选出来。然后通过
events = (tasksArr[idx])( idx, events );
这个函数调⽤其事件处理函数,完成事件的响应。
现在,我们回过头来处理我们之前遗留下来的问题。
第⼀、pHalKeyProcessFunction这个函数指针为何指向了OnBoard_KeyCallback函数。
在HAL\Common\ hal_drivers.c这个⽂件中,我们到了HalDriverInit这个函数,在这个函数中,按键的初始化函数HalKeyInit被调⽤。在HalKeyInit中有这样的语句:
{
pHalKeyProcessFunction = NULL;
}
这说明在初始化以后pHalKeyProcessFunction并没有指向任何⼀个函数。那pHalKeyProcessFunction是什么时候被赋值的呢?
就在HalKeyInit的下⽅有⼀个这样的函数HalKeyConfig。其中有这样⼀条语句:
pHalKeyProcessFunction = cback;
cback是HalKeyConfig所传进来的参数,所以,想要知道它所指向的函数,必须到其调⽤的地⽅。经
过简单的搜索我们不难出答案。在main函数中有这样⼀个函数调⽤:InitBoard( OB_READY );此函数中做了如下调⽤:
{
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
}
第⼆、registeredKeysTaskID为什么标识了SimpleBLEPeripheral这个任务?
由于 OSAL 是⼀个⽀持多任务的调度机制,所以在同⼀时间内将会有多个任务同时运⾏。但是从逻辑上来讲,⼀个事件只能由⼀个任务来处理。按键事件也不例外。
那么如何向 OSAL 声明处理按键事件的任务是SimpleBLEPeripheral呢?
在SimpleBLEPeripheral_Init(SimpleBLEPeripheral的任务初始化函数)中有这么⼀个语句:
{
RegisterForKeys( SimpleBLEPeripheral_TaskID );
}
RegisterForKeys函数向 OSAL 声明按键事件将由SimpleBLEPeripheral任务来处理。在RegisterForKeys函数中:
{
registeredKeysTaskID = task_id;
}
我想我不⽤再做多余的解释了,聪明的您肯定可以理解。
五、消息队列
sizeof是什么⾸先我需要向⼤家解释清楚消息与事件的联系。事件是驱动任务去执⾏某些操作的条件,当系统产⽣了⼀个事件,将这个传递给相应的任务后,任务才能执⾏⼀个相应的操作。但是某些事件在它发⽣的同时,⼜伴随着⼀些附加信息的产⽣。任务的事件处理函数在处理这个事件的时候,还需要参考其附加信息。最典型的⼀类便是按键消息,它同时产⽣了⼀个哪个按键被按下了附加信息。所以在OnBoard_SendKeys这个函数中,不仅向SimpleBLEPeripheral发送了事件,还通过调⽤osal_msg_sen
d函数向SimpleBLEPeripheral发送了⼀个消息,这个消息记录了这个事件的附加信息。在SimpleBLEPeripheral_ProcessEvent中,通过
{
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SimpleBLEPeripheral_TaskID );
}
获取了这样⼀个消息,然后再进⼀步处理。
OSAL 在后台维护了⼀个消息队列,每⼀个消息都会被放到这个消息队列中去,当任务接收到事件以后,从消息队列中获取属于⾃⼰的消息,然后进⾏处理。
以上就是我就将 OSAL 这样⼀个事件驱动的多任务的资源分配机制做了⼀个简明扼要的介绍,希望对⼤家有所帮助。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论