《嵌入式技术》学习总结报告

目录

一、嵌入式系统简介... 2

1.1 嵌入式系统的定义和特点... 2

1.2 嵌入式系统的构成... 3

1.3 嵌入式系统的应用... 5

1.4 嵌入式系统的工具链... 6

1.5 嵌入式系统的发展趋势... 7

二、嵌入式系统的学习实践... 8

三、实验内容... 9

3.1 ZedBoard-Zynq7000介绍... 9

3.2 软件的安装设置... 10

3.3 main函数分析... 10

3.4 任务管理... 12

3.4.1 任务优先级... 13

3.4.2 任务的堆栈... 14

3.4.3 任务的状态... 14

3.5 就绪任务的管理... 15

3.6 任务调度... 17

3.6.1 任务抢占... 17

3.6.2 轮转调度... 18

3.6.3 调度时机... 19

3.6.4 调度实现... 19

3.7 上下文切换... 20

四、工作总结... 22


一、嵌入式系统简介

1.1 嵌入式系统的定义和特点

嵌入式操作系统(Embedded Operation System,EOS)是一种“嵌入机械或电气系统内部、具有专属功能的计算机系统”,通常要求实时计算性能。被嵌入的系统通常是包含硬件和机械部件的完整设备。相反,通用计算机如个人计算机则设计灵活,以满足广大终端用户的需求。现在常见的很多设备都采用嵌入式系统控制。EOS负责嵌入系统的全部软、硬件资源的分配、任务调度,控制、协调并发活动。它必须体现其所在系统的特征,能够通过装卸某些模块来达到系统所要求的功能。嵌入式系统与对象系统密切相关,其主要技术发展方向是满足嵌入式应用要求,不断扩展对象系统要求的外围电路(如ADC、DAC、PWM、日历时钟、电源监测、程序运行监测电路等),形成满足对象系统要求的应用系统。因此,嵌入式系统作为一个专用计算机系统,要不断向计算机应用系统发展。因此,可以把定义中的专用计算机系统引伸成,满足对象系统要求的计算机应用系统。“嵌入性”、“专用性”与“计算机系统”是嵌入式系统的三个基本要素。对象系统则是指嵌入式系统所嵌入的宿主系统。另外,在理解嵌入式系统定义时,不要与嵌入式设备相混淆。嵌入式设备是指内部有嵌入式系统的产品、设备,例如,内含单片机的家用电器、仪器仪表、工控单元、机器人、手机、PDA等。

嵌入式系统的特点与定义不同,它是由定义中的三个基本要素衍生出来的。不同的嵌入式系统其特点会有所差异。与“嵌入性”的相关特点:由于是嵌入到对象系统中,必须满足对象系统的环境要求,如物理环境(小型)、电气/气氛环境(可靠)、成本(价廉)等要求的特点。与“专用性”的相关特点:软、硬件的裁剪性。满足对象要求的最小软、硬件配置等。与“计算机系统”的相关特点:嵌入式系统必须是能满足对象系统控制要求的计算机系统。与上两个特点相呼应,这样的计算机必须配置有与对象系统相适应的接口电路。

目前,已推出一些应用比较成功的EOS产品系列。随着Internet技术的发展、信息家电的普及应用及EOS的微型化和专业化,EOS开始从单一的弱功能向高专业化的强功能方向发展。嵌入式操作系统在系统实时高效性、硬件的相关依赖性、软件固化以及应用的专用性等方面具有较为突出的特点。EOS是相对于一般操作系统而言的,它除具备了一般操作系统最基本的功能,如任务调度、同步机制、中断处理、文件功能等外,还有以下特点:

(1)可装卸性。开放性、可伸缩性的体系结构。

(2)强实时性。EOS实时性一般较强,可用于各种设备控制当中。

(3)统一的接口。提供各种设备驱动接口。

(4)操作方便、简单、提供友好的图形GUI,图形界面,追求易学易用。

(5)提供强大的网络功能,支持TCP/IP协议及其它协议,提供TCP/UDP/IP/PPP协议支持及统一的MAC访问层接口,为各种移动计算设备预留接口.

(6)强稳定性,弱交互性。嵌入式系统一旦开始运行就不需要用户过多的干预,这就要负责系统管理的EOS具有较强的稳定性。嵌入式操作系统的用户接口一般不提供操作命令,它通过系统调用命令向用户程序提供服务。

(7)固化代码。在嵌入系统中,嵌入式操作系统和应用软件被固化在嵌入式系统计算机的ROM中。辅助存储器在嵌入式系统中很少使用,因此,嵌入式操作系统的文件管理功能应该能够很容易地拆卸,而用各种内存文件系统.

(8)更好的硬件适应性,也就是良好的移植性.

1.2 嵌入式系统的构成

