用面向对象思想分析vfb Framebuffer设备驱动
- 内核版本 Linux Kernel 2.6.34, 与 Robert.Love的《Linux Kernel Development》(第三版)所讲述的内核版本一样
- 源代码下载路径: https://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.34.tar.bz2
1. vfb 驱动概述
- vfb驱动的实现代码在drivers/video/vfb.c 文件中。
- vfb 驱动IS-Aframebuffer驱动,它继承了framebuffer的属性,是framebuffer的子类对象(s3c-fb.c s3c-2410.c 都是framebuffer驱动的子类,与vfb驱动是平级的)。
- vfb全称叫Virtualframe buffer device,它是一个虚拟的framebuffer驱动程序。它在实现了framebuffer设备驱动显示相关的函数逻辑,并且在/dev/*目录下可以通过
- opne/read/write等API访问到vfb设备。但是不同与真正的framebuffer驱动(如s3c-fb.cs3c-2410.c), vfb并没有和实际显示相关的控制器(比如 SOC芯片中的LCD控制器)和寄存器交互,所以在用户态虽然可以像访问framebuffer设备一样访问vfb驱动,但是对vfb的读写不会对真正的LCD显示屏起作用,因而它是虚拟的framebuffer驱动。
- vfb驱动可以说给我们提供了一个framebuffer驱动的samplecode或者参考模板,驱动开发者可以参考该模板,修改实现真正的framebuffer显示屏驱动。
2. vfb驱动的继承关系结构分析
- vfb驱动是UML简化描述图如图1所示,我们可以根据该图用面向对象的思想分析代码的继承与聚合关系的。
Figure 1 vfb驱动的UML结构简化描述
- module模块管理接口用来管理整个代码模块的装载和卸载,并没有framebuffer显示功能上的特性,因而我们认为它是通用接口,vfb LIKE-A module。但是vfb的基类fb_info并没有module接口,因而module接口是在vfb这个具体的子类对象中才实现的。
- 一般阅读源代码都从module_init(vfb_init)中的vfb_init()开始,vfb_init()是vfb装载时的初始化函数,核心的功能就是platform_driver_register(&vfb_driver)和platform_device_add(vfb_device),这样就实现了platformdevice虚拟平台相关总线接口。
- platformbus/device/device_driver 虚拟总线平台接口如前一章所述,本质上在一个构造与实例化的接口, 当有真正的vfb device或者 vfb device_driver被装载到系统时,该接口的vfb_driver.probe()函数将被调用,probe()用于构造实例化一个vfb设备,并将该设备实例挂载到/dev/*目录下。
- 同理vfb_driver.remove()函数用于设备实例的析构。由于vfb是一个虚拟的frambebuffer驱动,因而struct platform_device *vfb_device;中并没有LCD控制器或者寄存器相关的资源描述,也就是说BSP硬件相关的代码描述是空的,只有struct platform_driver vfb_driver;中的驱动逻辑代码,因而它不能对真实的显示屏产生作用。对比s3c-2410fb.c framebuffer驱动的 struct platform_device s3c_device_lcd;(arch/arm/plat-s3c24xx/devs.c文件中)的bsp设备描述,可以发现真正的framebufferLCD驱动都会在struct platform_device 中描述相关的显示控制器和寄存器资源 。
- 由于vfb是虚拟的framebuffer驱动,platform deivce总线接口中suspend()休眠,resume()唤醒等电源管理相关的函数就没有实现了,在真正的s3c-2410fb.c驱动中,显示屏电源管理部分也是不可少的。
- vfb_driver.probe()的构造实例化函数中,通过framebuffer_alloc()分配vfb的fb_info对象实例,并用vfb_ops作为struct fb_ops对象的实现,覆盖fb_info基类中struct fb_ops对象的fb_info-> fbops的相关函数方法,从而实现多态函数覆盖。最后调用register_framebuffer(info);注册函数,注册这个info, info就是一个vfb对象的子类实例,它继承了fb_info的特性,并且使用vfb自定义的structfb_ops vfb_ops中的函数方法,实现了多态函数。
- register_framebuffer()函数会把info对象实例放到registered_fb[](在drivers/video/fbmem.c中)数组容器中,并且会在/dev/*目录下创建相应的字符次设备节点,从而一个vfb设备驱动的实例就抽象成/dev/目录下一个字符设备文件。
- 如面向对象地分析Linux内核设备驱动(1)中的继承基本规则所述,framebuffer系统中,由于有2级继承关系,从基类获取子类的方法是通过一个抽象层来管理基类与子类的继承关系。在fbmem_init()函数装载整个framebuffer系统时,会通过register_chrdev(FB_MAJOR,"fb",&fb_fops)创建一个framebuffer设备的主设备节点,这个设备节点我们称为framebuffer对象(实际上动态分配的,没有全局名称),framebuffer实际上这是这是继承了cdev对象的一个子类,用structfile_operations fb_fops里的函数覆盖了cdev对象的struct file_operations ops里的函数方法,从而实现多态。
- 当用户态应用程序通过open/read/write/ioctl等函数方法访问/dev/*目录下vfb的设备实例fbX时,实际上是通过上一段所述的framebuffer注册的主设备cdev节点中的fb_fops对象中对应的同名open/read/write/ioctl函数,同名函数再通过次设备节点号在registered_fb[]数组中找到对应的vfb对象对应的fb_info实例,再嵌套调用vfb对象对应的fb_info实例中同名的fb_open/fb_read/fb_write/fb_ioct函数,从而实现基类通过多态函数调用子类对应的多态函数实现的方法。
- 相关的代码实际上在面向对象地分析Linux内核设备驱动(1)中图8已经贴出,这里在图2中再次展示。
- 整个的read函数的多态调用链就是字符设备文件read(fd) -- > cdev.read() = fb_read() -- >vfb.fops.fb_read() = fb_sys_read()最终实现了基类通过多态调用子类函数的实现。整个过程需要阅读源码的同时,通过面向对象思想来分析模拟这种继承调用关系,才能看清楚整个系统从抽象的字符设备文件到具体的vfb驱动设备的一级一级的抽象关系。
Figure 2 framebuffer驱动代码中通过cdev基类调用fb_info子类从而实现多态函数调用的实现方法
3. 分析vfb驱动的实现
- 通过面向对象的思想的继承体系,我们能够从更高的层次看到了vfb驱动在Linux内核这栋大厦中被安放在哪个角落,使用了Linux内核提供的哪些零件。有了整体大局观之后,再阅读vfb驱动的设计细节,就不会有只见树木,不见森林的困惑了。
- vfb驱动继承了include/linux/fb.h中的 struct fb_info对象,struct fb_info对象所描述的结构中显示相关的变量和细节非常多,需要具备一些显示和色彩技术相关的业务背景知识,在具体调试的时候才能完全搞清楚每个细节。在struct fb_info对象中,最重要的组成者还是struct fb_ops *fbops,fbops包含了framebuffer驱动最核心的函数方法。
- vfb驱动的structfb_ops函数方法的具体实现代码如下:
static struct fb_ops vfb_ops = {
.fb_read = fb_sys_read,
.fb_write = fb_sys_write,
.fb_check_var = vfb_check_var,
.fb_set_par = vfb_set_par,
.fb_setcolreg = vfb_setcolreg,
.fb_pan_display = vfb_pan_display,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
.fb_mmap = vfb_mmap,
};
- 实际上,struct fb_ops将从cdev的struct file_operations中继承的函数方法和framebuffer对象fb_info所特有的函数方法混在一个结构中了。fb_read/fb_write/fb_mmap这些函数方法实际上都是继承struct file_operations中的/read/write/mmap函数方法,从fbmem.c中的 structfile_operations fb_fops中的函数实现中可以看出,fb_read/fb_write/fb_mmap都会被structfile_operations fb_fops中的read/write/mmap同名函数调用,用以模拟基类调用子类的多态函数。
- 由于vfb是虚拟的framebuffer驱动,并没有直接操作硬件显示器相关的寄存器,所以fb_read/fb_write都是使用了Linux内核已经实现的fb_sys_read/fb_sys_write函数,所以可以说是直接使用基类framebuffer的已经实现的函数方法,没有覆盖。
- fb_mmap 使用了vfb自己实现的vfb_mmap,vfb_mmap实际上是一个sample code函数,给用户参考如何把虚拟的地址的vma描述的区域remap到实际的物理页面。
- fb_check_var与fb_set_par一般是用户ioctl操作时,framebuffer做参数检查和参数设置的函数,vfb驱动使用了自己的实现方式,实现了多态函数。
- fb_setcolreg与fb_pan_display分别是用于设置伪调色板和pandisplay和平移显示的函数,这个也是vfb自己实现的多态函数,覆盖了基类的函数。调试板可以看做一个数组容易保存了常用色彩的编码,调色板可以用索引号获取颜色的编码,刷新屏幕,这样可以节约显示像素帧所占的空间大小(32位的色彩码,用8位的索引码保存,去32位调色板容器中索引,进而每个像素存储空间压缩到四分之一)。
- fb_fillrect/ fb_copyarea/ fb_imageblit分别是显示屏矩形填充,区域拷贝和图像拷贝的加速函数,一般的显卡驱动都会有自己特定的显示加速驱动函数,使得显示更加流畅逼真。由于vfb是虚拟的显示驱动,没有真正的显示屏,因而直接采用Linux内核默认实现的sys_fillrect/ sys_copyarea/ sys_imageblit,这样就没有覆盖基类的函数,直接使用基类framebuffer的已经实现的函数方法。
- 最后,深入到每个函数的实现细节,根据源码的流程分析每个函数实现的时候到底做了什么,这就回到了C程序员最熟悉的面向过程思维上来了,需要读者进一步深入源码了。
4. 小结
- Linux内核驱动中面向对象的基本规则和实现方法与 Linux内核驱动中面向对象的基本规则和实现方法 两篇文章都是在讲面向对象的方法论,是让读者能够通过一些规则,从而将面向对象的思想带入到Linux设备驱动的C语言程序中,在更高的层次阅读Linux设备驱动程序,明白程序之间的结构关系,对整体结构有自己的把握。
- 第三篇文章用面向对象思想分析vfb Framebuffer设备驱动是通过一个设备驱动实际例子,使用之前的方法论,面向对象地分析一个真正Linux设备驱动程序。从而让读者知道阅读的驱动代码在Linux内核大厦中的位置,也知道Linux内核为驱动开发者提供了哪些零部件。当然具体到驱动程序源代码,每个函数的实现细节,以及每个细节产生的效果,还需要读者仔细研读,并且通过debug做实验来自我尝试,本文也只能点到为止。
- 当然这些方法中,有很多不够严谨支持,也有一些不足与缺陷,希望读者多留言评论,文中的笔误或者有缺陷之后,作者会在以后继续修改补丁。