基于ARM的嵌入式操作系统微内核设计外文翻译资料

 2023-01-15 16:44:31

基于ARM的嵌入式操作系统微内核设计

摘要:本文详细介绍了在Linux平台上使用GNU工具链开发的基于ARM的嵌入式操作系统微内核的设计和实现。该微内核包括三层架构(启动层、核心层和任务层),多任务调度(实时优先级和时间片循环),IRQ中断服务程序,SWI中断服务程序,系统调用,和基于微内核体系结构构建的任务间通信。在这个微内核的基础上,更多对实用操作系统至关重要的组件可以添加进来,比如文件系统和TCP/IP协议处理,以形成真实、实用的多任务微内核嵌入式操作系统。

关键词:嵌入式操作系统;微内核;ARM; 多任务调度;任务间通信

简介

随着电子和计算机技术的飞速发展,操作系统已经成为嵌入式系统的必要组成部分。尽管已经开发并使用了一些著名的嵌入式操作系统,例如VxWorks,QNX,嵌入式Linux,WindowsCE,uC / OS,eCos等,但是由于其专用功能,仍需要各种类型的嵌入式操作系统来满足行业的要求[1]。显然,有必要加强对嵌入式操作系统设计和开发的研究。本文作者开发的基于ARM的嵌入式操作系统微内核只是一个很好的尝试,其中任务间通信奠定了微内核的基础。该嵌入式操作系统微内核的主要贡献是:

  • 实时和分时调度。众所周知,实时系统是嵌入式系统的重要应用领域。另一方面,也普遍需要分时任务,例如各种基于Web的嵌入式控制或管理系统。本文描述的微内核旨在实现两种调度。优先级计划用于实时任务,而轮询则用于分时任务。
  • 三层体系结构。内核被设计为三层系统,并且内核的所有操作均精心指定给它们中的每一层。例如,启动层负责操作系统的启动和初始化,核心层负责内核的内部操作。任务层处理系统级任务和用户级任务。任务间通信例程完成分时任务之间的消息传输。通过这种架构,它实际上被设计为微内核,在此基础上可以添加更多组件以形成实用的微内核嵌入式操作系统。
  • 对课程教学和实验有利。与嵌入式系统相关的课程已经成为本科计算机专业的必要组成部分。本文描述的微内核是使用基于C和ARM汇编中的GNU工具链[7]设计的。整个内核的代码数仅约3,000行,并且简单,基础且可扩展。它可以提供给学生进行学习和研究,因此有利于课程教学和实验。

本文详细介绍了该微型内核的设计和实现,包括内核的体系结构(启动层,核心层和任务层),多任务调度策略(实时优先级和轮循机制),IRQ中断服务程序,SWI中断服务程序,系统调用以及基于微内核体系结构的任务间通信。

嵌入式操作系统微内核的体系结构

可以通过将操作系统划分为更小的部分来创建操作系统,每个部分都应该是明确定义的,并具有经过仔细定义的输入,输出和功能[5]。通常,操作系统的体系结构彼此不同,两种众所周知的类型是微内核和单片内核。

微型内核可能非常小,仅包含操作系统的基本功能和必要功能。Windows和Minix是具有此类体系结构的示例。在另一个极端,单片内核可以将操作系统的几乎所有功能合并到一个非常大的内核中。UNIX和Linux的大多数版本都具有这种内核。对于嵌入式操作系统,两种体系结构都在使用。

本文所描述的嵌入式操作系统微内核只是课程教学的最低要求,其架构尽可能分为三层:启动层,核心层和任务层。微内核的总体架构如图1所示。

图1.嵌入式操作系统微内核的总体架构

启动层

启动层的任务是首先从一种辅助存储器(例如FLASH存储器,SD卡或硬盘等)引导系统,然后为内核提供适当的硬件环境。考虑到可移植性,该层通常被设计为独立的,甚至分离的模块,例如嵌入式Linux或WindowsCE等的模块[2]。为了适应各种硬件环境,本文描述的启动层被设计为自适应的内置启动加载程序,它可以自动识别不同的硬件结构,相应地加载内核映像,然后适当地引导系统[6]。该层的核心包括一些硬件组件的初始化,例如看门狗,内存和系统时钟,自动识别启动地址,为每种工作模式设置堆栈,加载内核代码,启动MMU,清除BSS,等等。

该层由两个程序文件组成,一个在程序集中,另一个在C中。实际上,一个程序集构成了微内核的整个程​​序框架。为了从闪存中加载内核代码,设计了一些访问闪存的必要功能。由于在NAND FLASH存储空间的低端只有4K SDRAM空间,称为垫脚石[8],因此值得注意的是,用于引导的关键代码(包括加载内核代码的功能)地址范围必须低于4K。由于篇幅所限,这里不再赘述。