嵌入式系统按形态可分为设备级(工控机)、板级(单板、模块)、芯片级(MCU、SoC)。嵌入式系通常由嵌入式处理器、外围设备、嵌入式操作系统和应用软件等几大部分组成。嵌入式系统与对象系统密切相关,其主要技术发展方向是满足嵌入式应用要求,不断扩展对象系统要求的外围电路(如ADC、DAC、PWM、日历时钟、电源监测、程序运行监测电路等),形成满足对象系统要求的应用系统。因此,嵌入式系统作为一个专用计算机系统,要不断向计算机应用系统发展。

(1)嵌入式处理器

嵌入式处理器是嵌入式系统的核心部件。嵌入式处理器与通用处理器的最大不同点在于其大多工作在为特定用户群设计的系统中。它通常把通用计算机中许多由板卡完成的任务集成在芯片内部,从而有有利于嵌入式系统设计趋于小型化,并具有高效率、高可靠性等特征。嵌入式处理器大概可分为两类。一类是普通微处理器:使用独立的集成电路存储器和外设。另一类是单片机:具有片上外设,降低了功耗、尺寸和成本。嵌入式系统的软件是为某种应用定制的,而不是像个人计算机那样的由终端用户安装的商品,因此可以使用各种不同的基本CPU架构:既有范纽曼型架构也有不同程度的哈佛结构;既有RISC也有非精简指令集处理器;字长从4位到64位甚至更高,当然最典型的仍然是8/16位。多数架构由几家不同的公司生产,使用了大量不同的变量和类型。嵌入式系统也会使用通用型微处理器,但比单片机需要更多外围电路。大的硬件厂商会推出自己的嵌入式处理器,因而现今市面上有1000多种嵌入式处理器芯片,其中使用最为广泛的有ARM 、MIPS、PowerPC、MC6800等。

SoC是一种常见的为超大批量嵌入式系统设计的可配置阵列。它在单个芯片内包含了多处理器、乘法器、缓存和接口,形成一个完整的系统;通过特定用途集成电路或现场可编程门阵列来实现。

(2)嵌入式主板

嵌入式主板一般理解为嵌入在设备里面做控制、数据处理使用的CPU板,也就是设备的“大脑”。嵌入式到设备里面,当然就会对主板的体积以及功耗(嵌入式主板的散热问题)有比较严格的要求。所以一般来讲嵌入式主板会具备尺寸小、高集成度、低功耗等特性。目前嵌入式主板比较常见的一般有两大类:基于X86的嵌入式主板(一般使用INTEL、威盛、AMD或其他产家的X86芯片如:台湾RDC、台湾ICOP等等);基于RISC的ARM 嵌入式主板(由ARM公司授权生产,每个芯片产家各有自己特殊的功能)。嵌入式的ARM板一般都是板载CPU,而基于x86 CPU的主板则不一定。基于RISC 的ARM板一般都是根据产品的要求做具体设计,所以主板在尺寸外观上面通常没有做定义。

PC/104和PC/104+是小型、小批量嵌入式强固系统的标准之一,大多基于x86架构;通常比标准PC要小,而比多数简单的8/16位嵌入式系统要大;使用MSDOS、Linux、NetBSD,或实时嵌入式操作系统如MicroC/OS-II、QNX、VxWorks。有时这些主板也会使用非x86处理器。在某些应用中,小巧、高效并非主要关注点,因而可以使用与x86型PC主板兼容的部件。VIA EPIA系列板卡则可以弥补这个空缺,它兼容PC但是高度集成、体积较小,或提供其他对嵌入式工程师很有吸引力的特性。这种方法的好处是低成本商品也可以使用通用的软件开发工具。用这种方法构建的系统仍然是嵌入式系统,因为它嵌入在较大的设备中、用于满足单一用途。例如ATM和电子游戏机,它们都包含了针对各自应用的代码。

多数嵌入式主板都不是围绕PC设计的,也不使用ISA或PCI总线。如果采用SoC处理器,用标准总线连接分立组件就不是上策,此外软硬件开发环境都可能会很不一样。一种常用的设计模式是采用小型系统模块——也许只有商务卡片大小,容纳高密度的BGA芯片如ARM处理器和外设、用于存储的外部闪存、作为内存的DRAM。模块厂商通常会提供引导软件和操作系统选项,一般包括Linux和一些实时操作系统。这些模块由熟悉专业测试方法的组织大批量生产,配合较小批量的、带特殊应用外设的定制主板使用。

(3)外围设备

