摘 要:本文在分析嵌入式系统中图形系统的软件体系结构后,系统介绍了实现图形化输出的底层驱动的相关数据结构和算法实现,并介绍了将图形显示驱动安装到嵌入式操作系统?滋Clinux内核的方法。
关键词:μClinux 驱动 FrameBuffer
随着计算机技术的迅速发展,嵌入式系统得到了广泛的应用。因为嵌入式系统本身固有的嵌入性和专用性,所以对嵌入式系统的开发有别于传统意义上的计算机系统开发。嵌入式操作系统的普及使得基于嵌入式操作系统的开发成为嵌入式应用系统开发的主导方式。
嵌入式系统对完整的图形系统的要求越来越迫切,GUI(图形化接口)已经成为嵌入式系统信息输出的一种重要方式。本文详细阐述了基于?滋Clinux操作系统的图形应用的软件结构,并在此基础上介绍了图形显示的底层硬件驱动技术,系统地给出了实现驱动的数据结构和算法。
1 μClinux简介
μClinux由Linux2.0内核发展而来,它继承了Linux的主要特点,并针对微控制领域中不具有MMU(存储管理单元)的处理器做了修改。μClinux重写了内核中大部分的二进制代码和源代码,因此内核比Linux2.0小很多,但它同时却保留了Linux操作系统的稳定性以及出色地支持多种文件系统的特性。?滋Clinux已被广泛应用于嵌入式系统中,本文将基于该操作系统研究图形硬件驱动。
2 图形系统的体系结构
嵌入式系统的显示输出分为图形输出和纯文本输出二部分。μClinux操作系统中控制台(Console)处理纯文本的输出,而帧缓冲(Frame Buffer)负责图形信息的输出。
μClinux操作系统的图形系统从软件结构角度可以分为如图1所示的三层。
最底层是图形硬件驱动程序,用来操作图形硬件设备。μClinux系统中对图形硬件设备的操作通过标准化的调用接口映射到该层实施。
中间层是GUI图形引擎,该层把最底层提供的基本图形输出操作结合起来完成较为复杂的图形输出。为该层设计的GUI图形引擎已经有成熟的产品,如国内的MiniGUI、国外的MicroWindows以及Embedded QT等。
最高层是图形应用程序层,各种图形应用程序都在该层实现。应用程序调用中间层的图形引擎完成各种复杂图形效果的输出。
本文研究的对象是图形系统最底层的硬件驱动。
3 图形硬件驱动
3.1 μClinux设备驱动简介
μClinux将设备分为字符设备、块设备和网络设备三大类。图形显示硬件设备属于字符设备。
μClinux驱动程序的基本结构和Linux驱动程序的结构类似。不同的是:Linux使用模块化(module)的方式处理设备驱动,可以根据需求将所需驱动加载到系统内核中。μClinux虽然也支持模块化的处理方式,但是,由于存储空间的限制以及嵌入式系统具有针对性的功能要求,所以,通常在编译内核时便放弃了对模块化的支持而采用将驱动直接编译进内核的方式安装驱动。
μClinux的设备驱动程序和文件系统紧密地结合在一起,各种设备以文件的形式存放在/dev目录下,称为设备文件。用户程序使用open( )、close( )、read( )、write( )等标准调用函数操作硬件设备。内核通过file_operations结构调用驱动程序中的函数。这是一个通用的文件操作函数指针的集合,包含了?滋Clinux提供的全部文件系统操作函数。在硬件驱动程序中只需实现该硬件所需的部分函数,而将其他的函数指针置空。
3.2 图形硬件驱动的研究与实现
3.2.1 图形硬件驱动
图形硬件的驱动在?滋Clinux中是比较复杂的驱动,同它相关的函数及文件分为二类。
(1)Frame Buffer驱动程序。驱动程序的代码存放在fbmem.c文件中。Frame Buffer为显示设备提供一个通用接口,它是将显存抽象后的设备。它通过地址映射允许上层用户在图形模式下对显示缓冲区进行读写。Frame Buffer的存在使用户不必关心物理显存的位置、换页机制等细节问题,同时简化了用户程序代码在不同硬件平台间的移植。
(2)Frmae Buffer的辅助函数。这些函数声明在fb.h中。不同的嵌入式系统中,图形显示硬件不完全相同,硬件独有的状态数据由这些辅助函数记录并修改。Frame Buffer调用辅助函数控制显示硬件设备。
下面从这二方面探讨图形硬件驱动的实现。
3.2.2 Frame Buffer驱动
Frame Buffer的驱动代码存放在fbmem.c中,该文件最早出现在Linux-1.3.94内核版本中。其中最核心的数据结构是包含操作Frame Buffer函数的指针集合struct file_operations fb_fops。
static struct file_operations fb_fops={
NULL, //lseek
fb_read, //read
fb_write, //write
NULL, //readdir
NULL, //select
fb_ioctl, //ioctl
fb_mmap, //mmap
fb_open, //open
fb_release, //release
NULL //fsync
};
Frame Buffer设备文件的特征决定了其只需要实现文件操作函数中的部分调用,如:fb_read、fb_write、fb_mmap等。
由于处理器不支持MMU,μClinux操作系统对内存的管理不同于标准的Linux。在没有MMU的嵌入式系统中,显存的空间是独立且固定的,μClinux操作系统可以线性地访问显存空间。基于此,?滋Clinux中的fb_mmap可以修改成如下代码:
static int fb_mmap(struct inode*inode,struct file*file,
struct vm_area_struct*vma) {
struct fb_ops*fb=registered_fb[ GET_FB_IDX(inode->i_rdev)];
struct fb_fix_screeninfo fix;
if(!fb)
return -ENODEV;
fb->fb_get_fix(&fix,PROC_CONSOLE( ));
vma->vm_start=fix.smem_start+vma->vm_offset;
return 0;
}
由于μClinux直接通过地址总线访问显存空间,所以地址映射被处理成直接访问内存地址的方式。
fb_open、fb_write、fb_read等函数完成驱动Frame Buffer所必须的另外几个操作,函数fb_ioctl则用来调用辅助函数记录和修改硬件状态数据。这些函数只需做微小的修改便可以满足嵌入式系统图形显示的需要。限于篇幅,在此不作详细说明。
3.2.3 Frame Buffer的辅助函数
Frame Buffer调用显示驱动的辅助函数记录与修改显示硬件状态数据。由于不同显示硬件设备的工作方式不同,所以需要为它们定制特别的辅助函数。Frame Buffer的实现离不开辅助函数,因此include/linux/fb.h最初和fbmem.c一起出现在Linux-1.3.94内核版本中。fb.h中声明了辅助函数的接口,函数实现代码则需要根据具体的硬件结构编写并保存在文件xxxfb.c中。
fb.h文件中定义了记录图形硬件固有状态参数的struct fb_fix_screeninfo和记录图形硬件可变参数的struct fb_var_screeninfo,同时声明了操作这二组数据的函数指针集合struct fb_ops:
struct fb_ops {
//读取固有参数
int (*fb_get_fix) (struct fb_fix_screeninfo*,int);
//读取可变参数
int (*fb_get_var) (struct fb_var_screeninfo*,int);
//设置可变参数
int (*fb_set_var) (struct fb_var_screeninfo*,int);
//读取color map
int (*fb_get_cmap) (struct fb_cmap*,int,int);
//设置color map
int (*fb_set_cmap) (struct fb_cmap*,int,int);
//平面显示函数
int (*fb_pan_display) (struct fb_var_screeninfo*,int);
int (*fb_ioctl)(struct inode*,struct file*,unsigned int,
unsigned long,int);
};
在xxxfb.c文件中必须声明这样两个变量:
static struct fb_fix_screeninfo xxx_fb_fix;/*硬件固有参数*/
static struct fb_var_screeninfo xxx_fb_var;/*硬件可变参数*/
fb_ops中的函数指针在xxxfb.c文件中完成函数代码:
static struct fb_ops xxxfb_ops={
xxxfb_get_fix,
xxxfb_get_var,
xxxfb_set_var,
xxxfb_get_cmap,
xxxfb_set_cmap,
xxxfb_pan_display,
xxxfb_ioctl
};
结合显示硬件结构特征实现这几个函数,其中关键的函数有xxxfb_get_fix、xxxfb_set_var和xxxfb_get_var。这三个函数分别对xxx_fb_fix和xxx_fb_var中的参数进行读取和设置。
以xxxfb_get_fix为例,该函数从xxx_fb_fix中读取硬件的固有状态参数。有二种实现方法:(1)从xxx_fb_fix中逐个读取需要的参数。对具体的硬件,fb_fix_screeninfo中只有部分数据是需要被处理的,因此只需要读取有效数据。(2)调用系统的memcpy( )函数将xxx_fb_fix完全拷贝出来。这种方法方便,但对嵌入式系统来说是以加大存储空间的开销为代价的。
另外二个函数xxxfb_get_var和xxxfb_set_var也可以做类似的处理。
在xxxfb.c文件中,启动显示硬件的函数是xxxfb_init( )。该函数将当前的显示硬件注册到系统中供Frame buffer调用,同时还完成对xxx_fb_fix的赋值。具体代码如下:
void xxxfb_init(void)
{
……
/*硬件的固有数据是固定的,因此在这里对xxx_fb_fix进行赋值*/
……
//将显示硬件注册到系统中
err=register_framebuffer(&fb_info.gen.info);
if (err<0)
return err;
……
return mem_start;//返回显存起始地址
}
xxxfb_init( )函数被fbmem.c文件的fb_open( )函数调用。由于图形硬件的多样性,fb_open( )函数根据具体硬件信息选择xxxfb_init( )启动辅助函数。
μClinux中线性的显存访问是实现图形显示驱动首先要注意的问题,处理好上面提到的函数和数据就可以为特定嵌入式系统的显示设备开发出合适的辅助函数。
3.2.4 安装驱动
下面介绍将显示驱动安装进内核的步骤。
(1)图形显示硬件属字符设备,因此将fbme.c文件保存到目录/linux-2.0/driver/char/中。
(2)将图形设备加入到的Makefile文件中。
(3)在/linux/init/main.c文件中添加驱动的启动函数fbmem_init( )。
(4)修改编译选项文件,在/linux/arm/armnommu/config.in文件中加入:
bool′ framebuffer support′ CONFIG_FB_XXX
(5)为文件系统的open( )调用提供设备文件名。在/vendors/
(6)使用make工具重新编译内核,就可将图形显示的驱动编译进μClinux内核。
以上六步将显示硬件驱动安装到μClinux的内核中,在此基础上选用合适的图形包就可以在嵌入式系统中方便地开发图形应用程序。
4 结束语
在某些特殊场合应用的嵌入式系统可能需要支持多个显示硬件的输出,依照本文的方法可以为不同的显示硬件设计辅助函数来支持多终端图形输出。
至此,本文完成了基于?滋Clinux操作系统的嵌入式设备的图形显示驱动的探讨并给出了部分实现算法。
参考文献
1 Bovet D P,Cesati M著,陈莉君译.深入理解Linux内核(第二版).北京:中国电力出版社,2004
2 Rubini A,Corbet J著,魏永明译.Linux设备驱动程序(第二版).北京:中国电力出版社,2002
3 毛德操,胡希明.Linux内核源代码情景分析.杭州:浙江大学出版社,2001