相信很多人接触了OSAL操作系统之后对它的任务资源分配机制还是很模糊,我细看了很多遍也还是略知一二,现在分享一篇我觉得写得特别好的文章。
深入浅出Z-Stack OSAL多任务资源分配机制
一 概述
  OSAL (Operating System Abstraction Layer),翻译为“操作系统抽象层”。如何理解这个复杂的名词呢?表面上看它是作为操作系统存在的,可是为什么又加上“抽象层”呢?它的本质是什么?在Z-Stack协议栈中,它又扮演了什么角色呢?要解答这些问题,我们必须先从宏观入手,渐渐深入探究,最后答案自然会浮出水面。
  下图是ZigBee协议的结构图:
  

从这幅图中,我们可以很清楚地从宏观上了解ZigBee协议的结构。可是,经过粗略的浏览,我们并没有发现任何OSAL的踪迹。当然,我们都知道,Z-Stack与ZigBee之间并不能完全划等号。Z-Stack是ZigBee的具体实现,所以存在于Z-Stack中的OSAL并不一定出现在ZigBee中。但是,我们可以在ZigBee中找到些许OSAL的踪影。
  在ZigBee协议中,协议本身已经定义了大部分内容。在基于ZigBee协议的应用开发中,用户只需要实现应用程序框架即可。从上图可以看出应用程序框架中包含了最多240个应用程序对象。如果我们把一个应用程序对象看做为一个任务的话,那么应用程序框架将包含一个支持多任务的资源分配机制。于是OSAL便有了存在的必要性,它正是Z-Stack为了实现这样一个机制而存在的。
  OSAL就是以实现多任务为核心的系统资源管理机制。所以OSAL与标准的操作系统还是有很大的区别的。简单而言,OSAL实现了类似操作系统的某些功能,但并不能称之为真正意义上的操作系统。
二、OSAL任务运行方式
  弄明白了OSAL是何方神圣,接下来我们将深入Z-Stack,进一步研究OSAL。
  为了方便,我们使用Z-Stack所提供的GenericApp这个例程为例来进行分析。此例程的默认路径为
  C:\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\GenericApp。
  首先我们去繁就简,先来了解应用程序的运行方式。
  在右侧工作空间窗口打开App文件夹,我们可以看到三个文件,分别是“GenericApp.c”、“GenericApp.h”、“OSAL_GenericApp.c”。我们整个程序所实现的功能都在这三个文件当中。
  首先打开GenericApp.c这个文件。我们首先看到的是比较重要的两个函数:GenericApp_Init和GenericApp_ProcessEvent。从函数名称上我们很容易得到的信息便是,GenericApp_Init是任务的初始化函数,而GenericApp_ProcessEvent则负责处理传递给此任务的事件。
  大概浏览一下GenericApp_ProcessEvent这个函数,我们可以发现,此函数的主要功能是判断由参数传递的事件类型,然后执行相应的事件处理函数。我们可以由此推断Z-Stack应用程序的运行机制如下图所示:

 当有一个事件发生的时候,OSAL负责将此事件分配给能够处理此事件的任务,然后此任务判断事件的类型,调用相应的事件处理程序进行处理。
  明白了这个问题,新的问题又摆在了我们的面前:OSAL是如何传递事件给任务的。
三、OSAL的事件传递机制
  在试图弄清楚这个问题之前,我们需要弄清楚另外一个十分基础而重要的问题。那就是如何向我们的应用程序中添加一个任务。
  我们先来看看GenericApp是如何添加任务的。
  我们打开OSAL_GenericApp.c文件。这里我们可以找到一个很重要的数组tasksArr和一个同样很重要的函数osalInitTasks。
  TaskArr这个数组里存放了所有任务的事件处理函数的地址,在这里事件处理函数就代表了任务本身,也就是说事件处理函数标识了与其对应的任务。
  osalInitTasks是OSAL的任务初始化函数,所有任务的初始化工作都在这里面完成,并且自动给每个任务分配一个ID。
  要添加新任务,我们需要编写新任务的事件处理函数和初始化函数,然后将事件处理函数的地址加入此数组。然后在osalInitTasks中调用此任务的初始化函数。在此例中,我们此前提到过的GenericApp_ProcessEvent这个函数被添加到了数组的末尾, GenericApp_Init这个函数在osalInitTasks中被调用。
  值得注意的是,TaskArr数组里各任务函数的排列顺序要与osalInitTasks函数中调用各任务初始化函数的顺序必须一直,只有这样才能够保证每个任务能够通过初始化函数接收到正确的任务ID。
  另外,为了保存任务初始化函数所接收的任务ID,我们需要给每一个任务定义一个全局变量来保存这个ID。在GenericApp中GenericApp.c中定义了一个全局变量GenericApp_TaskID;并且在GenericApp_Init函数中进行了赋值
  {
       GenericApp_TaskID = task_id;
  }
  这条语句将分配给GenericApp的任务ID保存了下来。
  到此,我们就给应用程序中完整的添加了一个任务。
  我们回到OSAL如何将事件分配给任务这个问题上来
  在OSAL_GenericApp.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 )是4个字节,即一个任务的长度(同样是uint16定义),乘以任务数量tasksCnt,即全部内存空间/

  我们可以看出所有任务的状态都被初始化为0。代表了当前任务没有需要响应的事件。
  紧接着,我们来到了main()函数。此函数在ZMain文件夹的ZMain.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就将需要响应的事件传递给了对应的任务处理函数进行处理。