外围设备是指在一个嵌入式系统中,除了嵌入式处理器以外用于完成存储、通信、调试、显示等辅助功能的其它部件。根据外围设备的功能可以分为存储器、接口和人机交互。嵌入式系统通过外设与外部通信串行,包括通信接口:RS-232、RS-422、RS-485等;同步串行通信接口:I2C、SPI、ESSI等;USB;多媒体卡:SD卡、CF卡等;网络:以太网、LonWorks等;现场总线:CAN总线、LIN总线、PROFIBUS等;定时器:PLL、捕获比较模块和时间处理单元;分立IO:GPIO;模拟-数字/数字-模拟转换(ADC/DAC);调试接口:JTAG、ISP、ICSP、BDM端口、BITP、DP9端口等。

(4)嵌入式操作系统

在大型嵌入式应用系统中,为了使嵌入式开发更方便、快捷,需要具备一种稳定、安全的软件模块集合,用以管理存储器分配、中断处理、任务间通信和定时器响应,以及提供多任务处理等,即嵌入式操作系统。嵌入式操作系统的引入大大的提高了嵌入式系统的功能,方便了应用软件的设计,但同时占用了宝贵的嵌入式系统资源。一般在比较大型或需要多任务的应用场合才考虑使用嵌入式系统。嵌入式系统常常需要有实时要求,所以嵌入式操作系统往往又是“实时操作系统 ”。早期的嵌入式系统几乎都用于控制目的,从而或多或少都有些实时要求,所以从前“嵌入式操作系统”实际上是“实时操作系统”的代名词。今年来由于手持式计算机和掌上电脑等设备的出现,也有了不带实时要求的嵌入式系统。另外一方面,由于CPU速度的提高,一些原先认为是“实时”的反应速度现在已经很普遍了。这样,一些原先需要在“实时”操作系统上才能实现的应用,现在已不难在常的操作系统上实现。在这样的背景下,“嵌入式操作系统”和“实时操作系统”就成了不同的概念名词。

常见的嵌入式操作系统有: Android、Firefox OS、iPhone OS、uC/OS 、uCLinux、VxWorks、pSOS、Nucleus、PalmOS、Windows CE、Windows XP Embedded、Windows Vista Embedded、嵌入式Linux、ECOS、QNX、Lynx、Symbian、Arm-Linux等。

(5)应用软件

嵌入式系统的应用软件是针对特定的实际专业领域,基于相应的嵌入式硬件平台,并能完成用户的预期任务的计算机软件。用户的任务可能有时间和精度的要求。有些应用软件需要嵌入操作系统的支持,但在简单的场合下不需要专门的操作系统。由于嵌入式应用软件对成本十分敏感,因此,为减少系统成本,除了精简每个硬件单元的成本外,应尽可能的减少应用软件的资源消耗,尽可能的优化。

1.3 嵌入式系统的应用

嵌入式计算机在应用数量上远远超过了各种通用计算机,一台通用计算机的外部设备中就包含了5-10个嵌入式微处理器。嵌入式系统技术具有非常广阔的应用前景,其应用领域可以包括:工业控制:业过程控制、数字机床、电力系统、电网安全、电网设备监测、石油化工系统;交通管理:在车辆导航、流量控制、信息监测与汽车服务方面,嵌入式系统技术已经获得了广泛的应用,内嵌GPS模块,GSM模块的移动定位终端已经在各种运输行业获得了成功的使用;信息家电:这将称为嵌入式系统最大的应用领域,冰箱、空调等的网络化、智能化将引领人们的生活步入一个崭新的空间。即使你不在家里,也可以通过电话线、网络进行远程控制。在这些设备中,嵌入式系统将大有用武之地。家庭智能管理系统:水、电、煤气表的远程自动抄表,安全防火、防盗系统,其中嵌有的专用控制芯片将代替传统的人工检查,并实现更高,更准确和更安全的性能;POS网络及电子商务:公共交通无接触智能卡(Contactless Smartcard, CSC)发行系统,公共电话卡发行系统,自动售货机,各种智能ATM终端将全面走入人们的生活,到时手持一卡就可以行遍天下。环境工程与自然:水文资料实时监测,防洪体系及水土质量监测、堤坝安全,地震监测网,实时气象信息网,水源和空气污染监测。在很多环境恶劣,地况复杂的地区,嵌入式系统将实现无人监测。军事领域:战机、火控系统、导弹、火箭。

1.4 嵌入式系统的工具链

工具对工程师设计开发的成功所起的决定性因素也在提高。应用越来越复杂,工具的选择也越来越重要。现在市场上有些工具是免费的,也有很多商业工具。无论是免费的还是商业的,工具的好坏往往会影响整个工程的最终结果。现在的嵌入式系统开发工具非常多样化,市场分散。目前据不完全统计,全世界嵌入式处理器的品种数已经超过1000多种,流行体系结构有30多个系列,在其上运行的操作系统环境也非常多样化,包括VxWorks、QNX、Linux、 Nuclears、WinCE等等。不仅各种操作系统有各自的开发工具,在同一系统下不同的开发阶段也有不同的开发工具。如在用户的目标板开发初期,需要硬件仿真器来调试硬件系统和基本的驱动程序,在调试应用程序阶段使用交互式的开发环境进行软件调试,在测试阶段需要专门的测试软件进行功能和性能的测试等等。对于设计人员来说,要掌握、驾驭这样庞大的开发体系是一件非常困难的事情,而且编程的复杂度相当大。

