嵌入式实时操作系统的RAM盘扩展

发布时间:2019/4/11

摘要:介绍了一种在嵌入式实时操作系统内核(以下简称实时内核)上实现RAM盘的方法,配合接受用户命令的Shell任务,可实现嵌入式系统的多任务动态加载和监控,扩展了实时内核的应用领域。实时内核采用目前十分流行的免费内核μC/OS-Ⅱ,硬件不台为通用现场总线控制器系统。


    关键词:μC/OS-Ⅱ内核 嵌入式系统 通用现场总线控制器(GPFC) ColdFire


1 嵌入式RTOS


目前,嵌入式RTOS的应用领域越来越广泛。已经有80多个RTOS厂商生产面向8位、16位、32位、甚至64位微处理器的RTOS产品。商业的实时操作系统如VxWorks,pSOS,VRTX,WindowsCE等功能完善,提供了完备的开发环境,但大多价格昂贵,不提供源代码(即所谓的黑箱)。用户不了解其工作机制,更不能进行修改和扩展。某些商业系统还要求用户在产品投产后继续支付软件费用。这对于国内的用户开发中小应用系统来说,是一项沉重的负担。而采用开放源代码的实时内核不失为一种选择。开放源代码的另一个好处是用户可以根据具体需要删改和扩展功能。本文将以μC/OS-Ⅱ在通用网络控制器上的应用为例,说明开放源代码的实时内核在可扩展性方面的优点,以及笔者在使用中的一些心得体会。


2 通用现场总线控制器


