MQX操作系统底层驱动设计分析与改进*

2020-03-20 03:05潘俊虹蔡闯华1
关键词:驱动程序调用队列

潘俊虹,彭 涛,蔡闯华1,

(1.武夷学院数学与计算机学院,福建 武夷山 354300;2.苏州大学计算机科学与技术学院,江苏 苏州 215006;3.认知计算与智能信息处理福建省高校重点实验室,福建 武夷山 354300)

MQX(Message Queue eXecutive,消息队列执行)是由恩智浦公司推出的一款免费、开源且提供持续技术支持的嵌入式实时操作系统(Embedded Real Time Operation System,RTOS)[1].MQX以其丰富的外设驱动、精简的内核、清晰的架构和较强的实时性等优点,在工业自动化、智能家居等领域广泛应用.MQX版本在不断更新,目前的最新版本是于2015年7月推出的MQX 4.2.0.鉴于MQX在嵌入式开发领域广阔的市场前景[2-4],分析研究MQX底层设备驱动设计的原理和方法对促进其发展具有一定的意义.

在嵌入式软件系统中,底层设备驱动直接面向底层硬件,实现对底层硬件的访问控制,是操作系统的重要组成部分[5].MQX采用统一的模型来管理设备驱动,用户在应用程序中可以通过一组标准的接口函数对设备进行访问.MQX根据管理设备方式的不同,将设备驱动分为内核安装管理的输入输出(Input/Output,I/O)子系统层设备驱动和非内核安装管理的驱动2大类.对于非内核安装类驱动,用户直接调用MQX提供的应用程序编程接口(Application Programming Interface,API)函数或者静态库即可[6].笔者主要针对内核安装的I/O子系统层设备驱动管理和设计方法进行阐述,通过分析MQX底层设备驱动管理和设计模式,给出了底层驱动开发的基本方法,并在此基础上对底层驱动设计方式进行改进.

1 MQX设备驱动机制分析

1.1 MQX底层设备驱动运行模式

图1 MQX I/O设备驱动层次结构Fig.1 Hierarchical Structure of MQX I/O Device Drivers

MQX标准I/O设备驱动可分为格式化ANSI(American National Standards Institute,美国国家标准协会)I/O层、MQX I/O子系统层和底层设备驱动层[7],具体结构如图1所示.

格式化ANSI I/O层由应用程序调用,是MQX为应用程序提供的一组格式化ANSI文件接口.在源文件“MQXincludeio.h”中可以看出,MQX通过宏定义映射到I/O子系统层对应的接口函数上(表1).

表1 MQX设备驱动函数映射关系Table 1 Mapping Relationship Between MQX Device Driver Functions

MQX I/O子系统层遵循可移植操作系统接口(Portable Operating System Interface of UNIX,POSIX)标准[8],由格式化ANSI I/O层调用,是格式化ANSI I/O层与底层设备驱动的纽带,它提供了专门的API函数将标准I/O函数与设备的私有信息及底层设备驱动进行关联.这些API函数中,最关键的是驱动注册安装函数io_dev_install.该函数主要功能是将包含有I/O设备的标识符、驱动函数名及其数据结构的结构体安装到操作系统的设备管理队列中.实际打开设备时,I/O子系统以设备标识符作为索引检索设备管理队列,建立设备文件到设备结点的关联,并返回文件指针,通过文件指针句柄调用关联的设备驱动函数来执行操作.通过这种方式,底层设备在MQX中被抽象成文件,借助文件名间接调动底层设备驱动,以实现打开设备和打开文件的方式统一.

底层设备驱动层是根据各种具体设备开发的驱动程序.MQX为不同型号的嵌入式MCU提供了大量的板级支持包(Board Support Package,BSP)级底层驱动程序,用户也可以根据需要改写或自己编写设备驱动程序.当用户在应用工程中需要访问这些底层设备时,只需将所需的底层驱动程序添加到用户工程,并将驱动程序注册到I/O子系统设备管理队列中,就可以在用户任务中直接调用fopen,fclose,fread和fwrite等标准函数对底层设备进行访问和控制.