在选择工具时,主要需要注意以下几个方面:编译器的性能和稳定性、代码覆盖、仿真(不仅是对CPU的仿真,还有对其他硬件和设备的仿真)。工具链,一般由编译器、连接器、解释器和调试器组成,在嵌入式开发中一般指交叉工具链。在嵌入式开发中,往往在机器A中使用工具链生成可执行程序,而在机器B中执行程序。而机器A和机器B的指令系统往往不同,常见的是利用x86机器上的工具链开发基于ARM或MIPS的嵌入式系统。工具链的构成往往与目标对象和使用工具链的操作系统平台有关。通常构建交叉工具链有如下三种方法:(1)分步编译和安装交叉编译工具链所需要的库和源代码,最终生成交叉编译工具链。该方法相对比较困难,适合想深入学习构建交叉工具链的读者。如果只是想使用交叉工具链,建议使用下列的方法二构建交叉工具链。(2)通过诸如Crosstool等脚本工具来实现一次编译,生成交叉编译工具链。该方法相对要简单许多,并且出错的机会也非常少,建议大多数情况下使用该方法构建交叉编译工具链。(3)直接通过网上下载已经制作好的交叉编译工具链。该方法的优点不用多说,当然是简单省事,但该方法有一定的弊端就是局限性太大,因为毕竟是别人构建好的,也就是固定的,没有灵活性,所以构建所用的库以及编译器的版本也许并不适合你要编译的程序,同时也许会在使用时出现许多莫名其妙的错误,建议慎用此方法。

对于新手而言,直接下载一些厂商提供的集成开发环境(IDE),能够快速入手嵌入式开发。常用的IDE有Keil MDK、Xilinx ISE、Xilinx EDK、TKStudio IDE、Atmel的AVR Studio(for AVR以及AVR32)、Microchip的MPLab(for PIC、PIC18与dsPIC等)、Aiji的EDS(for ARM),南京万利的MedWin(for 8051)、IAR Embedded Workbench等。“整体解决方案是毒药”,我们使用IDE但是不要迷信整体解决方案。

嵌入式系统开发工具的发展已经有二十多年的历史,目前开放性和开放源码成为一股强大的潮流,推动嵌入式系统设计技术向前发展,传统的嵌入式系统开发工具已不能适应这一潮流。最新的发展趋势是,使用Eclipse开放源码集成化开发环境(IDE)平台,采用插件技术,在这样的平台基础上扩展许多开发工具套件。越来越多的嵌入式系统软件供应商将Eclipse平台作为自身工具的基础,推出个性化的开发工具套件,除提供标准的编译器、编辑器、调试器,还提供增强的操作系统内核级调试手段和高级的系统分析工具,如内存泄漏检测、系统性能监控等。总之,嵌入式开发工具将向高度集成、编译优化、具有系统设计、可视化建模、仿真和验证功能方向发展。

1.5 嵌入式系统的发展趋势

在嵌入式系统开发领域,技术发展的最新趋势可以从企业层面、平台层面和部件层面分别观察,其核心是以尽可能快的速度、尽可能低的成本来满足迅速变化的市场需求。(1)以往,嵌入式软件开发的技术问题主要是在工程师和开发团队的层面来解决。不同的工程师和开发项目之间可以共享的资源并没有得到足够的重视和利用,这是业界经常说的一个问题—制造每辆汽车都必须从轮子开始。共享资源、提高软件部件的可重用性,这是整个软件业界几十年来的普遍趋势,只是这个趋势在嵌入式软件领域的步伐比企业软件慢了不少。随着DSO(Device Software Optimization,设备软件优化)概念的提出,嵌入式软件的可重用性将会得到快速提高。这就是嵌入式软件在企业层面的主流趋势。(2)从开发平台层面来看,开放已经成为一种主流思想,对于开放系统首先会让人想到的就是Linux。不过,开放本身不是一种技术,而是一种包容先进技术的思路和方法。新技术往往是从封闭开始,通过走向开放而发挥更大的价值。(3)从部件层面来看,最主流的趋势是多核技术的兴起。这个趋势从处理器芯片巨头的动向可以一目了然。对于嵌入式软件工程师来讲,采用开放、高效并且能够让自己不断积累开发成果的开发平台与工具,让自己能够专注于产品功能特性的开发,而不是花费太多精力去做维护工具和基础性、重复性的功能开发。

二、嵌入式系统的学习实践