核心层

顾名思义,该层由内核的核心组件组成,包括IRQ中断服务程序,SWI中断服务程序,系统调用,多任务调度程序,任务间通信以及其他必要功能,例如初始化内核,创建任务等。

IRQ中断服务程序的任务是处理硬件的中断请求。这也是抢占式调度的基础。另一方面,SWI中断服务程序处理由内核引起的软件中断,例如系统调用。

多任务调度程序在任务之间执行抢占式调度,包括两种策略,即实时优先级和轮询。

任务层

该层由内核级任务和用户级任务两种任务组成,其中Task_Sys是通过ITC(任务间通信)机制执行其他任务之间通信最重要的任务。在当前阶段,它是除Task_Idle之外唯一的系统任务。基于ITC,可以在不久的将来添加嵌入式操作系统必不可少的其他任务,例如文件系统。为了显示效果,还设计了一些用户级任务,这些任务将在本文后面进行描述。

设计嵌入式操作系统微内核的关键技术

多任务调度

为了研究的目的,设计了两种典型的多任务调度策略,它们是实时优先级和轮询。

1) 任务控制块(TCB)的结构

任务控制块(TCB)表示任务的存在。TCB最重要的字段是任务的状态,任务可以处于多种状态,例如RUNNING意味着可以运行,SENDING意味着向其他任务发送消息,或者RECEIVING意味着从另一个任务接收消息。对于优先级计划,字段prio用于存储任务的优先级值,而对于循环调度,多了两个字段,分别称为slice和ticks,用于存储指定的时间片及其当前剩余值。还需要其他必填字段,例如与TCB表,任务间通信等有关的内容,将在后面进行介绍。

2) 两种调度策略

优先级调度是根据位图设计的,与uC / OS [3]中的方法类似,因此不再详细描述技术细节。

为了与实时优先级计划兼容,循环调度的任务均以最低优先级指定,命名为OS_LO_PRIO。这意味着只有当所有实时任务都被阻止时,才能计划分时任务。如果没有任何任务准备好,那么将调度空闲任务,实际上这只是一个由空白循环组成的伪函数,什么也不做[4]。

有几种情况可能会导致任务调度,例如当较高优先级的任务准备就绪时,创建任务时,结束任务时,在发送或接收消息期间任务被阻塞时,以及发生IRQ等。

3) 创建任务

任务生命周期的开始是它的创建,包括三个部分的操作,即初始化任务控制表(TCB),初始化任务堆栈以及为新任务重新启动调度器。

第一部分包括初始化TCB的相关字段,例如状态位和优先级。对于分时任务,还需要初始化时间片字段,消息字段和与ITC相关的字段。

由于每个任务必须具有其自己的堆栈,并且在任务切换时,必须保存相应的寄存器值,任务堆栈的结构尤为重要。

任务创建的第二部分只是初始化堆栈,该函数的C代码如图2所示,其中get_cpsr是一个汇编函数,用于获取寄存器cpsr的当前值。

图2.初始化任务堆栈的C代码

IRQ中断服务程序

IRQ的主要功能是定期为调度器和软件计时器提供时钟滴答,调用调度程序以切换任务。 IRQ中断服务程序的性能将影响整个系统的质量,因此应尽可能详细地进行设计。中断服务程序的框架使用ARM汇编语言编写,而核心例程使用C语言编写。

由于任务之间的切换是通过中断实现的,因此第一个任务(空闲任务)的开始和任务级别的调度(例如,任务创建或任务退出)实际上是一种中断模拟,因此其代码为类似于IRQ中断服务程序。由于这个原因,本文中描述的IRQ中断服务程序被设计为上述两个功能,即空闲任务启动和任务切换。这两个功能与IRQ中断服务程序紧密结合在一起,从而使代码简洁而紧凑。

中断服务程序首先在C中调用中断服务程序例程,该例程的返回值说明是否需要切换任务(实时和分时)。如果是,则存储一些相关值,然后调用名为task_renew()的C例程,该例程的返回值为新任务的TCB。将堆栈指针设置为新任务的堆栈后,从中断服务程序返回并完成任务切换。

SWI中断服务程序

SWI中断服务程序是嵌入式操作系统微内核的基础,通过该内核可以实现系统调用和任务间通信。值得一提的是,当参数数量不大于4时,SWI的参数存储在r0,r1,r2和r3中。这意味着在这种情况下,参数可以由调用的函数直接使用,而不是被压入堆栈。由于寄存器r0将用于存储返回值,因此不需要保存它,因此仅将r1-r12和r14压入堆栈进行存储。