1.2 I/O设备驱动队列管理器

图2 I/O驱动管理队列设备结点结构Fig.2 Node Structure of I/O Driver Management Queue Device

MQX通过链队列的形式来实现I/O设备驱动的组织和管理.MQX系统在内核初始化时创建设备驱动链队列的头结点,如图2所示,队列中每一个结点都是IO_DEVICE_STRUCT结构体类型,包括设备标识符、驱动接口指针和驱动设备信息指针等.用户在使用驱动前需将驱动注册到I/O子系统层的设备管理队列中,注册安装过程实际上就是将各设备的驱动结点插入到驱动管理队列当中,并通过指针连接成完整的驱动链表队列.设备驱动必须注册到I/O设备驱动管理队列才能为标准I/O层的格式化标准函数提供服务.设备驱动结点由_io_dev_install函数安装设备驱动时动态创建,并由I/O子系统维护.新添加的设备驱动被插入到链队列的尾部.由于每个I/O设备传入安装函数的初始化信息参数不同,在调用安装函数之前每个I/O设备都要定义自己的安装函数_io_dev_install.在打开设备时,先由fopen函数创建一个设备文件描述符,并由该描述符的DEV_PTR指针(文件句柄)建立与设备驱动结点的关联,通过该句柄在设备队列中快速定位设备结点,然后调用标准I/O函数如同访问文件一样对设备进行访问.

1.3 底层设备驱动参数的组织和分解

MQX底层设备驱动程序的设计开发相对独立于上层I/O子系统层[9].只要明确上层函数传递来的参数格式,在底层驱动中将其分解出来供底层设备驱动程序使用,其他设计原则和方法与无操作系统下的驱动开发一致.因此,设备驱动设计的关键就是函数参数的组织、传递和分解.

(1)底层设备驱动参数的组织封装.MQX操作系统底层设备驱动函数的参数传递过程,一般是先将参数信息封装在数据结构体中,再将数据结构体指针传递给底层设备驱动程序.底层设备驱动程序通过结构体指针从数据结构中分解出所需的参数,参数的这种封装有利于简化参数的传递且更加方便快捷.MQX将底层设备驱动初始化参数分为2个部分:一部分与MCU型号和开发板密切相关,基本不再改变;另一部分由标准函数fopen在打开设备时传入,可根据用户工程需要进行动态调整和改变.

(2)底层设备驱动初始化参数分解.底层设备驱动的初始化参数在执行fopen函数(已映射到_io_fopen函数)时传入,其格式如下:

驱动在注册时,IO_OPEN代表device_open函数指针,因此上面代码相当于调用device_open函数,其中参数file_ptr为文件句柄,是指向的结构体中保存了设备驱动结点的指针.设备驱动结点的DRIVER_INT_PTR指针指向封装了设备与BSP相关的信息,其他指针则指向设备标识符和相应的驱动函数.由此可见,只要明确传入底层驱动函数的指针含义,并熟悉参数结构体各成员的作用,即可解析出底层驱动模块所需的初始化参数.

2 MQX底层设备驱动设计基本方法

MQX将底层设备驱动统一在三层体系结构模型中,虽然设备驱动的实现内容各有不同,但是在MQX统一模型中设备驱动计方法是一致的[10].主要步骤如下:按照嵌入式底层软件构件的设计思想,将底层驱动封装成device.h和device.c软件构件(device表示具体的设备模块名);在device.h头函数中封装定义与板级相关的驱动模块参数,定义初始化参数值;给出用户可以动态修改的初始化参数结构体结构,以便于调用fopen函数时依据工程的需要动态修改底层模块的性能;在device.c中编写设备驱动的各个功能函数,在初始化设备功能函数(一般为device_open)中解析传入的初始化参数结构体,获取初始化参数;编写注册安装设备驱动函数,主要调用MQX提供的系统函数_io_dev_install注册设备驱动到管理队列中,但也可以对初始化参数进一步封装.