目前从事嵌入式开发的主要有两类人。一类是学电子工程、通信工程等偏硬件专业出身的人,他们主要是搞硬件设计,需要开发一些与硬件关系最密切的最底层软件:BootLoader、Board SupportPackage(像PC的BIOS一样,往下驱动硬件,往上支持操作系统),最初级的硬件驱动程序等。他们的优势是对硬件原理非常清楚,不足是他们更擅长定义各种硬件接口,但对复杂软件系统往往力不从心(例如嵌入式操作系统原理和复杂应用软件等)。另一类是学软件、计算机专业出身的人,主要从事嵌入式操作系统和应用软件的开发。如果我们学软件的人对硬件原理和接口有较好的掌握,我们完全也可写BSP和硬件驱动程序。嵌入式硬件设计完后,各种功能就全靠软件来实现了,嵌入式设备的增值很大程度上取决于嵌入式软件,这占了嵌入式系统的最主要工作。目前有很多公司将硬件设计包给了专门的硬件公司,稍复杂的硬件都交给台湾或国外公司设计,国内的硬件设计力量很弱,很多嵌入式公司自己只负责开发软件,因为公司都知道,嵌入式产品的差异很大程度在软件上,在软件方面是最有“花头”可做的,所以搞软件的人完全不用担心我们在嵌入式市场上的用武之地,越是智能设备越是复杂系统,软件越起关键作用,而且这是目前的趋势。

中国的学生能够在大学相关课程中充分对MCU基础知识进行学习,这已经走在了很多国家(甚至包括日本)的前面。但中国新一代工程师面对的主要问题是如何去积累经验。嵌入式系统的开发通常是硬件和软件同时进行的,其在开发过程中出现不良状况的原因有可能是硬件或是软件,有时甚至可能是两者同时发生故障。在这样的状况下,就要求从事硬件的技术人员要相当程度的懂得软件,从事软件的技术开发人员也要在一定程度上懂得硬件。另外,目前该行业存在最终产品的寿命较短的情况,这就意味着每年都有必要开发新的产品。但是从初级阶段进行开发,需要花费大量的开发成本及开发时间。因此,有效地归纳总结现有的开发成果,并有效地投入新开发中加以利用是十分重要的。

很多工程师在设计嵌入式系统的时候往往选择最底层的工具,把绝大部分的时间都花在了底层的细节,而往往忽视了创新性和系统级的把握。工程师无论是为了自身的发展还是为了所设计产品的竞争力,这两点其实都是至关重要的。

首先是“工欲善其事,必先利其器”,若有条件要尽可能采用更快更强的工具进行开发。其次是“磨刀不误砍柴工”,要尽可能挖掘集成开发软件中的资源,如花些时间去了解许多实用辅助软件,如DMA、Cache和片内带宽分析工具等。同时,在至少一个领域中具有深入的技术知识。工程师能够分析问题或是编写程序是远远不够的,他们还要能够利用自己的知识完成独特创新的设计。所以,工程师要体现自身价值,必须有系统性的眼光,能够进行有创新性的设计。在这种情况下,工程师们更多的应该从底层的编程细节中脱身,让工具去处理那些“常规”事务,而工程师本身就可以专注于自己的设计或研究领域,从更高的角度来看待自己在设计的系统,而不是只见树木,不见森林。

三、实验内容

我所在的小组的实验任务,基于ZedBoard-Zynq7000的μC/OS-III代码的分析和调试,重点分析μC/OS-III操作系统的任务调度机制。

3.1 ZedBoard-Zynq7000介绍

ZedBoard是一款基于赛灵思Zynq-7000可扩展处理平台(EPP)的低成本开发板,也是行业首款面向广大开源小区的Zynq -7000 EP可扩展处理平台开发工具包。开发板为基于Linux、安卓、Windows或其它操作系统/实时操作系统的设计开发提供了所需的一切。另外,该平台提供数款扩展连接器,便于用户访问处理系统和可编程逻辑。Zynq-7000 EPP紧密集成了ARM®处理系统和7系列可编程逻辑,充分利用它们的优势,并结合ZedBoard可以开发出独树一帜且功能强大的设计。

图一:ZedBoard-Zynq7000

Zynq7000系列是基于Xilinx的可编程SOC架构,集成了一个双核的ARM® Cortex?-A9 处理器,一个Xilinx可编程逻辑(PL)。Zynq7000集成有仿真芯片,提供了板载USB-JTAG编程、USB-UART,方便开发者进行调试。

图二:Zynq-7000 AP SoC系统框架

3.2 软件的安装设置

(1)安装UART驱动。目前只有32位版本的,在windows 7以下可安装。在windows 8安装失败。

(2)安装Vivado Design Suite.选择默认安装即可。

(3)uCOS-III项目的构建。Micrium-ZC702-uCOS-III源码压缩包中有“Micrium ZC702 uCOS-III Readme.pdf”说明书。打开Xilinx SDK,安装说明书里面的步骤一步一步进行配置即可完成uCOS-III 项目的构建。