GPFC (General Purpose Field bus
Controller)——通用现场总线控制器是用于加速器数据采集系统中的网络控制器,由德国国家同步辐射实验室(DESY)Dr.Clausen
Matthias领导的研究小组开发。用于不同种类的现场总线、以太网之间的通讯控制,功能相当于不同类型子网之间的网关(关于GPFC的技术细节,感兴趣的读者可以查询DESY的主页http://www.desy.de)。系统微控制器采用Motorola公司68K家庭的32位MCU
ColdFire 5206E。在33MHz总线频率下能达到17MIPS的处理能力。由于ColdFire将片选逻辑电路、总线控制器、DRAM控制模块等全部集成在
MCU内部,使得外转帐电路室得十分简单。系统配有32MB DRAM和1MB Flash
RAM,并有两个网络接口A和B。该系统的硬件设计非常灵活,网络接口配以不同的驱动电路,可支持以太网、CAN、Profibus、MIL1553等多种现场总线,实现网络间的连接和控制。只要采用不同的网络协议,就可以连接不同的子网,实现不同的控制。GPFC配以不同的网络模块可以替代目前广泛使用的VME工控系统,由于舍弃了VME昂贵的机箱、总线板,大幅度减低了主机成本。GPFC系统结构示意如图1所示。


3 在GPFC上运行RTOS


GPFC
的系统软件基于嵌入式实时操作系统,国外是在VxWorks上开发的,主要是两个独立的任务分别处理两个网络接口的通讯协议。为了扩展GPFC的应用范围,系统软件包中包含了各种可能用的网络协议,都以独立的任务形式存在。用不上的任务处于休眠状态,如果需要连接某种类型的网络,只要运行针对某种网络协议的任务就行了。


VxWorks价格昂贵,在国内买一套要几十万元人民币。为了满足国内应用要求,笔者希望寻找一种廉价的实时内核,一方面可降低软件方面的成本,同时可根据自己折需要定制软件。经过调研,决定采用当前十分流行的μC/OS-Ⅱ作为实时内核。


μC/OS-Ⅱ是基于优先级的抢占式实时多任务内核,其绝大部分代码是由C写成的。目前已经应用于包括Motorola 68000系列,以及Intel
80x86等各种处理器上,在世界控制领域取得了一席之地。μC/OS-Ⅱ的可应用领域非常广阔,涵盖了几乎所有的实时应用。μC/OS-Ⅱ是完全免费的,全部源代码都可以从Internet上获得。


μC/OS
-Ⅱ是面向中小型嵌入式系统的。如果包含全部的功能模块(信号量、消息邮箱、消息队列及相关函数),编译后的μC/OS-Ⅱ内核大约有6KB;如果只保留最核心的代码,则可压缩到3KB,这使得μC/OS-Ⅱ可以用于更小模块的应用系统。同时,由于系统的可扩展性,稍加修改可以用于更大模块的系统上。
RAM的占用与系统中的任务数有关,任务的堆栈要占用大量的RAM空间,堆栈的大小取决于任务的局部变量、缓冲区大小及可能的中断嵌套的层数。对于一般的中小系统,任务堆栈可以取几百字节到几千字节。对于频率中断和高吞吐率的系统,要为任务预留足够的堆栈空间。本系统中为每个任务分配了512KB的堆栈文凭间,并有堆栈越界检查。


采用μC/OS-Ⅱ所遇到的主要问题是μC/OS-Ⅱ本身不带文件系统,缺乏调试工具和手段。为了方便调试和日后的管理操作,基于μC/OS-Ⅱ的开放源代码和可扩展性,笔者将
Linux的RAM盘、文件系统和用户Shell移植到合并μC/OS-Ⅱ上并编写了内存管理模块,实现内存的动态分配和释放。系统中运行的任务可以对
RAM盘进行文件操作,文件系统可以为任务保存数据并提供了统一的接口。通过Shell任务,用户可以登录到系统中,运行或者挂起任务,以处理不同的通讯协议,实现任务的动态管理。在调试过程中,可以通过命令查看各个任务的运行状态。而任务以文件的形式保存在RAM盘中,不同种类的任务保存在不同的目录中,方便了管理和维护。


4 RAM盘的扩展方法


RAM盘采用了和Linux EXT2系统类似的文件系统。EXT2是一种高效、安全的文件系统。图2是EXT2的逻辑布局。它由一个引导块和重复的块组组成,每个块组又由超级块、组描述符表、块位图、索引节点位图、索引节点表、数据区组成。文件以树型目录形式组成。不同点在于磁盘文件系统的操作单元是磁盘块,而RAM盘所操作的单元是内存块。在笔者为GPFC设计的RAM盘中,将内存最高端的4MB划分为RAM盘空间,每一个逻辑块为512字节。限于篇幅所限,关于EXT2系统的细节,请感兴趣的读者参考有关Linux文件系统的资源,本文主要介绍如何在RAM盘中实现及主要的数据结构。


其中引导块存系统的引导代码,由于RAM盘不用来引导系统,所以可不必设定引导块。超级块用来存放EXT2文件系统整体信息的数据结构,是EXT2的核心所在。超级块记录文件系统当前状态,盘有多大,能存放多少文件,何处可以找找到空闲和用于文件系统管理的信息。用于管理磁盘的超级块非常复杂,用于RAM盘则可对其进行简化,简化后的
ram_super_block结构如下:


struct ram_super_block


{


long s_inodes_count; /*文件系统中节点总线*/


long s_blocks_count; /*文件系统中的块总数*/


long s_r_blocks_cout; /*为超级用户保留的块数*/


long s_free_blocks_count; /*文件系统中空闲块总数*/


long s_free_inodes_coutnt;/*文件系统中空闲节点总数*/


long s_first_data_block; /*文件系统中的第一个数据块*/


long s_log_block_size; /*系统逻辑块的大小*/


short s_inode_size; /*索引节点结构的大小*/


short s_state;/*文件系统的状态*/


time_t s_wtime;/*超级块最后一次修改的时间*/


}


在文件系统中每一个文件(包含目录)占据一个索引节点表项。索引节点是一个记录文件信息的数据结构dinode。访问文件,其它就是寻找文件对应的索引节点。索引节点集中保存在索引节点表中,索引节点表的第一顶固定为根目录对应的索引节点。用于管理RAM盘文件的dinode定义如下:


struct Dinode{


short di_node; /*文件模式:是文件还是目录,是可读、可写还是可执行*/


short di_nlink; /*和文件相关的链接数*/


short di_uid; /*文件所有者的标示*/


short di_gid; /*文件所有者的组标示*/


long di_size; /*文件大小*/


char di_addr[ ]; /*文件数据所在的逻辑块编号*/


time_t di_atime; /*文件最后一次访问的时间*/


time_t di_mtime; /*文件最后一次修改的时间*/


time_t di_ctime; /*文件建立的时间*/


}


块位图、索引节点位图用于查找空闲的RAM盘内存块和索引节点表现。


与文件访问过程相关的一个主要函数为i_name(),其输入参数为希望访问的文件路径名,输出值为文件对应的索引节点号。获得了文件的索引节点号,就可以在索引节点表中找到该节点,从节点的数据结构中就可以知道文件的种类、存储位置等信息,进一步就可以读写操作。


RAM盘属于Linux标准设备中的块设备。作为标准设备,Linux提供了通用的接口函数。用户可以直接调用标准接口函数对RAM盘进行操作:


#include<sys/types.h>


#include<sys/stat.h>


#include<fcntl.h>


#include<unistd.h>


int open(const char pathname,int flag); /*打开文件,返回文件描述符*/


int close(int fd); /*关闭文件*/


int read(int fd,void buf,int count); /*从文件中读出数据到缓冲区,返回读出的字节数*/


int write(int fd,void buf,int count);/*从缓冲区写数据到文件,返回写入字节数*/


5 用户Shell的扩展与实现


用户Shell是一个在μC/OS-Ⅱ下独立运行的任务。Shell启动后,完成一些必要的初始化操作,进入挂起状态,等待用户登录和输入。用户从终端上输入命令后将唤醒Shell,在笔者设计的Shell程序中可接受的命令包括:cd(改变RAM盘当前目录),pwd(显示RAM盘当前目录),
mkdir、rmdir(在RAM盘创立或删除目录),ps(显示当前系统中任务状态),kill(终止一一个任务的运行)。处理不同通讯协议的任务分别以可执行文件形式保存在RAM盘上不同的目录中。如果在Shell中键入任务的名称,Shell会在当前目录下查找是否有匹配的可执行文件。如果有,则调用μC/OS-Ⅱ中的系统函数OSTaskCreate创立一个新的任务,否则返回错误信息。也可以使用kill命令,调用系统函数OSTaskDel终止一个任务的运行。在程序的调试期间,可以使用ps命令查看任务运行状态。


由于μC/OS-Ⅱ的内存管理功能有限,OSTaskCreate函数不能动态分配堆栈空间,OSTaskDel也不能释放任务的堆栈空间。为此,笔者重新编写了内存管理模块,采用页方式和首次拟合算法实现内存的动态管理。每一内存页为2KB,系统的内存资源由一个双向链表进行管理。任务函数可调用
OSMemAlloc()t
OSMemFree()分配释放自己所占的内存空间。OSMemAlloc()在空闲内存链表中查询每一个符合要求的内存块,并分配给任务。而
OSMemFree()则是将释放后的内存块重新添加进空闲内存链表中。


在开发过程中,笔者感觉到采用开放源代码的实时内核的最大优点是可扩展性和可裁剪性,可以根据需要定制出最精练的内核结构。μC/OS-Ⅱ的本意是为中小型嵌入式系统设计的,对于不需要的部分,可以删改;当内核缺乏某些功能,设计得很容易自行设计和添加,从而可以应用在更大型的系统上。其性能可以同昂跺的商业实时操作系统的相媲美,而这种改动和扩展并不难。这正是开放源代码软件的生命力所在。