下面通过一个串行通信接口(Serial Communication Interface,SCI)设备驱动实例来介绍设备驱动设计过程.主要步骤如下:

(ⅰ)SCI设备属性信息封装.设备属性信息是在操作系统中配置设备驱动、调用驱动函数必须的参数,这些参数在MQX设备驱动三层模型中的不同层之间传递,对于调用的上层应用程序是透明的.SCI设备属性通常包含设备号、模块时钟和通信波特率等,可用SCI_INIT_STRUCT属性信息结构体进行封装,结构体类型定义放在sci.h文件中.在加载设备时,MQX将该结构体加载到设备驱动链队列的对应SCI设备结点中,并由 DRIVER_INIT_PTR类型指针来指向,实际使用时可通过该指针提取SCI设备属性信息.描述SCI设备属性的结构体代码如下:

∥SCI_INIT_STRUCT 结构体

typedef struct _sci_init_struct{

uint_8 SCI_ID;∥SCI设备号

uint_32 BUS_CLK;∥模块工作时钟

uint_32 BAUD_RATE;∥通信波特率

} SCI_INIT_STRUCT,*SCI_INIT_PTR;

(ⅱ)编写SCI驱动函数.SCI驱动函数利用设备标识符可快速定位到设备驱动队列中的SCI结点,并通过该节点中的设备驱动初始化句柄DRIVER_INIT_PTR解析出设备属性信息,由传入的设备参数对串口模块进行访问以实现具体的各个功能.驱动函数声明放在sci.h文件中,驱动函数的实现放在对应的sci.c文件中.SCI串口驱动包含串口打开、关闭、发送和接收数据等功能,部分串口驱动函数代码如下:

∥串口打开函数

_mqx_int _io_sci_open(MQX_FILE_PTRfile_dev_ptr,char_ptr sci_name,char_ptrparams)

∥串口关闭函数

_mqx_int_io_sci_close(MQX_FILE_PTRfile_dev_ptr)

∥串口读取函数

_mqx_int_io_sci_read(MQX_FILE_PTRfile_dev_ptr,char_ptrbuff,_mqx_intlen)

∥串口写入函数

_mqx_int_io_sci_write(MQX_FILE_PTRfile_dev_ptr,char_ptrbuff,_mqx_intlen)

其中:参数file_dev_ptr为关联设备文件句柄;open_name为设备标识符;params 为设备初始化属性;buff为数据缓冲区;len为数据长度.

(ⅲ) 编写SCI驱动注册安装函数.MQX的I/O子系统层提供专门的API函数来管理设备驱动队列,包括_io_dev_install,_io_dev_uninstall,_io_get_handle,_io_init和_io_set_handle等.其中_io_dev_install函数负责将设备驱动结点(包含设备标识符、设备驱动函数名和指向设备属性信息结构体指针)安装到设备管理队列中,从而建立I/O设备驱动的中间转换层.下面代码段是在MQX/IO/io_inst.c文件中定义的_io_dev_install函数声明部分:

_mqx_uint_io_dev_install(char_ptr identifier,∥设备标识符

_mqx_int(_CODE_PTR_ io_open)∥I/O设备打开函数

(MQX_FILE_PTR,char_PTR_,char_PTR_),∥I/O设备关闭函数

_mqx_int(_CODE_PTR_o_close)(MQX_FILE_PTR),

_mqx_int(_CODE_PTR_ io_read)∥I/O设备读取函数

(MQX_FILE_PTR,char_PTR_,_mqx_int),∥I/O设备写函数

_mqx_int(_CODE_PTR_io_write)(MQX_FILE_PTR,char_PTR_,_mqx_int),

_mqx_int(_CODE_PTR_io_ioctl)∥I/O控制设备属性函数

(MQX_FILE_PTR,_mqx_uint,pointer),∥I/O设备初始化数据结构体

pointer io_init_data_ptr)