3.3 main函数分析

结合《Micrium-uCOS-III-UserManual》对代码进行静态分析,最好的方法是找到程序入口函数main(ZC702_uCOSIII\Application\app.c),然后在main函数内逐行代码分析从而对程序的流程有一个大体的了解。在分析过程中碰到关键或感兴趣的函数或结构可以进一步跟入分析。

int  main (void)

{

    OS_ERR   os_err;

    Xil_DCacheDisable();//关数据cache

    Xil_ICacheDisable();//关 instruction cache

    Reset_Handler();

                                                                /* Scatter loading is complete.                         */

                                                                /* Now the caches can be activated.                     */

    BSP_BranchPredictorEn();                                    /* Enable branch prediction.                            */

    BSP_L2C310Config();                                         /* Configure the L2 cache controller.                   */

    BSP_CachesEn();                                             /* Enable L1 I&D caches + L2 unified cache.             */

    CPU_Init();                                                 /* Initialize the uC/CPU services                       */

    BSP_Init();

    OSInit(&os_err);                                            /* Initialize uC/OS-III.                                */

    OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,//任务控制块                /* Create the start task                                */

                 (CPU_CHAR   *)"Startup Task",

                 (OS_TASK_PTR ) AppTaskStart,//任务地址

                 (void       *) 0,

                 (OS_PRIO     ) APP_CFG_TASK_START_PRIO,

                 (CPU_STK    *)&AppTaskStartStk[0],

                 (CPU_STK_SIZE) APP_CFG_TASK_START_STK_SIZE / 10u,

                 (CPU_STK_SIZE) APP_CFG_TASK_START_STK_SIZE,

                 (OS_MSG_QTY  ) 0u,

                 (OS_TICK     ) 0u,

                 (void       *) 0,

                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),

                 (OS_ERR     *)&os_err);

    OSStart(&os_err);                                           /* Start multitasking (i.e. give control to uC/OS-III). */

    for(;;) {

    }

}

对main函数内调用的一些函数和结构做进一步分析,可知OS_TCB(\ZC702_uCOSIII\uCOS-III\Source\os.h)是系统用于表示一个任务的结构,是系统的核心数据结构之一。OSInit(\ZC702_uCOSIII\uCOS-III\Source\os_core.c)函数是值得重点看的函数。main->OSInit->OS_IdleTaskInit,通过调用OS_IdleTaskInit的调用创建了系统的一个名叫“uC/OS-III Idle Task”的任务,其优先级值为31。这个任务相应执行的函数,基本没有做任何操作,简单对OSIdleTaskCtr、OSStatTaskCtr两个系统计数进行增加。OSIdleTaskHook()是用户可以自定义的函数,uCOS-III代码中有大量这种Hook函数,方便用户进行功能扩展。当然用户也可直接对任意代码进行修改,但是对系统Hook函数进行重写能够很好地保持原有uCOS-III系统的完整性。

void  OS_IdleTask (void  *p_arg)

{

   ……

    while (DEF_ON) {

        CPU_CRITICAL_ENTER();

        OSIdleTaskCtr++;

#if OS_CFG_STAT_TASK_EN > 0u

        OSStatTaskCtr++;

#endif

        CPU_CRITICAL_EXIT();

        OSIdleTaskHook(); /* Call user definable HOOK  */

    }

}

main->OSInit->OS_TickTaskInit创建了一个名叫"uC/OS-III Tick Task"的任务(优先级为10),任务的创建都得通过OSTaskCreate来创建,main函数中又创建"Startup Task"任务。这三个任务对应的运行函数内都包含了while死循环,因此系统运行起来后至少有三个任务,分别是Idle任务、Tick任务、Startup任务。

3.4 任务管理

μC/OS-III支持多任务,理论上可以支持任意多个任务,但实际通常受限于系统内存的多少。所有任务管理都是基于OS_TCB结构来进行的,接下来我们详细分析OS_TCB的定义(uCOS-III\Source\os.h),可知一个任务有优先级、状态、堆栈,下面分别予以介绍。

3.4.1 任务优先级

μC/OS-III的优先级共有32个(0-31),数值越小优先级越高,一般地优先级0和31保留给系统使用。中断处理任务的优先级最高,空闲任务的优先级最低。

图三:μC/OS-III任务优先级

3.4.2 任务的堆栈

在利用OSTaskCreate创建新任务前,必须先创建好任务堆栈,如Startup任务就用如下代码来分配堆栈:

static CPU_STK      AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE];