四、事件的捕获
  不过接下来就有了更加深入的问题了,事件是如何被捕获的?直观一些来说就是,tasksEvents这个数组里的元素是什么时候被设定为非零数,来表示有事件需要处理的?为了详细的说明这个过程,我将以GenericApp这个例程中响应按键的过程来进行说明。其他的事件虽然稍有差别,却是大同小异。
  按键在我们的应用里面应该属于硬件资源,所以OSAL理应为我们提供使用和管理这些硬件的服务。稍微留意一下我们之前说过的tasksArr这样一个数组,它保存了所有任务的事件处理函数。我们从中发现了一个很重要的信息:Hal_ProcessEvent。HAL(Hardware Abstraction Layer)翻译为“硬件抽象层”。许多人在这里经常把将Z-Stack的硬件抽象层与ZigBee的物理层混为一谈。在这里,我们应该将Z-Stack的硬件抽象层与ZigBee的物理层区分开来。硬件抽象层所包含的范围是我们当前硬件电路上面所有对于系统可用的设备资源。而ZigBee中的物理层则是针对无线通信而言,它所包含的仅限于支持无线通讯的硬件设备。
  通过这个重要的信息,我们可以得出这样一个结论:OSAL将硬件的管理也作为一个任务来处理。那么我们很自然的去寻找Hal_ProcessEvent这个事件处理函数,看看它究竟是如何管理硬件资源的。
  在“HAL\Commen\ 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 )
  这个函数。此函数在“ZMain\OnBoard .c”文件中可以找到。在这个函数中,又调用了
  void OnBoard_KeyCallback ( uint8 keys, uint8 state )
  在这个函数中,按键的状态信息被封装到了一个消息结构体中(对于消息,我们稍后再说)。最后有一个极其重要的函数被调用了。
  osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
  与前面的pHalKeyProcessFunction相同,我先直接告诉大家registeredKeysTaskID所指示的任务正是我们需要响应按键的GenericApp这个任务。
  那么也就是说,在这里我们向GenericApp发送了一个附带按键信息的消息。在osal_msg_send函数中
  osal_set_event( destination_task, SYS_EVENT_MSG );
  被调用,它在这里的作用是设置destination_task这个任务的事件为SYS_EVENT_MSG。而这个destination_task正式由osal_msg_send这个函数通过参数传递而来的,它也指示的是GenericApp这个任务。在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函数向GenericApp任务发送一个按键消息,并且设置tasksEvents中GenericApp所对应的值为非零。如此,当main函数里这样一段代码
  {
do
{
if (tasksEvents[idx])
{
break;
}
} while (++idx < tasksCnt);
  }
  执行了以后,GenericApp这个任务就会被挑选出来。然后通过
  events = (tasksArr[idx])( idx, events );
  这个函数调用其事件处理函数,完成事件的响应。
现在,我们回过头来处理我们之前遗留下来的问题。
  第一、pHalKeyProcessFunction这个函数指针为何指向了OnBoard_KeyCallback函数。
  在HAL\Commen\ 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为什么标识了GenericApp这个任务?
  由于OSAL是一个支持多任务的调度机制,所以在同一时间内将会有多个任务同时运行。但是从逻辑上来讲,一个事件只能由一个任务来处理。按键事件也不例外。
  那么如何向OSAL声明处理按键事件的任务是GenericApp呢?
  在GenericApp_Init(GenericApp的任务初始化函数)中有这么一个语句:
  {
RegisterForKeys( GenericApp_TaskID );
  }
  RegisterForKeys函数向OSAL声明按键事件将由GenericApp任务来处理。在RegisterForKeys函数中:
  {
registeredKeysTaskID = task_id;
  }
  我想我不用再做多余的解释了,聪明的您肯定可以理解。