对于驱动函数注册安装,需要具体设备的标识符和指向存有设备属性信息结构的指针作为参数,并在注册驱动函数内部调用系统API函数_io_dev_install实现注册安装.SCI注册安装驱动函数编写如下:

_mqx_uint io_sci_install(char_ptr identifier,SCI_INIT_PTR sci_init_ptr){

∥调用系统注册安装API函数

return_io_dev_install(identifier,_io_sci_open,_io_sci_close,_io_sci_read,_io_sci_write,_io_sci_ioctl,(pointer) sci_init_ptr);}

其中:参数identifier为SCI 设备标识符;sci_init_ptr为设备属性信息结构体指针._io_dev_install 函数的形参在实际调用时与第2大部分步骤(ii)中定义的SCI具体驱动功能函数指针关联,安装成功即将SCI设备结点添加到系统的设备管理队列之后.执行打开SCI设备文件操作时,I/O子系统以SCI设备标识符作为索引检索设备管理队列,定位到设备后,通过关联的文件句柄调用驱动函数对SCI进行访问.

(ⅳ) 在任务中调用驱动服务.底层设备驱动注册成功后,就可在MQX的功能任务中调用驱动服务.用户可像对文件操作一样,通过系统I/O层提供的标准函数对底层设备进行访问和控制.在调用设备之前,需要获取设备文件访问句柄,这一操作由标准函数fopen函数实现.用户打开设备时,实际上是获取设备文件的访问句柄,有了设备访问句柄,其他标准函数就可以通过其来对设备进行操作.以下是测试串口驱动调用代码:

void task_sci_test(uint_32 init_data)

{

MQX_FILE_PTR file_dev_ptr;

char buff=”This is SCI test!”;

file_dev_ptr=fopen(“sci2”,null);∥打开设备,获取设备文件句柄

if(file_dev_ptr){

write(file_dev_ptr,buff,strlen(buff));∥串口输出字符串

}

fclose(file_dev_ptr);∥关闭设备

_task_block();∥任务阻塞

}

3 MQX底层设备驱动设计方法改进

3.1 底层设备驱动可移植性分析

对于无操作系统驱动调用而言,传入的参数一般可被驱动函数直接调用.而通过对MQX系统底层驱动机制的分析可以发现,在其三层驱动模型中,底层设备驱动与无操作系统下设计驱动构件的主要区别在于,MQX底层驱动传入的是结构体类型指针,包含了传入参数信息的解析操作.这些操作都是由操作系统的API函数进行组织和传递的,这样可以提高设备驱动与操作系统的粘合性,降低底层设备驱动的移植和复用效率,也难以复用被验证过的成熟设备驱动构件,导致增加嵌入式软件的开发难度,同时也延长开发周期[11].

3.2 驱动设计改进基本思想

对于底层驱动过于依附操作系统API函数的情况,可以将MQX底层设备驱动程序所需的参数由专门的函数(接口构件)进行解析后,再提供给底层设备驱动函数使用,这样MQX底层设备驱动程序就与无操作系统下的设备驱动程序趋于一致.同样地,无操作系统下的设备驱动程序只需加上一层负责参数解析的接口构件,就可以挂接到MQX操作系统的设备管理队列中,在MQX操作系统环境下使用.通过在I/O子系统层和底层设备驱动层之间增加接口驱动层(图3),可以使得底层设备驱动程序不再紧密依附操作系统,实现设备驱动完全独立于MQX操作系统.这样一来,底层驱动就不必为某种操作系统专门设计,应用成熟的驱动构件还可以进入构件库,为用户提供面向底层设备而不是面向操作系统的驱动构件.根据软件工程构件化开发思想,成熟的构件复用不仅能提高软件开发效率,还能提高系统的稳定性和可靠性[12].

图3 改进的I/O驱动层次结构Fig.3 Improved I/O Driver Hierarchy

3.3 接口驱动构件设计