堆栈主要的大小是可以由用户自己定义(需考虑任务里函数的嵌套层数和局部变量占用空间多少。另外在嵌入式开发中,应尽可能避免递归的调用),堆栈是向下增长的。堆栈溢出(这里的溢出是指堆栈空间不够用)是操作系统必须考虑的问题,μC/OS-III有三种解决方法:(1)利用内存管理单元(MMU)或内存保护单位(MPU);(2)利用某些CPU提供的堆栈溢出检测功能;(3)基于软件实现的堆栈溢出检测。前两者都依赖于硬件提供该功能。

图四:μC/OS-III堆栈结构

3.4.3 任务的状态

OS_TCB结构中的TaskState表示任务的状态。在μC/OS-III中,从用户的角度来看任务有五个状态:静止(Dormant)、就绪(Ready)、运行(Running)、挂起(Pending)、中断(Inrerrupted)。任务的状态是动态转换的,构造好OS_TCB结构后,此时任务处于静止状态,OSTaskCreate函数中调用OS_TaskInitTCB函数将任务状态设为OS_TASK_STATE_RDY并调用OS_RdyListInsertTail将任务插入相应优先级的就绪队列中。

图五:任务状态的转换

3.5 就绪任务的管理

任务在运行前都处于就绪状态或中断状态,CPU的调度基本上就是以某种策略从所有就绪任务中挑选一个任务运行。μC/OS-III利用一个就绪优先级位图和一个就绪队列表来管理就绪任务。

OSInit->OS_PrioInit()中对任务优先级位图表进行了初始化。μC/OS-III 有OS_CFG_PRIO_MAX优先级(见os_cfg.h),优先级值越小表示优先级越高。优先级0表示最高级别,优先级OS_CFG_PRIO_MAX-1最低(在本实验中OS_CFG_PRIO_MAX=32). 如果某个优先级上存在就绪的任务,那么就在位图表中相应的位设置为1,否则设置为0.位图宽度可以是8bit、16bit、32bit,这样依赖于CPU_DATA的值即依赖所用CPU的特性(本实验中是32bit,优先级总数为32,因此位图大小为32*1)。查找最高就绪的优先级,只需在优先级位图中找第一不为0的bit。

OS_PRIO OS_PrioGetHighest (void)

{

CPU_DATA *p_tbl;

OS_PRIO prio;

prio = (OS_PRIO)0;

p_tbl = &OSPrioTbl[0];

while (*p_tbl == (CPU_DATA)0)

{

prio += DEF_INT_CPU_NBR_BITS;)

p_tbl++;

}

prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl);

return (prio);

}

图六:就绪优先级位图

就绪列表实际上是一包含OS_CFG_PRIO_MAX个OS_RDY_LIST(见os.h)类型实体的数组(OSRdyList[])。OS_RDY_LIST实体包含三个成员:Entries、TailPtr、HeadPtr。Entries表示该优先级别上任务个数,TailPtr和HeadPt用于创建包含该所有优先级别上的就绪任务的双向链表。代码中包含了OS_RdyListInit()、OS_RdyListInsert() 、OS_RdyListInsertHead()、OS_RdyListInsertTail()、 OS_RdyListMoveHeadToTail()、OS_RdyListRemove()等函数用于操作就绪列表。

图七:任务创建后加入就绪列表

3.6 任务调度

3.6.1 任务抢占

在μC/OS-III中,任务调度器(scheduler)即分发器(dispatcher)负责任务的调度。μC/OS-III使用基于优先级、抢占式的调度策略。抢占是指当由于某种原因使更高优先级任务变成就绪状态(较当前任务),那么调度器立即将CPU分别给该更高优先级任务,而当前任务变成挂起状态。类似地,当中断服务程序(ISR)将一个处于挂起状态的更高优先级任务唤醒时,当前被中断的任务保持挂起状态,被唤醒的那个更高优先级任务得到执行。如图八,是一种直接的抢占方式,μC/OS-III还支持一种较Post Defferd即延时的抢占方式(图九)。延时抢占方式,多一步ISR Handler(在操作系统中被称为中断软处理,主要目的是减少关中断的时间) 用于处理在ISR(中断硬处理)中来不及处理事情。

图八:任务抢占

图九:带延迟处理的任务抢占

3.6.2 轮转调度

如果未开启轮转策略,正在执行的任务除非主动放弃执行(包括等待某些时间、或者调用睡眠函数)或者被抢占,那么同级别的任务得等到该任务执行完采用机会执行。当同一个优先级上有两个以上就绪任务时,μC/OS-III 采用时间片策略使得该优先级上所有任务得到依次轮转执行。当然如果一个任务的时间片未用完,也可主动放弃CPU使得下个任务得到执行。轮转调度策略不是必须的,μC/OS-III允许用户开启或关闭该策略。

图九:时间片调度

3.6.3 调度时机

