1 FrameBuffer的发展历史
参考文档:http://blog.csdn.net/yangwen123/article/details/12096483
计算机研究者从很早开始已经开始讨论理论上FrameBuffer的优点,但却一直苦于没有能力生产一台拥有足够内存的计算机。1969年,贝尔实验室的JoanMiller试验了第一个已知的FrameBuffer。该设备显示了一幅3位位深的图片。然而,直到20世纪70年代,集成电路的内存芯片上的进展才使得制造一个可以显示标准视频图像的FrameBuffer成为可能。
1972年,Richard Shoup在施乐帕洛阿尔托研究所里设计了SuperPaint系统,该系统拥有311,040字节的内存,并能存储640*480像素8位位深的图片。这些内存分散在16个回路电板上,每个电板上装有多个2千比特的片选芯片。要使得整个系统可以正常工作,Richard Shoup的设计必须保证整个FrameBuffer实现为307,200字节的片选注册芯片并保证能和电视信号同步。这种设计最初的缺点已经显露出来,即内存不能随机访问。当然,指定位置的内存在要求的扫描线开始扫描的时候可以访问。这样使得该系统存在着极大的潜在缺陷,导致写入数据到FrameBuffer时有33毫秒的延迟。
同时,Richard Shoup可以用SuperPaint系统制造一个早期的数字视频捕获系统。通过将输出信号同步到输入信号,Richard Shoup可以重写每个像素点的数据。Richard Shoup还曾经试验用色彩表来修改输出信号,这些色彩表允许SuperPaint系统产生多种多样的超出8位数据所包含色彩限制之外的颜色。这种体制在不久的将来成为计算机帧缓冲区中司空见惯的体制。
1974年,Evans和Sutherland发布了第一个商业版的FrameBuffer,花费15,000美元。它能够在8位灰度级模式下产生512*512个像素点的分辨率,并成为那些没有资源为自己建立FrameBuffer的研究者们的福利。不久的将来,美国纽约理工大学用3个Evans和Sutherland的系统制造了第一个24位的色彩系统,每一个FrameBuffer被连接到一个RGB色彩输出,用一个微型计算机控制三个设备作为一个设备。
20世纪70年代末,集成电路技术的飞速发展使得家庭计算机包含低色彩FrameBuffer成为可能。虽然相比在某些计算机中使用的类似于Atari 400这种更加精确的图形设备,低色彩FrameBuffer因其差劲的表现总是被嘲笑,FrameBuffer却因此在个人计算机中成为一种标准。今天,几乎所有有图形处理能力的计算机都利用FrameBuffer来产生视频信号。
80年代,FrameBuffer在高端智能终端开始流行。SGI公司,Sun公司,HP公司,DEC公司,IBM公司都为他们的计算机发布了FrameBuffer版本,这些FrameBuffer通常情况下比大部分家庭计算机具有更高的质量,应用于电视,印刷,计算机建模和3D图形技术等。
Amiga计算机由于其在图形处理方面的出色能力,创造了一个基于图形卡的广阔的FrameBuffer市场。值得一提的是,在Amiga A2500 Unix计算机中使用的图形卡,它是1991年出现的第一台实现以X11服务器作为图形环境和Open Look GUI高分辨率(1024*1024或1024*768,256色)图形界面的宿主机服务器的计算机。这块被装在A2500 Unix上的图形卡被称作A2400,它是以德州仪器公司TMS34010图形处理器为基础的图形板,时钟频率为50MHz,是一个完全只能的协处理器。A2410图形卡是Amiga公司和Lowell大学共同研发的。其他值得一提的是,Amiga的FrameBuffer是基于GVP公司的一个很有趣的视频集成系列中的Impact Vision IV24图形卡实现的,它有能力将同步锁信号、色度和电视信号混合到24位的FrameBuffer中,直到今天,它还一直在市场中活跃着,如图形卡视频捕获系统DCTV,32位图形卡Firecracker,Harlequin卡等等一直还在使用它。
2 FrameBuffer相关概念
FrameBuffer中文译名为帧缓冲驱动,它是出现在2.2.xx内核中的一种驱动程序接口。
Linux是工作在保护模式下,所以用户态进程是无法象DOS那样使用显卡BIOS里提供的中断调用来实现直接写屏,Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。FrameBuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过FrameBuffer的读写直接对显存进行操作。用户可以将FrameBuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由FrameBuffer设备驱动来完成的。
但FrameBuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池。CPU将运算后的结果放到这个水池,水池再将结果流到显示器,中间不会对数据做处理。应用程序也可以直接读写这个水池的内容在这种机制下,尽管FrameBuffer需要真正的显卡驱动的支持,但所有显示任务都有CPU完成,因此CPU负担很重。
帧缓冲驱动应用广泛,在 linux 的桌面系统中,X window 服务器就是利用帧缓冲进行窗口的绘制。尤其是通过帧缓冲可显示汉字点阵,成为Linux 汉化的唯一可行方案。
在开发者看来,FrameBuffer 本质上是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16位色的FrameBuffer来说,FrameBuffer 中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
帧缓存可以在系统存储器(内存)的任意位置,视频控制器通过访问帧缓存来刷新屏幕。帧缓存也叫刷新缓存FrameBuffer或RefreshBuffer,这里的帧(frame)是指整个屏幕范围。
帧缓存有个地址,是在内存里。我们通过不停的向 FrameBuffer 中写入数据, 显示控制器就自动的从FrameBuffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
CPU 指定显示控制器工作,则显示控制器根据CPU的控制到指定的地方去取数据和指令,目前的数据一般是从显存里取,如果显存里存不下,则从内存里取, 内存也放不下,则从硬盘里取,当然也不是内存放不下,而是为了节省内存的话, 可以放在硬盘里,然后通过指令控制显示控制器去取。帧缓存 FrameBuffer里面存储的东西是一帧一帧的, 显卡会不停的刷新 FrameBuffer, 这每一帧如果不捕获的话,则会被丢弃,也就是说是实时的。这每一帧不管是保存在内存还是显存里, 都是一个显性的信息,这每一帧假设是800x600的分辨率,则保存的是800x600个像素点和颜色值。
3 FrameBuffer使用基础
framebuffer的设备文件一般是 /dev/fb0、/dev/fb1 等等。
可以用命令: #dd if=/dev/zero of=/dev/fb 清空屏幕。
如果显示模式是 1024x768-8 位色,用命令:$ dd if=/dev/zero of=/dev/fb0 bs=1024 count=768 清空屏幕;
用命令: #dd if=/dev/fb of=fbfile 可以将fb中的内容保存下来;
可以重新写回屏幕: #dd if=fbfile of=/dev/fb;
在使用Framebuffer时,Linux是将显卡置于图形模式下的。
在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用,比如下面的程序就打开 /dev/fb0 设备,并通过 mmap 系统调用进行地址映射,随后用memset 将屏幕清空(这里假设显示模式是 1024x768-8 位色模式,线性内存模式):
int fb;
unsigned char*fb_mem;
fb = open("/dev/fb0", O_RDWR);
fb_mem = mmap(NULL, 1024*768, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
memset (fb_mem,0, 1024*768); //这个命令应该只有在root可以执
FrameBuffer 设备还提供了若干 ioctl 命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。
通过 FrameBuffer 设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(2.4.9)中,就包含有对 S3、Matrox、nVidia、3Dfx 等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将 PCI 设备的内存I/O(memio)映射到进程的地址空间。这些 memio 一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。
PCI 设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为"memio"。一旦被映射到物理内存,Linux 的普通进程就可以通过mmap 将这些内存 I/O 映射到进程地址空间,这样就可以直接访问这些寄存器了。
当然,因为不同的显示芯片具有不同的加速能力,对memio 的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。
FrameBuffer 只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在 FrameBuffer 之上进行图形编程,还需要自己动手完成其他许多工作。
Framebuffer的具体操作流程如下:
1)打开/dev/fb设备文件;
2)用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
3)将屏幕缓冲区映射到用户空间。
4)映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。
4 FrmaeBuffer内部结构
FrameBuffer设备驱动基于如下两个文件:
1)linux/include/linux/fb.h
2)linux/drivers/video/fbmem.c
下面分析这两个文件。
1. fb.h
几乎主要的结构都是在这个中文件定义的。这些结构包括:
1)fb_var_screeninfo
这个结构描述了显示卡的特性。
2)fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
3) fb_cmap
描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜色映射信息。
4) fb_info
定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。
5) struct fb_ops
用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。
6) structfbgen_hwswitch
这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用。
2. fbmem.c
fbmem.c 处于FrameBuffer设备驱动技术的中心位置。它为上层应用程序提供系统调用也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己。fbmem.c 为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作。
1) 全局变量
struct fb_info*registered_fb[FB_MAX];
intnum_registered_fb;
这两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info 结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info 结构就会添加到这个结构中,同时num_registered_fb 为自动加1。
2)fbmem.c 实现了如下函数
register_framebuffer(structfb_info *fb_info);
unregister_framebuffer(structfb_info *fb_info);
这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
5 展讯基于framebuffer的显示驱动
众所周知,Android的显示驱动是基于Linux标准的FrameBuffer驱动,用来抽象出Android的底层显示驱动,以屏蔽不同的显示硬件造成的不兼容,下面就以展讯平台的显示驱动来分析一下FrameBuffer驱动,展讯在底层用一个叫做sprdfb_device的设备驱动来建立一个基于FrameBuffer的设备,其设备结构体定义如下:
struct sprdfb_device {
struct fb_info *fb;
struct ops_mcu *ops;
struct lcd_spec *panel;
int open;
int id;
uint32_t width;
uint32_t height;
uint32_t bpp;
uint32_t vsync_waiter;
uint32_t device_id;
uint32_t timing[2];
uint32_t reserved[2];
uint32_t run;
void (*update_lcm)(struct sprdfb_device *dev);
uint32_t fb_state;
uint32_t need_reinit;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;
#endif
#if CONFIG_CPU_FREQ
struct notifier_block freq_transition;
struct notifier_block freq_policy;
#endif
};
显示驱动在加载的过程中会为基于的FrameBuffer的显示驱动分配一段内存,至此基于FrameBuffer的显示驱动设备已经建立,
struct fb_info *fb;
struct sprdfb_device *dev;
struct sprd_lcd_platform_data* platform_data = pdev->dev.platform_data;
int32_t ret;
printk("sprdfb initialize!\n");
fb = framebuffer_alloc(sizeof(struct sprdfb_device), &pdev->dev);
if (!fb) {
ret = -ENOMEM;
goto err0;
}
显示驱动设备创建,并完成注册,Android的显示驱动完成基本的初始化工作,LCD控制器初始化,然后加载具体屏的驱动,
ret = setup_fbmem(dev, pdev);
if (ret) {
goto cleanup;
}
setup_fb_info(dev);
ret = register_framebuffer(fb);
if (ret) {
goto cleanup;
}
if (dev->fb_state == FB_NORMAL) {
lcdc_init(dev);
}
6 双缓冲机制
最早解释多缓冲区如何工作的方式,是通过一个现实生活中的实例来解释的。在一个阳光明媚的日子,你想将水池里的水换掉,而又找不到水管的时候,你就只能用木桶来灌满水池。当木桶被水龙头注满的,关掉水龙头,走到水池旁边,将水到进去,然后走回到水龙头旁边继续重复上述工作,如此往复直到将水池灌满。这就类似单缓冲工作过程。当你想将木桶里的水倒出的时候,你必须关掉水龙头。
现在假设你用两个木桶来做上面的工作。你会注满第一个木桶然后将第二个木桶换到水龙头下面,这样,在第二个水桶注满的时间内,你就可以将第一个木桶里面的水倒进水池里面,当你回来的时候,你只需要再将第一个木桶换下第二个注满水木桶,当第一个木桶开始注水的时候你就将第二个木桶里面的水倒进水池里面。重复这个过程直到水池被注满。很容易看得到用这种技术注满水池将会更快,同时也节省了很多等待木桶被注满的时间,而这段时间里你什么也做不了,而水龙头也就不用等待从木桶被注满到你回来的这段时间了。
当你雇佣另外一个人来搬运一个被注满的木桶时,这就有点类似于三个缓冲区的工作原理。如果将搬运木桶的的时间很长,你可以用更多的木桶,雇佣更多的人,这样水龙头就会一直开着注满木桶了。
在计算机图形学中,双缓冲是一种画图技术,使用这种技术可以使得画图没有(至少是减少)闪烁、撕裂等不良效果,并减少等待时间。
双缓冲机制的原理大概是:所有画图操作将它们画图的结果保存在一块系统内存区域中,这块区域通常被称作“后缓冲区(back buffer)”,当所有的绘图操作结束之后,将整块区域复制到显示内存中,这个复制操作通常要跟显示器的光栈束同步,以避免撕裂。双缓冲机制必须要求有比单缓冲更多的显示内存和CPU消耗时间,因为“后缓冲区”需要显示内存,而复制操作和等待同步需要CPU时间。
基于双缓冲机制可以实现页交换。
页交换初始状态如图所示:
如上图所示,此时由于处于初始状态,画图操作的结果都在后缓冲区中,而屏幕上显示的则是前缓冲区中的内容。此时画图操作尚未完成。
画图操作完成之后,页转换操作开始执行,示意图如图所示:
如上图所示,画图操作结束,下一个画图操作的结果保存对象指向前缓冲区,屏幕的显示对象指向后缓冲区,此时前缓冲区变成实际意义上的后缓冲区,后缓冲区变成实际意义上的前缓冲去,即实现“页交换”操作。
有时候也在页交换链中设置多个“后缓冲区”,这是就需要多缓冲区机制的支持。