根据前文的分析,接口驱动构件可以作为操作系统内核与底层驱动程序的连接通道,实现接口转接功能.其主要职责可以归结为3点:(1)接收来自I/O子系统层传入的设备管理队列中底层设备属性结构体参数;(2)提取分解结构体参数信息;(3)调用底层驱动函数传入至设备参数,供其使用.考虑到MQX驱动函数采用的是统一的模型,操作系统对设备驱动属性信息的解析方式和对驱动函数的调用规则是一致的,在设计接口驱动构件时,可复制工程中成熟的接口驱动构件进行改写,在提高效率的同时也能使接口驱动构件更好地与操作系统衔接.因此,1.3节中SCI串口打开接口驱动函数可改写如下(其他接口驱动与此类似,不再赘述):

_mqx_int _io_sci_open(MQX_FILE_PTRfile_dev_ptr,char_ptrbuff,_mqx_int len){

∥在设备管理队列中定位设备驱动结点

IO_DEVICE_STRUCT_PTRdev_ptr=file_dev_ptr-DEV_PTR;

∥通过文件句柄访问设备属性信息结构体

SCI_INIT_PTR sci_init_ptr=(SCI_INIT_PTR)(dev_ptr-DRIVER_INIT_PTR);

∥提取设备属性信息

uint_32 bus_clk=sci_init_ptr-BUS_CLK;

uint_32 b_rate=sci_init_ptr-BAUD_RATE;

∥调用底层驱动函数实现功能

sci_init(dev_id,bus_clk,baud_rate);∥初始化∥串口

sci_send(dev_id,len,buff);∥串口数据发送

}

3.4 SCI驱动构件测试

图4 串口驱动构件测试Fig.4 Test of Serial Port Driver Component

测试工程使用MQXFW工程框架[2],硬件采用基于ARM Cortex-M0+内核的KL25评估板.用串口转USB线连接PC与KL25评估板,通过嵌入式集成开发软件KDS(Kinetis Design Studio,Kinetis设计工作室)将包含SCI驱动构件和发送接收任务的工程进行编译并下载到KL25启动运行.程序的主要功能是KL25定时向PC机发送字符串“This is SCI test!”,并通过中断接收来自PC机发送的字符串,然后将字符串回发给PC机.运行在PC机端的串口测试软件显示接收到的字符串如图4所示.测试结果表明,KL25串口模块正常接收发送字符串,所设计的SCI驱动构件运行稳定可靠,结果正确.

4 结语

在嵌入式操作系统中,设备是应用程序必使用的资源,然而设备具有多样性,为了使操作系统下的应用程序与设备无关,须将设备的复杂性和多样性屏蔽起来,为应用程序提供统一的使用接口.在MQX操作系统中采用了三层模型,以设备驱动队列管理器为核心载体实现统一的驱动管理模式,使得用户可以使用标准的I/O函数来访问控制底层设备.但从设备驱动开发、移植和复用角度来看,由于设备参数信息需要MQX系统API函数进行解析传递,因此增加了设备驱动程序与操作系统的粘合性,一定程度上也增加了设备驱动的开发难度,降低了驱动构件的可移植性和可复用性.从嵌入式软件工程构件开发角度,在底层设备驱动外层再次进行封装,通过接口构件来解析设备属性参数信息,实现底层驱动与操作系统转接,这样就可实现面向底层设备的驱动设计,与操作系统无关,将底层驱动从操作系统中完全独立出来.实验证明,这种底层驱动开发方式有效提高了软件开发效率,开发的底层驱动构件在不同应用、不同操作系统间的移植和复用更加方便快捷.

猜你喜欢
驱动程序调用队列
队列里的小秘密
基于多队列切换的SDN拥塞控制*
核电项目物项调用管理的应用研究
阻止Windows Update更新驱动程序
系统虚拟化环境下客户机系统调用信息捕获与分析①
在队列里
计算机硬件设备驱动程序分析
丰田加速驶入自动驾驶队列
利用RFC技术实现SAP系统接口通信
妙用鼠标驱动