深入理解嵌入式操作系统的快速方法(第2篇)

2017年09月03日 07:53 | 1523次浏览

想要理解微信硬件蓝牙开发,必须对嵌入式操作系统由深入理解,本文主要基于TI CC254X OSAL的分析。

当工具链配置完成后,Source Insight向你展示一份源码工程,不借助百度和开发文档,能否在一两个小时内理解源码的组成框架和接口,进行快速开发?

在笔者过往撰写的博文中,一直在倡导两个嵌入式学习和开发理念:提高嵌入式系统架构和软件层次形成大局观;掌握从需求的角度去理解新系统和技术这个方法论。在软件大局观作为学习新系统的背景知识的基础上,从软件需求的角度入手就能快速理解和掌握一个全新的系统。本文以TI蓝牙BLE CC254x的源码库和工程为例进行分析研究。

一、操作系统相关概念

1. 操作系统

最常见的通用操作系统是Windows和Linux。操作系统给应用层提供进程调度、进程间通信、内存管理、驱动管理、文件管理、中断管理、时间管理等相关接口。在嵌入式领域,除了通用的嵌入式Linux操作系统,更多的是定制型的操作系统。定制型操作系统一般都是闭源且高度裁剪移植的系统,根据应用的需求提供必要的功能和管理模块。资源丰富型系统的应用层和内核层会使用不同的CPU运行模式,只有内核层能够访问硬件资源,而资源紧缺型系统的应用层和内核都是运行同一模式,都能够访问硬件资源。

嵌入式系统中有单任务操作系统和多任务操作系统。单任务操作系统一般用于简单的电子控制和处理产品中,如玩具、工业控制、家电等等。而多任务操作系统则用于较为复杂且功能丰富的电子产品中,如手机、视频监控等等。

在一般的简单的电子产品中,操作系统只是一种层次概念,其管理的功能显得比较弱,有时甚至为了效果和内存而进一步削减它的存在,但是软件层次能够让软件工程显得更加有序和易于维护和管理。只要中断管理、GPIO和驱动、时间管理单独成模块,我们都可以认为它形成操作系统。


2. 内核

内核是多任务操作系统的核心,最基本的功能就是任务调度和任务间通信。既然是多任务并发运行,而CPU只有一个(假设是单核CPU),就必须做好任务的调度和任务间的同步和通信。Linux是一个操作系统(OS),其内核(kernel)除了任务调度、任务间通信,还有内存管理、网络接口等;UCOS是常见的多任务内核,多用于资源有限型嵌入式系统,它提供了优先级抢占的任务调度和信号量、邮箱、消息队列等任务间通信,尽管UCOS也提供了内存管理,但完全可以裁剪掉这个功能。


3. 并发

一个系统可能有多个独立的任务,但是否是多任务操作系统,是以这些任务是否并发为标准。所谓并发,是指各个独立的任务是否能够得到公平的运行机会。如果一个任务必须要等待另一个任务完成才能执行,那两者是串行运行;如果一个任务能够在另一个任务执行过程中抢占CPU,才算是并发执行。并发执行跟任务上下文密切相关,单任务操作系统只有一个用户上下文,而多任务操作系统的每个任务都有一个上下文,任务切换就是对上下文的切换。


4. TI CC254x需求分析

TI CC254x是在8051核上集成蓝牙BLE4.0低功耗的单芯片,可以预想它是一个资源紧缺型的嵌入式系统;同时CC254x是为了完成蓝牙连接和简单的控制功能(即GAP profile和GATT profile),因此TI提供的库也重点围绕蓝牙连接和控制传输设计,而没有通用操作系统所支持的文件、驱动管理等功能;另外蓝牙协议是分层协议栈,各层都有独立的的业务和处理流程,它会是多任务应用吗?

TI向用户提供了一个叫做OSAL(操作系统抽象层)的编程框架,除了蓝牙相关的底层协议不透明,OS相关的任务调度和通信、蓝牙高层协议都是用户透明的。诚如以上对单任务操作系统的分析,OASL也是一个较弱的操作系统,只包含了调度和通信的内核,和一个硬件抽象层。但不妨碍我们把它当成一个操作系统去理解。


5. 如何快速开发

理解工程的架构、任务调度、任务通信、用户消息的输入和输出之后,就能进入快速开发阶段了。基于以上分析,要在OASL上进行快速开发,我们需要重点关注OSAL的任务编调度、任务间通信、用户消息输入和处理。对于蓝牙协议栈相关的内容(GAP、GATT等应用profile),不是本文的重点,以后再另外撰文阐述。


二、OSAL的任务调度

1. 背景知识