调度可能发生时机包括:(1)一个任务给其他任务发送(send)消息或信号时;(2)任务 OSTimeDly() 或者 OSTimeDlyHMSM();(3)任务调用等待函数OS???Pend()等待尚未发生的任务;(4)任务放弃挂起:其它任务调用OS???PendAbort()改变该任务状态;(5)任务创建时:创建的任务可能拥有比当前任务较高优先级;(6)任务被删除时:如果该任务就是当前任务;(7)内核对象被删除时;(7)任务优先级发生改变时;(8)任务调用OSTaskSuspend()挂起自己;(9)任务调用OSTaskResume()唤醒了其它任务;(10)所有嵌套的ISR退出时:调度由OSIntExit()发起而不是 OSSched();(11)调度器被解锁;(12)任务调用OSSchedRoundRobinYield()主动放弃时间片;(12)用户调用了OSSched();(13)任务时间片用完了。

3.6.4 调度实现

调度器的实现在函数OSSched() 和 OSIntExit()中。其中,OSSched()由任务级别代码发起而OSIntExit()是有中断服务程序发起。任务的调度是基于前面提及的就绪任务的相关数据结构:就绪优先级位图和就绪队列。

图十:调度时主要用到的数据结构

3.7 上下文切换

上下文切换:当μC/OS-III要执行另一任务时,需要将当前任务使用的那些CPU寄存器值保存当前任务堆栈中,同时加载要执行的任务的上下文给CPU的寄存器进而开始另一任务的执行。上下文切换存在一些开销,通常CPU寄存器越多(任务用到的寄存器越多),开销就会越大。上下文切换的时间开销取决于有多少个CPU寄存器需要被存储和载入。

图十一:上下文在堆栈中的存储

存在两种上下文的切换:普通任务的上下文切换、中断服务程序的上下文切换。前者在函数OSCtxSw()中实现(其实是由宏OS_TASK_SW()调用,后者在函数OSIntCtxSw()中实现。这两个函数都是采用汇编语言实现,因此是CPU相关的(见uCOS-III\Ports\ARM-Cortex-A\Generic\GNU\ os_cpu_a_vfp-d32.S)。这里只介绍OSCtxSw()的实现。

OSIntCtxSw:

    BL      OSTaskSwHook                 @ OSTaskSwHook()@

    MOVW    R0, #:lower16:OSPrioCur  @ OSPrioCur = OSPrioHighRdy@

    MOVT    R0, #:upper16:OSPrioCur

    MOVW    R1, #:lower16:OSPrioHighRdy

    MOVT    R1, #:upper16:OSPrioHighRdy

    LDRB    R2, [R1]

    STRB    R2, [R0]

    MOVW    R0, #:lower16:OSTCBCurPtr  @ OSTCBCurPtr = OSTCBHighRdyPtr@

    MOVT    R0, #:upper16:OSTCBCurPtr

    MOVW    R1, #:lower16:OSTCBHighRdyPtr

    MOVT    R1, #:upper16:OSTCBHighRdyPtr

    LDR     R2, [R1]

    STR     R2, [R0]

    LDR     SP, [R2]         @ SP = OSTCBHighRdyPtr->OSTCBStkPtr@

    OS_CPU_ARM_FP_REG_POP R0        @ RESTORE NEW TASK'S CONTEXT:

    LDMFD   SP!, {R0}          @    Pop new task's CPSR,

    MSR     SPSR_cxsf, R0

    LDMFD   SP!, {R0-R12, LR, PC}^     @    Pop new task's context.

如图,当有更高优先级就绪任务需要被执行,任务调度器就会调用OSCtxSW()。主要有以下步骤:

(1) OSTCBCurPtr指向当前正运行的任务对应的OS_TCB ,然后任务级调度器调用OSSched().

(2通过OSTCBHighRdyPt指针,可以找到即将要运行的任务的OS_TCB;

(3) OSTCBHighRdyPtr->StkPtr指向将要被执行的任务的堆栈的顶部;

(4)执行任务上下文切换,将CPU的相关寄存器存储到当前任务(即将被调出CPU的任务)的堆栈。由于保存了上下文(以栈帧形式存储),将来该任务可以被恢复现场,以便继续运行。

(5) 调用OSSched()后,CPU堆栈指针TSP会指向任务的堆栈。

图十二:OSCtxSw()所执行操作

四、工作总结

学习嵌入式,显然应偏重于嵌入式软件,特别是嵌入式操作系统方面。对于搞嵌入式软件的人,最重要的技术显然是:(1)掌握主流嵌入式微处理器的结构与原理。(2)必须掌握一个嵌入式操作系统。(3)必须熟悉嵌入式软件开发流程并至少做过一个嵌入式软件项目。

通过本门课程的学习,我对嵌入式系统有了较直观的理解,并加深了操作系统一些概念的理解。但毕竟时间有限,希望可以在后续的进一步学习中加强实践,希望早日能在嵌入式领域做一些开发工作。

相关推荐