SWI的真正处理是通过C中的一个名为sysc_sched()的函数实现的,该函数实际上是系统调用的主要例程。调用函数后,返回值存储在寄存器r0中。

本文描述的微内核的系统调用要求完成两个功能,即格式化输出和任务间通信,格式化输出名为cprintf()。使用cprintf(),用户任务可以通过UART显示消息,而不必担心同步和排斥的问题。

任务间通信(ITC)

任务间通信是分时多任务环境的重要机制。为了实现任务间通信[5],需要TCB中的某些字段。

  • p_msg:指向消息缓冲区的指针。该缓冲区是由发送或接收消息的任务而不是内核提供的,并且所有相关的消息缓冲区都一一链接起来以形成消息队列。显然,这种队列的长度不受限制。这意味着任务间通信的机制与用于实时任务的邮箱或消息队列的机制不同。
  • recv:任务T等待从中接收消息的另一个任务的任务ID。
  • send:任务T向其发送消息但尚未到达的另一个任务的任务ID。
  • q_send:如果有多个任务(例如A,B和C)在任务T尚未准备好接收消息的情况下向任务T发送消息,则任务A,B和C将形成上述队列,并且任务T的q_send字段指向队列中的第一个任务。
  • q_next:上面提到的三个任务A,B和C根据时间顺序形成队列。假设目标任务为T,则T的TCB中的字段q_send指向A,A的TCB中的q_next指向B,B的q_next指向C,而C的q_next指向NULL。显然,通过每个任务的TCB字段q_next,形成了一个任务的链接表,而任务T的字段q_send是该表的头指针,通过该表可以顺序地接收消息。

当任务通过任务间通信发送或接收消息时,其TCB字段状态可能处于以下三种状态之一:

  • RUNNING:任务正在运行或准备运行;
  • SENDING:任务处于发送消息状态。因为消息尚未到达目的地,所以任务被阻塞;
  • RECEIVING:任务处于接收消息状态。由于尚未收到消息,因此任务被阻塞。本文中微内核的任务间通信算法如图3和图4所示。

源任务A准备消息M。

通过接口函数sendrecv()调用函数msg_send()。

检查是否发生死锁。

检查目标任务B是否正在等待从源任务A接收消息。

如果是,则将消息复制到任务B,然后任务B离开阻塞状态以继续运行。否则,将阻止任务A并将其添加到任务B的发送队列中。

图3.任务A向任务B发送消息M的算法

目标任务B准备一个空白结构M以接收消息。

通过接口函数sendrecv()调用函数msg_receive()。

如果任务B要从所有任务接收消息,则从它的发送队列(如上所述)中获取第一个(如果有),然后将其复制到M。

如果任务B要从特定任务A接收消息,则要做的第一件事是检查任务A是否正在等待向任务B发送消息。如果是,则将消息从A复制到消息结构M。

如果没有向任务B发送消息的任务,则任务B被阻塞。

图4.任务B接收消息的算法

值得注意的是,每个任务都维护一个消息结构,无论它发送或接收,只不过用于发送消息的部分填充了消息,而接收消息的部分为空。有了 ITC 的这种同步机制,只有满足任务的要求,才能继续运行,否则它将被阻塞,其状态将指定为SENDING或RECEIVING。这种沟通策略通常被称为'会合'。由于内核不需要维护任何额外的消息缓冲区,因此实现将相对简单。

任务间通信的演示

为了显示使用微内核进行任务间通信的效果,设计了一些实时以及分时任务,每个任务都会定期显示一条消息,然后延迟一段时间。实时任务的延迟由IRQ中断服务程序实现,而分时任务的延迟则由基于任务间通信(ITC)的函数get_ticks()来实现。图5显示了get_ticks()的C代码,图6显示了超级终端上所有运行的任务。

图5. get_ticks()的代码

图6.任务显示

结论

操作系统是通用计算机和嵌入式系统的基本组件,其性能将直接影响系统的质量。因此,它一直是一个值得深入研究的热门话题。

本文描述的微内核是在具有GNU工具链的Linux平台上开发的,目的是实现一个精巧而紧凑的基于ARM的嵌入式操作系统微内核。基于这样的内核,可以添加其他组件,例如文件系统,网络例程等,以形成实用的多任务微内核嵌入式操作系统。由于篇幅所限,一些技术细节已被省略。

参考文献

[1] A. N. Sloss, D. Symes and C. Wright

剩余内容已隐藏,支付完成后下载完整资料


资料编号:[239568],资料为PDF文档或Word文档,PDF文档可免费转换为Word

您需要先支付 30元 才能查看全部内容!立即支付

发小红书推广免费获取该资料资格。点击链接进入获取推广文案即可: Ai一键组稿 | 降AI率 | 降重复率 | 论文一键排版