嵌入式系统中每个任务都是while(1)的大循环。在单任务中可能还会有不同的场景,例如每个功能菜单都可以认为是一个场景,每个场景都有自己的消息大循环,但本质上还是一个while(1)的循环。而在多任务操作系统中,每个任务都有自己的消息循环。任务间的协同运行依赖任务的主动释放控制权(主动休眠)和被动的挂起(如等待另一个任务的信号量)。每个任务都必须要在就绪状态才可能得到运行的机会。

2. OAL应用需求

OSAL支持蓝牙协议栈,包括链路层、适配层、GAP连接管理、ATT属性管理、GAP profile应用、GATT profile应用,此外还要支持用户的按键消息输入处理等。各层均有独立的分工和职责。到底是以多任务来支持这种模型,还是单任务的多场景呢。拭目以待!

3. OSAL代码分析(以SimplePeripheral为例)

1)从main开始,main->osal_init_system-> osalInitTasks,如下图:

tasksCnt是系统的任务个数,如下图:

数组中每个成员都是一个任务处理过程。先给tasksEvents申请空间,tasksEvents是做什么的,先不管它。

osalInitTasks接着就是进行各个任务的初始化,这时还没有进入消息循环。我们注意到taskID在每个任务初始化后,taskID都会加1,而在每个任务初始化的开始都会记录传入的taskID并保存,作为任务的标记。

2)main-> osal_start_system,如下图:

我们看到大循环了。继续看:

可见,taskID除了任务标识,还有优先级的含义。tasksEvents数组的每个元素即对应每个任务的就绪状态,0为没有事件需要处理,非0代表有事件需要处理。

跟踪一个任务处理流程,我们发现里面并没有while(1)循环,都是等到某个任务执行完才返回,返回之后回到osal_start_system大循环。因此该系统本质上是一个单任务操作,只有一个上下文。但是OASL的编程模型看起来确实蛮想多任务系统的,或者其借鉴了很多多任务编程的思维,如在OASL看到了很多UCOS的影子。

为什么要将返回值设置到tasksEvents中,就是因为其本质是一个单任务循环,如果某个子任务时间执行过长,会影响更高优先级的任务的响应变慢,影响整体性能。因此如果一个任务执行比较长,宜进行分割,在返回值那里设置继续处理事件,然后返回大循环,看看有没有更高优先级的任务需要执行。

三、OASL的任务间通信

1. 背景知识

Linux的任务间通信包括消息队列、共享变量,有信号量来同步;UCOS也有消息队列、邮箱和条件变量、信号量等手段来进行通信和同步。OSAL是单任务操作系统,需要由用户层的各个任务处理过程自己约束保证同步。

消息一般包括按键消息、时间消息、绘图消息等等,信号量用于任务间的同步,并不属于消息传递。

2. OSAL的代码分析

tasksEvents的元素除了作为就绪状态,还有另外一个作用就是作为参数传入该应用处理过程。我们跟踪一个处理过程:

可以看到,event可能是用户自定义的事件,如这个任务要做的周期性的事情和启动连接的事件。另外,event还可能是系统消息事件(SYS_EVENT_MSG),它是什么?消息和事件在OSAL中是怎么理解的。

其实把事件作为消息的类型更容易理解,事件用一个16位的整型来表示,从代码来看,是每个比特表示一种事件,其最多只能表示16种事件。对于按键消息,由于其可能有多个不同的键值,因此不宜通过多个事件来表示,而是用一个按键事件来表示,然后按键的键值通过消息队列来传递。

通过跟踪消息发送osal_msg_send( uint8 destination_task, uint8 *msg_ptr )和消息接收osal_msg_receive( uint8 task_id )接口,可以发现,所有的任务共用一个链表型的消息队列,每个结点记录消息的事件类型/值和接收任务的taskID。

那么事件的发送接口呢?如下图

即往tasksEvents的taskID下标成员写入对应的事件值。这样在之后的大循环中目标task就会得到执行的机会。消息发送osal_msg_send不仅将消息插入到全局消息队列链表,而且最后也会调用osal_set_event接口填入SYS_EVENT_MSG事件。

此外,还有一个定时发送事件的接口osal_start_timerEx,其在规定的时间到达后才会发送该事件,以让目标taskID得到执行事件的机会。


四、OSAL消息处理

OSAL提供了一个HAL硬件抽象层,咱们主要分析按键输入和串口输出就好了。

利用TI CC2540进行蓝牙方案开发还是几个前,当时一个小时就理解了OSAL,但现在把当时的理解思路还原整理出来花了差不多四个小时,主要是因为在撰写过程中不断地考虑如何让读者更好地理解本文的学习思路,即总结软件架构方面的知识,和如何从需求的角度去理解新的系统。呜呜,还是把OSAL的消息处理流程留到下一篇好了。

接下来将会陆续推出物联网-微信蓝牙和wifi接入相关的技术分享。敬请关注!


小说《我是全球混乱的源头》

感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程