五、消息队列
  首先我需要向大家解释清楚消息与事件的联系。事件是驱动任务去执行某些操作的条件,当系统产生了一个事件,将这个传递给相应的任务后,任务才能执行一个相应的操作。但是某些事件在它发生的同时,又伴随着一些附加信息的产生。任务的事件处理函数在处理这个事件的时候,还需要参考其附加信息。最典型的一类便是按键消息,它同时产生了一个哪个按键被按下了附加信息。所以在OnBoard_SendKeys这个函数中,不仅向GenericApp发送了事件,还通过调用osal_msg_send函数向GenericApp发送了一个消息,这个消息记录了这个事件的附加信息。在GenericApp_ProcessEvent中,通过
  {
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
  }
  获取了这样一个消息,然后再进一步处理。
  OSAL在后台维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件以后,从消息队列中获取属于自己的消息,然后进行处理。
  以上就是我就将OSAL这样一个事件驱动的多任务的资源分配机制做了一个简明扼要的介绍,希望对大家有所帮助。

OSAL操作系统实验学习笔记04相关推荐

  1. 《30天自制操作系统》笔记(04)——显示器256色

    <30天自制操作系统>笔记(04)--显示器256色 进度回顾 从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题. 本篇做一个小练习:设置显卡显示256色. ...

  2. 【操作系统/OS笔记04】内存分层体系、地址生成、连续内存分配概论

    本次笔记内容: 3.1 计算机体系结构及内存分层体系 3.2 地址空间和地址生成 3.3 连续内存分配:内存碎片与分区的动态分配 3.4 连续内存分配:压缩式与交换式碎片整理 文章目录 物理内存 计算 ...

  3. 操作系统复习笔记 04 Process 进程

    []进程的概念 1.进程是操作系统执行的各种程序. 2.现在的操作系统多为并发执行,具有许多新的特征.引入并发执行的目的是提高资源利用率. 3.OS的基本特征是[并发与共享]. 4.顺序环境计算机系统 ...

  4. 5、CC2541芯片中级教程-OSAL操作系统(PWM+看门狗)

    本文根据一周CC2541笔记汇总得来-- 适合概览和知识快速索引-- 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

  5. Linux操作系统学习笔记【入门必备】

    Linux操作系统学习笔记[入门必备] 文章目录 Linux操作系统学习笔记[入门必备] 1.Linux入门 2.Linux目录结构 3.远程登录 3.1 远程登录Linux-Xshell5 3.2 ...

  6. 《计算之魂》读书笔记 04

    <计算之魂>读书笔记 04 1.4 关于排序的讨论 [1.4.3]针对特殊情况,我们是否还有更好的答案? [附录]为什么排序算法的复杂度不可能小于 O(nlogn)O(nlogn)O(nl ...

  7. 操作系统概念学习笔记 11 进程同步(一)

    操作系统概念学习笔记 11 进程同步(一) 互相协作的进程之间有共享的数据,于是这里就有一个并发情况下,如何确保有序操作这些数据.维护一致性的问题,即进程同步. 从底层到高级应用,同步机制依次有临界区 ...

  8. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的 ...

  9. 取得 Git 仓库 —— Git 学习笔记 04

    取得 Git 仓库 -- Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区.索引.本地版本库之间的交互:二是本地版本库和远程版本库之间的交互.第一块是基础,第二块是难点. 下面, ...

最新文章

  1. tomcat jvm调优
  2. torch view view_as
  3. 【PHP】 debian apt 安装PHP7.1 安装composer
  4. Spring思维导图,让Spring不再难懂(mvc篇)
  5. 使用Jasmine,Spock和Nashorn测试JVM服务器端JavaScript
  6. datagridview取消默认选中_winform datagridview中的 combobox如何选中默认值?
  7. 计算机视觉论文-2021-06-23
  8. Oracle的配置文件
  9. Windows学习总结(20)——Win10 子系统Linux(Ubuntu 18.04)的安装与卸载
  10. keras笔记(3)-猫狗数据集上的训练以及单张图片多张图片的测试
  11. java 文本框输入监听事件_JAVA GUI 事件监听事件 详解 和 案例.
  12. 国际标准战争的技术真相
  13. 虚拟串口软件:VSPD的使用
  14. 【CF #781 Div2】A-C
  15. python - RSA加密
  16. 自制WIFI放大器(信号真的有增强)
  17. wp/wordpress文章页面添加阅读量/点击量,后台并显示阅读量
  18. 《JS玩算法系列》海王的鱼塘
  19. 魔塔之拯救白娘子~我的第一个VB6+DX8做的小游戏源码~6地图编辑器-初始化
  20. arthas监控服务和入门

热门文章

  1. Gephi实战,从零开始
  2. python读取dbf文件、dbf转xls、入库Postgres
  3. 仿百度医生的智能分诊界面
  4. 1、搭建深度学习图像识别开发环境
  5. 18135usm_更快更安静 佳能18-135mm USM镜头解析
  6. go下载文件The system cannot find the path specified.
  7. 遇到Could not load file or assembly ... or one of its dependencies怎么办
  8. 成就你一生的100个哲理61-70
  9. vue 导出Excel乱码问题解决方案
  10. python里的图像加高斯噪声与matlab中imnoise加高斯噪声的区别?