(多年前发的一篇文章,贴出来作开场吧)
1 引 言
和以往的嵌入式系统不同,现在的嵌入式系统要求有很好的人机交互,要求有一定的GUI界面和输入接口。近年来,操作系统和图形界面GUI相结合开始作为一种潮流在工业测控设备和智能手持设备中广泛采用。在很多国内外发表的文章中,一些嵌入式GUI的图形接口和机制已经得到了充分的阐述[6],但对输入接口则由于涉及GUI本身,操作系统内核以及难于统一,反而很少见到专门的论文。对于开发人员,必须对硬件、操作系统和GUI的整套机制都有所了解才能在实际的开发中针对不同的应用自由选择和设计最为合适的输入设备。因此,根据对国内外基于Linux的最新流行嵌入式GUI的分析,我们力图对嵌入式GUI输入接口机制作一个透彻的研究,并重点介绍我们在为地方厂商开发嵌入式手持测试设备方案时所作的Microwindows接口实现。
2 嵌入式GUI的发展和现状
2.1 X Window对嵌入式GUI的影响
X Window是传统Unix以及其他类Unix系统上已经开发成熟的标准 GUI,在PC平台上直接被Linux借鉴移植,成为Linux下最早的成熟GUI系统。X WINDOW一般适合于工作站和PC平台,运行需要许多其它程序和库的支持,如X窗口管理器,Xlib,以及Xlib上的工具集函数库,因此占用系统资源较多,不适合嵌入式系统使用。但是X window的许多特征对后来开发嵌入式GUI产生了重要的影响。它的事件驱动,分层结构,和C/S客户服务机制等特征基本都被主流嵌入式GUI继承下来,在配置定制输入设备和输出图形设备的时候,具有灵活易扩展的特点。
2.2 目前几种主流嵌入式GUI
由于X Window的体系中包含了一些不适合嵌入式系统的内容,因此对GUI的体系在X window的基础上进行重新设计成为开发高效嵌入式GUI的主流选择。从90年代末期开始,一些重新设计过的,具有嵌入式特色的GUI纷纷出现。
1) QT/Embedded。由Trolltech公司完全基于C++基于对象开发,采用了C/S客户服务和信号槽机制,服务器只完成管理窗口和产生派发事件的功能。客户端直接对图形接口进行操作并绘制窗口,从而减少了GUI中数据交换的规模和频率,提高了GUI的效率。其存在的缺点是,C++的对象开发方式虽然有模块清晰,接口规范,易于复用和二次开发的特点,却要消耗较多的计算和内存资源。
2) Microwindows。这是一个著名的嵌入式GUI开源项目, 采用了C/S客户服务和事件驱动机制,输入事件管理和图形输出由服务器承担。在具体设计上源程序使用C语言和面向对象方法设计GUI分层结构,各层之间相互独立,具有适合跨平台移植,支持多种软硬件环境的特点,在近几年十分流行。
3) MiniGUI。这是一款国产的著名GUI,最初由清华大学魏永明先生开发并在LGPL协议下作为自由软件发布,先后发行了基于多线程的MiniGUI-Threads和多进程的MiniGUI-lite。在实现机制和性能上,MiniGUI采用微C/S客户服务和消息机制。具体设计上综合了分层设计和面向对象消息编程的方法,实现了较完善的多窗口机制和消息传递机制。
3 GUI设计机制和输入接口的关系
3.1 嵌入式GUI 的设计分类
从系统整体角度看,根据是否采用多进程C/S机制和服务程序的具体功能,基本可以将Linux下的嵌入式GUI划分为类X GUI和非X GUI两大类。
是否采用C/S客户服务机制,决定于用户的设计需求和实时性考虑。当实时能力能够得到满足时,类X的GUI在系统健壮性和功能完备性上要大大超过非X的GUI。非X GUI往往采用单进程多线程,所有数据和操作共享同一内存区域,所以当某个线程出现错误时容易造成整个进程瘫痪,因此现代流行的用于复杂场合的成熟的GUI大多数都采用类X GUI多进程多客户单服务的机制。由于类X的嵌入式 GUI具有应用普遍性,我们在论述输入接口机制时也着重研究类X GUI 客户服务结构中的输入接口过程。
3.2 类X GUI C/S交互机制中的输入设备事件
类X GUI 充分借鉴了X Window的客户服务机制,采用了事件驱动的设计方法。应用程序在客户端,输入接口在服务器端,应用程序通过和服务器通讯间接和输入输出硬件设备交互。
为了给出一个最典型的机制示意,我们直接用Microwindows的机制示意图来说明。
图一 类X GUI (Micorwindows)C/S交互原理
客户程序向服务器程序提交感兴趣的事件或要阻止的事件请求,服务器主程序启动循环收集事件(包括输入设备上的事件),通过输入引擎接口进行系统调用来接收输入设备事件将客户感兴趣的事件发送给客户程序。由客户程序处理后再向服务器程序提交绘制和其它操作请求。两者之间通过IPC或socket连接的办法来传送事件和请求。
类X Window基本都采用了类似的C/S交互架构, Microwindows中与X server对应的是NANO-X服务器,客户和服务器之间通过网络socket或进程间通信IPC连接。相似的还有MiniGUI的Lite版本,服务器为mignit,采用微客户服务机制,最大程度减少了服务器的负荷,只把输入设备事件收集和简化的图形输出任务交给服务器,以换取较高的工作速度。QT/Embedded也采用了客户服务机制,不过服务器更为简单,只承担事件收集派发,图形输出功能已经由客户程序接管。
3.3 嵌入式GUI输入接口的分层设计
现代的流行嵌入式GUI设计一般都采用了将设备独立部分和设备依赖部分进行分层的设计。这是一种成熟的思想,其灵活易用性和可移植性已经得到公认。输入接口分层的基本结构如下:
1) 顶层:接口API层,对中间层实现的功能进行组合和封装。
2) 中间层:输入引擎抽象层,与设备无关,主要完成一些基本的输入读取和配置功能。这是可以灵活使用于各种硬件平台之上的关键。
3) 底层:硬件设备驱动层,包括键盘鼠标触摸屏等输入设备的操作系统底层驱动。
图二 输入接口分层结构
通过中间层的设置,底层驱动与上层API在中间层通过系统调用的方法实现接口,将设备相关部分和设备无关部分分开,可以使API和上层风格的设计独立于底层硬件。GUI设计人员使用同样的API就能在不同平台上完成同样风格和功能的GUI设计,这实际就是面向对象的复用设计。
3.4 嵌入式GUI输入接口的底层:操作系统与设备驱动
实现自己定制的非标准输入设备时,除了实现输入引擎,还需要单独编写底层的操作系统的输入设备驱动。Linux下一般将I/O设备建立为特殊的字符设备文件,存取通过内核空间字符设备输入设备驱动程序提供的固定入口点进行。嵌入式GUI通过文件系统的系统调用来调用设备驱动,其层次图如图三。
在设备驱动程序中,设计者根据实际硬件情况,定义了操作字符IO设备的具体函数,分别对应以上的llseek,read,write,ioctl,open,release等操作。然后定义一个包含这些函数指针的struct file_operation 变量并在设备驱动程序初始化时使用register_chrdev函数将驱动中的各种操作向系统注册,写入字符设备开关表,以便文件子系统在适当时候调用。详细的过程参考文献3对其作了深入的阐述。
图三 Linux系统与硬件驱动接口
4 一个Microwindows下的输入接口实现
我们在开发嵌入式手持测试设备时采用了Microwindows作为GUI,通过接口分层和驱动设计实现了自定义的触摸式键盘在GUI中的接口。
图四 测试系统GUI框架示意图
4.1 Microwindows输入事件C/S交互的实现
在服务器程序和客户程序中,完成自定义键盘输入事件的C/S客户服务交互过程设计。
1.服务器主程序:
a)当有事件发生时,服务器查询事件是否为键盘输入。
FD_ISSET(keyb_fd, &rfds);
b)如果是,函数返回1,并执行完整的键盘输入检查和读取。
keystatus = GdReadKeyboard(&mwkey, &modifiers, &scancode);
在这一步的读取中,函数内部通过输入设备的分层机制中的上层的输入设备处理函数,来进一步与底层设备接口并读取底层设备状态。
c)读出键盘状态和扫描码,通过socket向客户端派发键盘事件。
GsDeliverKeyboardEvent(0, (keystatus==1?GR_EVENT_TYPE_KEY_DOWN:GR_EVENT_TYPE_KEY_UP),mwkey,modifiers, scancode);
2.客户主程序:
a)首先与服务器建立socket连接。
Gropen();
b)通知服务器关于各个窗口所感兴趣的事件。
void GrSelectEvents(GR_WINDOW_ID wid, GR_EVENT_MASK eventmask);
输入事件对应有
GR_EVENT_MASK_BUTTON_DOWN,GR_EVENT_MASK_BUTTON_UP, GR_EVENT_MASK_MOUSE_MOTION,GR_EVENT_MASK_MOUSE_POSITION,GR_EVENT_MASK_MOUSE_ENTER,GR_EVENT_MASK_MOUSE_EXIT, GR_EVENT_MASK_KEY_DOWN,GR_EVENT_MASK_KEY_UP。
c)然后启动while循环,检查下一个事件。
GrCheckNextEvent(&event);
d)根据检测到的事件类型,处理从服务器来的各种事件。这往往用一个switch case结构来实现。
对于接收到的上述输入事件,处理函数的类型有如下:
处理GR_EVENT_MASK_BUTTON_DOWN对应鼠标按键事件的函数:
do_buttondown(&event.button);
处理GR_EVENT_MASK_BUTTON_UP对应鼠标按键事件的函数:
do_buttonup(&event.button);
处理对应GR_EVENT_TYPE_KEY_DOWN,GR_EVENT_MASK_KEY_UP对应键盘按键事件的函数:
do_keystroke(&event.keystroke);
处理GR_EVENT_MASK_MOUSE_MOTION,GR_EVENT_MASK_MOUSE_POSITION对应鼠标运动事件的函数:
do_motion(&event.mouse);
处理GR_EVENT_TYPE_MOUSE_ENTER,GR_EVENT_TYPE_MOUSE_EXIT对应事件的函数:
do_enter(&event.general);
e)对输入事件处理的结果最终转化为下一步操作的请求在上面的函数中发送给服务器。
至此完成了Microwindows的设备输入在客户和服务端交互的完整过程。
4.2 Microwindows下的键盘接口各层设计
Microwindows的键盘处理放置在NANO-X服务程序中,因此接口分层也在服务程序中实现。
顶层键盘输入与Server的接口定义在/engine/Devkbd.c中,它是程序相对稳定的部分。
int GdOpenKeyboard(void);
void GdCloseKeyboard(void);
void GdGetModifierInfo( MWKEYMOD *modifiers, MWKEYMOD *curmodifiers);
int GdReadKeyboard(MWKEY *buf, MWKEYMOD *modifiers, MWSCANCODE *scancode);
extern KBDDEVICE kbddev;
顶层接口函数主要通过调用下层即中间层抽象的Open,Read,Close,GetModifierInfo,Poll等来实现。
中间层实际上实现了键盘输入引擎。kbddev是在上层由用户定义的全局变量,是程序中根据输入设备灵活进行配置的关键。它使用的数据结构定义在/device.h中:
typedef struct _kbddevice {
int (*Open)(struct _kbddevice *pkd);
void (*Close)(void);
void (*GetModifierInfo) (MWKEYMOD *modifiers, MWKEYMOD * curmodifiers );
int (*Read)(MWKEY *buf,MWKEYMOD *modifiers,MWSCANCODE *scancode);
int (*Poll)(void); } KBDDEVICE;
通过对kbddev的定义,可以根据输入设备的实际情况在中间层很灵活的改变键盘接口指向。当使用的是TTY键盘输入时,变量赋值如下:
KBDDEVICE kbddev = {
TTY_Open,
TTY_Close,
TTY_GetModifierInfo,
TTY_Read,
…};
microwindows在输入引擎底层kbdtty.c驱动定义如下:
static int TTY_Open(KBDDEVICE *pkd);
static void TTY_Close(void);
static void TTY_GetModifierInfo(MWKEYMOD *modifiers, MWKEYMOD *curmodifiers);
static int TTY_Read(MWKEY *kbuf, MWKEY MOD *modifiers, MWSCANCODE *scancode);
static int TTY_Poll(void);
TTY_Open(KBDDEVICE *pkd)
{ …
if (!(kbd = getenv("CONSOLE")))
kbd = KEYBOARD;
fd = open(kbd, O_NONBLOCK);//注意,通过open函数开始调用系统驱动程序。
…}
TTY_Read(MWKEY *kbuf, MWKEYMOD *modifiers, MWSCANCODE *scancode)
{...
cc = read(fd, buf, 1);//通过read函数调用系统驱动程序。
…}
这样,在Microwindows的输入引擎底层由服务器通过系统调用实现了和Linux系统驱动程序的接口。我们在MiniGUI_Lite的实现中发现代码的设计是类似的,机制上采用了同样的分层方法。
5 结束语
嵌入式GUI为开发智能设备、手持终端所普遍采用,输入设备的多样性和随意性,意味着开发者必须掌握GUI中输入接口的机制才能实现灵活完美的配置。把握嵌入式GUI是否具有C/S特征以及接口的分层实现,就能从根本上把握嵌入式GUI输入接口的实现机制。本文根据主流嵌入式GUI的这两个基本特点,实现了用于数据采集的智能手持测试设备的Microwindows输入接口,并认为这一开发过程适用于当前主流的嵌入式GUI输入接口开发。
参考文献
1. Gregory Haerr, Microwindows Architecture, http://www.microwindows.Org,2000
2. Qt/Embedded Whitepaper,http://www.trollech.com ,Aug,2004,
3. Alessandro Rubini & Jonathan Corbet,Linux Device Drivers, 2nd Editon, Oreilly,Jun,2001.
4. William Rutlser,Object-oriented programming of X Window System graphical user interfaces,ACM,1990.
5. 魏永明,MiniGUI技术白皮书,http://www.minigui.org, 2004
6. 陈沨,毛洋林,基于Linux的图形界面显示系统的设计.测控自动化,2004,Vol 20.1