FROM:
http://bbs.chinaunix.net/viewthread.php?tid=1932291&extra=page%3D1%26amp%3Bfilter%3Dtype%26amp%3Btypeid%3D610
(一)、一个使用FrameBuffer的例子
1. FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
2. 由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动 的话,是可以实现的).
3. 对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。
好,现在可以让我们开始实现直接写屏:
1、打开一个FrameBuffer设备
2、通过mmap调用把显卡的物理内存空间映射到用户空间
3、直接写内存。
/********************************
File name : fbtools.h
*/
#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#include <linux/fb.h>
//a framebuffer device structure;
typedef struct fbdev{
int fb;
unsigned long fb_mem_offset;
unsigned long fb_mem;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
char dev[20];
} FBDEV, *PFBDEV;
//open & init a frame buffer
//to use this function,
//you must set FBDEV.dev="/dev/fb0"
//or "/dev/fbX"
//it's your frame buffer.
int fb_open(PFBDEV pFbdev);
//close a frame buffer
int fb_close(PFBDEV pFbdev);
//get display depth
int get_display_depth(PFBDEV pFbdev);
//full screen clear
void fb_memset(void *addr, int c, size_t len);
#endif
/******************
File name : fbtools.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <asm/page.h>
#include "fbtools.h"
#define TRUE 1
#define FALSE 0
#define MAX(x,y) ((x)>(y)?(x)y))
#define MIN(x,y) ((x)<(y)?(x)y))
//open & init a frame buffer
int fb_open(PFBDEV pFbdev)
{
pFbdev->fb = open(pFbdev->dev, O_RDWR);
if(pFbdev->fb < 0)
{
printf("Error opening %s: %m. Check kernel config\n",
pFbdev->dev);
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_VSCREENINFO,&(pFbdev->fb_var)))
{
printf("ioctl FBIOGET_VSCREENINFO\n");
return FALSE;
}
if (-1 == ioctl(pFbdev->fb,FBIOGET_FSCREENINFO,&(pFbdev->fb_fix)))
{
printf("ioctl FBIOGET_FSCREENINFO\n");
return FALSE;
}
//map physics address to virtual address
pFbdev->fb_mem_offset = (unsigned long)(pFbdev->fb_fix.smem_start) &
(~PAGE_MASK);
pFbdev->fb_mem = (unsigned long int)mmap(NULL,
pFbdev->fb_fix.smem_len + pFbdev->fb_mem_offset, PROT_READ |
PROT_WRITE, MAP_SHARED, pFbdev->fb, 0);
if (-1L == (long) pFbdev->fb_mem)
{
printf("mmap error! mem:%d offset:%d\n", pFbdev->fb_mem,
pFbdev->fb_mem_offset);
return FALSE;
}
return TRUE;
}
//close frame buffer
int fb_close(PFBDEV pFbdev)
{
close(pFbdev->fb);
pFbdev->fb=-1;
}
//get display depth
int get_display_depth(PFBDEV pFbdev);
{
if(pFbdev->fb<=0)
{
printf("fb device not open, open it first\n");
return FALSE;
}
return pFbdev->fb_var.bits_per_pixel;
}
//full screen clear
void fb_memset (void *addr, int c, size_t len)
{
memset(addr, c, len);
}
//use by test
#define DEBUG
#ifdef DEBUG
main()
{
FBDEV fbdev;
memset(&fbdev, 0, sizeof(FBDEV));
strcpy(fbdev.dev, "/dev/fb0");
if(fb_open(&fbdev)==FALSE)
{
printf("open frame buffer error\n");
return;
}
fb_memset(fbdev.fb_mem + fbdev.fb_mem_offset, 0,
fbdev.fb_fix.smem_len);
fb_close(&fbdev);
}
(二)基于Linux核心的汉字显示的尝试
我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下运行一个如下的简单程序。
main ( )
{
puts("hello, world.\n");
}
puts 函数向缺省输出文件(/dev/tty1)发出写的系统调用write(2)。系统调用到linux核心里面对应的核心函数是console.c中的con_w rite(),con_write()最终会调用do_con_write()。在do_con_write( )中负责把"hello,world.\n"这个字符串放到tty1对应的缓冲区中去。
do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下do_con_write()这个函数的声明。
static int do_con_write(struct tty_struct * tty, int from_user, const
unsigned char *buf, int count)
其中tty是指向tty_struct结构的指针,这个结构里面存放着关于这个tty的所有信息(请参照
linux/include/linux/tty.h)。Tty_struct结构中定义了通用(或高层)tty的属性(例如宽度和高度等)。在do_con_ write()函数中用到了tty_struct结构中的driver_data变量。driver_data是一个vt_struct指针。在vt_struct结构中包 含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的数组vc_cons,这个数组就是各虚拟终端的私有 数据。
static int do_con_write(struct tty_struct * tty, int from_user,const
unsigned char *buf, int count)
{
struct vt_struct *vt = (struct vt_struct
*)tty->driver_data;//我们用到了driver_data变量
. . . . .
currcons = vt->vc_num; file://我们在这里的vc_nums就是1
. . . . .
}
要访问虚拟终端的私有数据,需使用vc_cons〔currcons〕.d指针。这个指针指向的结构含有当前虚拟终端上光标的位置、缓冲区的起始地址、缓冲区大 小等等。
"hello, world.\n"中的每一个字符都要经过conv_uni_to_pc()这个函数转换成8位的显示字符。这要做的主要目的是使不同语言的国家能把16位的UniCode码映射到8位的显示字符集上,目前还是主要针对欧洲国家的语言 ,映射结果为8位,不包含对双字节(double byte)的范围。
这种UNICODE到显示字符的映射关系可以由用户自行定义。在缺省的映射表上,会把中文的字符映射到其他的字符上,这是我们不希望看到也是不需要的。所以我们 有两个选择∶
1. 不进行conv_uni_to_pc( )的转换。
2. 加载符合双字节处理的映射关系,即对非控制字符进行1对1的不变映射。
我们自己定制的符合这种映射关系的UNICODE码表是direct.uni。要想查看/装载当前系统的unicode映射表,可使外部命令loadunima p。
经过conv_uni_to_pc( )转换之后,"hello,world.\n"中的字符被一个一个地填写到tty1的缓冲区中。然后do_con_write()调用下层的驱动,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA显存中去)。 sw->con_putcs(vc_cons〔currcons〕.d, (u16 *)draw_from, (u16*)draw_to-(u16 *)draw_from, y, draw_x);
之所以要调用底层驱动,是因为存在不同的显示设备,其对应VGA显存的存取方式也不一样。
上面的Sw->con_putcs()就会调用到fbcon.c中的fbcon_putcs()函数(con_putcs是一个函数的指针,在Framebuffer模式下指向fbcon_putcs()函数)。也就是说在do_con_write()函数中是直接调用了fbcon_putcs()函数来进行字符的绘制。比如说在256色模式下,真正负责输出的函数是void fbcon_cfb8_putcs(struct vc_data *conp, struct display *p,const unsigned short *s, int count, int yy, int xx)
显示中文
比如说我们试图输出一句中文∶putcs(你好\n);(你好的内码为0xc4,0xe3,0xba,0xc3)。这时候会怎么样呢,有一点可以肯定,"你好"肯定不会出现在屏幕上,国为核心中没有汉字字库,中 文显示就是无米之炊了.
1 在负责字符显示的void fbcon_cfb8_putcs()函数中,原有操作如下∶对于每个要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(低位字节是ASCII码,高8位是字符的属性),由于汉字是双字 节编码方式,所以这种操作是不可能显示出汉字的,只能显示出xxxx_putcs()是一个一个VGA字符.
要解决的问题∶
确保在do_con_write( )时uni□pc转换不会改变原有编码。一个很直接的实现方式就是加载一个我们自己定制的UNICODE映射表,loadunimapdirect.uni,或者直接把direct.uni置为核心的缺省映射表。 针对如上问题,我们要做的第一个尝试方案是如下。
首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs()函数,在fbcon_cfb8_putcs()中一次读两个WORD,检查这两个WORD的低位字节是否能拼成一个汉字,如果发现能拼成一个汉字,就算出这个汉字在汉字字库中的偏移,然后把它当成一个16
x 16的VGA字符来显示。
试验的结果表明∶
1. 能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一串汉字,则这半个汉字后面的汉字都会是乱码。这是半个汉字的问题。
2. 光标移动会破坏汉字的显示。表现为,光标移动过的汉字会变成乱码。这是因为光标的更新是通过xxxx_putc( )函数来完成的。
xxxx_putc( )函数与xxxx_putcs()函数实现的功能类似,但是xxxx_putc()函数只刷新一个字符而不是一个字符串,因而xxxx_putc()的输入参数是一个整数,而不是一个字符串的 地址。Xxxx_putc()函数的声明如下∶void fbcon_cfb8_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx)
下一个尝试方案就是同时修改xxxx_putcs()函数和xxxx_putc()函数。为了解决半个汉字的问题,每一次输出之前,都从屏幕当前行的起始位置开始扫描,以确定要输出的字符是否落在半个汉字的位置 上。如果是半个汉字的位置,则进行相应的调整,即从向前移动一个字节的位置开始输出。
这个方案有一个困难,即xxxx_putc( )函数不用缓冲区的地址,而是用一个整数作为参数。所以xxxx_putc()无法直接利用相邻的字符来判别该定符是否是汉字。
解决方案是,利用xxxx_putc( )的光标位置参数(yy,xx),可以逆推出该字符在缓冲区中的位置。但仍有一些小麻烦,在Linux的虚拟终端下,用户可能会上卷该屏幕(shift +
pageup),导致光标的y座标和相应字符在缓冲区的行数不一致。相应的解决方案是,在逆推的过程中,考虑卷屏的参量。
这样一来,我们就又进了一步,得到了一个相对更好的版本。但仍有问题没有解决。敲入turbonetcfg,会发现菜单的边框字符也被当成汉字显示。这是因为, 这种边框字符是扩展字符,也使用了字符的第8位,因而被当作汉字来显示。例如,单线一的制表符内码为0xC4,当连成一条长线就是由一连串0xC4组成,而0x C4C4正是汉字哪。于是水平的制表符被一连串的哪字替代了。要解决这个问题就非常不容易了,因为制表符的种类比较多,而且垂直制表符与其后面字符的组合型式又 多种多样,因而很难判断出相应位置的字符是不是制表符,从理论上说,无论采取什么样的排除算法,都必然存在误判的情况,因为总存在二义性,没有充足的条件来推断 出当前字符究竟是制表符还是汉字。
我们一方面寻找更好的排除组合算法,一方面试图寻找其它的解决方案。要想从根本上解决定个问题,必须利用其它的辅助信息,仅仅从缓冲区的字符来判断是不够的。
经过一番努力,我们发现,在UNIX中使用扩展字符时,都要先输出字符转义序列(Escape sequence)来切换当前字符集。字符转义序列是以控制字符Esc为首的控制命令,在UNIX的虚拟终端中完成终端控制命令,这种命令包括,移动光标座标、 卷屏、删除、切换字符集等等。也就是说在输出代表制表符的字符串之前,通常是要先输出特定的字符转义序列。
在console.c里,有根据字符转义序列命令来记录字符状态的变量。结合该变量提供的信息,就可以非常干净地把制表符与汉字区别开来。
在如上思路的指引下,我们又产生了新的解决方案。经过改动得到了另一各版本.
在这个新版本上,turbonetcfg在初次绘制的时候,制表符与汉字被清晰地区分开来,结果是非常正确的。但还有新的问题存在∶turbonetcfg在重绘的时候(如切换虚拟终端或是移动鼠标光标的时候),制表符还是变成了汉字,因为重绘完全依赖于缓冲区,而这时用来记录字符集状态的变量并不反映当前字符集 状态。问题还是没有最终解决。我们又回到了起点。∶(
看来问题的最终解决手段必须是把字符集的状态伴随每一个字符存在缓冲区中。让我们来研究一下缓冲区的结构。每一个字符占用16bit的缓冲区,低8位是ASCI I值,完全被利用,高8位包含前景颜色和背景颜色的属性,也没有多余的空间可以利用。因而只能另外开辟新的缓冲区。为了保持一致性,我们决定在原来的缓冲区后面 添加相同大小的缓冲区,用来存放是否是汉字的信息。
也许有读者会问,我们只需要为每个字符添加一bit的信息来标志是否是汉字就足够了,为什么还要开辟与原缓冲区大小相同的双倍缓冲区,是不是太浪费呢?我们先放 下这个问题,稍后再作回答。
其实,如果再添加一bit来标志是当前字符是汉字的左半边还是右半边的话,就会省去扫描屏幕上当前整行字符串的工作,这样一来,编程会更简单。但是有读者会问, 即使是这样,使用8bit总够用了吧?为什么还要使用16bit呢?
我们的作法是∶用低8位来存放汉字另外一半的内码,用高8位中的2 bit来存放上面所讲的辅助信息,高8位的剩余6位可以用来存放汉字或其它编码方式(如BIG5或日文、韩文)的信息,从而使我们可以实现同屏显示多种双字节语 言的字符而不会有相互干扰。另外,在编程时,双倍缓冲也比较容易计算。这样我们就回答了如上的两个问题。
迄今为止,我们有了一套彻底解决汉字和制表符相互干扰、半个汉字的刷新、重绘等问题的方案。剩下的就是具体编程实现的问题了。
但是,由于Framebuffer的驱动很多,修改每一个驱动的xxxx_putc()函数和xxxx_putcs()函数会是一项不小的工作,而且,改动驱动程序后,每种驱动的测试也是很麻烦的,尤其是对于有硬件加速的显卡,修改和测试会更不容易。那么,存不存在一种不需要 修改显卡驱动程序的方法呢?
经过努力,我们发现,可以在调用xxxx_putcs()或xxxx_putc()函数输出汉字之前,修改vga字库的指针使其指向所需显示的汉字在汉字字库中的位置,即把一个汉字当成两个vga
ASCII字符输出。也就是说,在内核中存在两个字库,一个是原有的vga字符字库,另一个是汉字字库,当我们需要输出汉字的时候,就把vga字库的指针指向汉 字字库的相应位置,汉字输出完之后,再把该指针指向vga字库的原有位置。
这样一来,我们只需要修改fbcon.c和console.c,其中console.c负责维护双倍缓冲区,把每一个字符的信息存入附加的缓冲区;
而fbcon.c负责利用双倍缓冲区中附加的信息,调整vga字库的指针,调用底层的显示驱动程序。这里还有几个需要注意的地方∶
1. 由于屏幕重绘等原因,调用底层驱动xxxx_putc()和xxxx_putcs()的地方有多处。我们作了两个函数分别包装这两个调用,完成替换字库、调用xxxx_putcs( )或xxxx_putc()、恢复字库等功能。
2. 为了实现向上滚屏(shift + pageup)时也能看到汉字,我们需要作另外的修改。
Linux 在设计虚拟终端的时候,提供了回顾被卷出屏幕以外的信息的功能,这就是用热键来向上滚屏(shift + pageup)。当前被使用的虚拟终端拥有一个公共的缓冲区(soft back),用来存放被滚出屏幕以外的信息。当切换虚拟终端的时候,公共缓冲区的内容会被清除而被新的虚拟终端使用。向上滚屏的时候,显示的是公共缓冲区中的内 容。因此,如果我们想在向上滚屏的时候看到汉字,公共缓冲区也必须加倍,以确保没有信息丢失。当滚出屏幕的信息向公共缓冲区填写的时候,必须把相应的附加信息也 填写进公共缓冲区的附加区域。
这就要求fbcon.c必须懂得利用公共缓冲区的附加信息。
当然,有另外一种偷懒的方法,那就是不允许用户向上滚屏,从而避免对公区缓冲区的处理。
3. 把不同的编码方式(GB、BIG5、日文和韩文)写成不同的module,以实现动态加载,从而使得扩展新的编码方式不需要重新编译核心。
测试
本文实现的Kernel Patch文件(patch.kernel.chinese)可以从http://www.turbolinux.com.cn下载。Cd /usr/src/(该目录下应有Linux核心源程序所在的目录linux/) patch -p0 -b <patch.kernel.chinesemake menuconfig 请选择Console drivers选项中的 〔*〕 Double Byte Character Display Support(EXPERIMENTAL)
〔*〕 Double Byte GB encode (module only)
〔*〕 VESA VGA graphics console
<*> Virtual Frame Buffer support (ONLY FOR TESTING!)
<*> 8 bpp packed pixels support
<*> 16 bpp packed pixels support
<*> VGA characters/attributes support
〔*〕 Select compiled-in fonts
〔*〕VGA 8x8 font
〔*〕VGA 8x16 font
make dep
make bzImage
make modules
make install
make modules_install
然后用新的核心启动。
Insmod encode-gb.o
*四、其它*
(一) 设置FrameBuffer
FrameBuffer,可以译作"帧缓冲",有时简称为fbdrv,基于fbdrv的console也被称之为fbcon。这是一种独立于硬件的抽象图形设备。FrameBuffer的优点在于其高度的可移植
性、易使用性、稳定性。使用Linux内核的FrameBuffer驱动(vesafb),可以轻松支持到1024X768X32bpp以上的分辩率。而且目前可得到的绝大多数linux版本所发行的内核中,已经预编译了FrameBuffer支持,通常不需要重新编译内核就可以使用。所以FrameBuffer也是zhcon推荐使用的驱动方式。
进入FrameBuffer可以简单地在系统启动时向kernel传送vga=mode-number的参数来激活FrameBuffer设备,如:
lilo:linux vga=305
将会启动1024x768x8bpp模式。
640x480 800x600 1024x768 1280x1024
8 bpp 769 771 773 775
16 bpp 785 788 791 794
32 bpp 786 789 792 795
(二) 要使linux缺省进入FrameBuffer,可以修改/etc/lilo.conf,加入一下语句:
vga=0x303退出编辑,执行:
lilo -v
重新启动linux,可以使其进入800x600的256色模式。
grub也是一样,在grub.conf中的kernel行后面写上vga=xxx就行了,也可以用vga=ask,让系统启动的时候询问你用多大的分辨率
(三)我编译内核时,选择framebuffer模式,启动时屏幕上有一企鹅图片,不知这是如何造成的这个图片可以去掉或改动吗?
可以将drivers/video/fbcon.c: fbcon_setup()中if (logo) { } 代码去掉。
转帖,原出处不可考
Framebuffer的配置及应用
借助于framebuffer,我们能够在console下面作很多事情。首先下载framebuffer的配置工具fbset:
# apt-get install fbset 下载完毕后,配置文件/etc/fb.modes随之产生。
比较简单的作法是用万能的vesafb,如果它被编译进了内核,如:
Device Drivers -> Graphics support ->
那么在grub内核引导那一行的后面加上vga=791 它的含义是VESA framebuffer console @ 1024x768x64k,进入系统后可以直接使用framebuffer,看一下这种情况下的各项数据: # fbset -s mode "1024x768-76" # D: 78.653 MHz, H: 59.949 kHz, V: 75.694 Hz geometry 1024 768 1024 768 16 timings 12714 128 32 16 4 128 4 rgba 5/11,6/5,5/0,0/0 endmode 用具体显卡的framebuffer驱动是另一种选择,拿Nvidia显卡为例,Nvidia显卡的xorg驱动模块与其framebuffer的驱动模块是互相排斥的,如果要用一个就必须清除另一个: # rmmod nvidia 装载nvidia的framebuffer驱动: # modprobe nvidiafb 装载成功的时候,会产生/dev/fb0设备,console屏幕上的字体会有变化。 看一下当前的配置: # fbset -s mode "1024x768-85" # D: 94.500 MHz, H: 68.677 kHz, V: 84.997 Hz geometry 1024 768 1024 32767 8 timings 10582 208 48 36 1 96 3 hsync high vsync high accel true rgba 8/0,8/0,8/0,0/0 endmode 需要改变一下geometry及色深: # fbset -g 1024 768 1024 768 32 # fbset -s mode "1024x768-85" # D: 94.500 MHz, H: 68.677 kHz, V: 84.997 Hz geometry 1024 768 1024 768 32 timings 10582 208 48 36 1 96 3 hsync high vsync high accel true rgba 8/16,8/8,8/0,8/24 endmode 我们把它与使用VESA ramebuffer后的数据比较一下,显然,根据具体的显卡来驱动framebuffer可以在颜色上达到最佳值,好,现在我们在console下面能够作的事情: 一、视频播放,可以用mplayer 或者 fbxine: # mplayer -vo fbdev -vf scale=1024:768 video_file.avi -vo fbdev 是告诉mplayer用framebuffer作视频驱动. -vf scale=1024:768 是全屏的方法,可按屏幕的具体情况作调整 用fbxine的话需要下载: # apt-get install xine-console 二、图片文件与pdf文件浏览: # apt-get install fbi 用这个软件包里的fbi可以浏览图片,fbgs可以观看pdf文件: # fbi -a *jpg # fbgs -c *pdf 三、中文显示: # apt-get install jfbterm # jfbterm 中文显示的效果完美。 文章出处:飞诺网(http://www.diybl.com/course/6_system/linux/Linuxjs/2008721/133651.html) ------------------------------------------------------------------------ 在内核Documentation/fb/vesafb.txt文件中,有如下vesa-framebuffer的说明 Switching modes is done using the vga=... boot parameter. Read Documentation/svga.txt for details. You should compile in both vgacon (for text mode) and vesafb (for graphics mode). Which of them takes over the console depends on whenever the specified mode is text or graphics. The graphic modes are NOT in the list which you get if you boot with vga=ask and hit return. The mode you wish to use is derived from the VESA mode number. Here are those VESA mode numbers: | 640x480 800x600 1024x768 1280x1024 ----+------------------------------------- 256 | 0x101 0x103 0x105 0x107 8位色 32k | 0x110 0x113 0x116 0x119 15位色 64k | 0x111 0x114 0x117 0x11A 16位色 16M | 0x112 0x115 0x118 0x11B 24位色 The video mode number of the Linux kernel is the VESA mode number plus 0x200. Linux_kernel_mode_number = VESA_mode_number + 0x200 So the table for the Kernel mode numbers are: | 640x480 800x600 1024x768 1280x1024 ----+------------------------------------- 256 | 0x301 0x303 0x305 0x307 8位色 32k | 0x310 0x313 0x316 0x319 15位色 64k | 0x311 0x314 0x317 0x31A 16位色 16M | 0x312 0x315 0x318 0x31B 24位色 To enable one of those modes you have to specify "vga=ask" in the lilo.conf file and rerun LILO. Then you can type in the desired mode at the "vga=ask" prompt. For example if you like to use 1024x768x256 colors you have to say "305" at this prompt. If this does not work, this might be because your BIOS does not support linear framebuffers or because it does not support this mode at all. Even if your board does, it might be the BIOS which does not. VESA BIOS Extensions v2.0 are required, 1.2 is NOT sufficient. You will get a "bad mode number" message if something goes wrong. 1. Note: LILO cannot handle hex, for booting directly with "vga=mode-number" you have to transform the numbers to decimal. 2. Note: Some newer versions of LILO appear to work with those hex values, if you set the 0x in front of the numbers. 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/songbohr/archive/2010/03/25/5415637.aspx |
Framebuffer使用测试
这两天拾起以前做过的Framebuffer,不相同的是以前在嵌入式上做的,现在在自己电脑上Debian上进行测试,不过都类似罢了,嵌入式里要初始化很多东西。下面具体列一下步骤。至于Framebuffer的原理,就我的理解是比较简单的,无非往mmap好的fb上填写显示数据罢了,不对这些数据进行处理,FrameBuffer 只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备,它需要真正的显卡驱动的支持。在这次测试中,我用了默认就安装的vesafb,好像又被称为万能Fb驱动。
1、 首先在系统Grub启动时按e进入命令启动行的编辑模式,改为:kernel /boot/vmlinuz-2.6.18-5-686 root=/dev/sda6 ro vga=791(vga=791表示fb用1024 * 768 * 16bpp,其他模式的参数可以上网查查);
2、 进入系统的命令行模式,编译fb测试例子:gcc fb_test.c;
3、 允许测试例子:sudo ./a.out >> fb.txt(必须要用超级用户权限,>>将屏幕打印写到fb.txt中),效果如下:
打印如下:
Fixed screen info: id: VESA VGA smem_start: 0xf0000000 smem_len: 3145728 type: 0 type_aux: 0 visual: 2 xpanstep: 0 ypanstep: 0 ywrapstep: 0 line_length: 2048 mmio_start: 0x0 mmio_len: 0 accel: 0 Variable screen info: xres: 1024 yres: 768 xres_virtual: 1024 yres_virtual: 768 yoffset: 0 xoffset: 0 bits_per_pixel: 16 grayscale: 0 red: offset: 11, length: 5, msb_right: 0 green: offset: 5, length: 6, msb_right: 0 blue: offset: 0, length: 5, msb_right: 0 transp: offset: 0, length: 0, msb_right: 0 nonstd: 0 activate: 0 height: -1 width: -1 accel_flags: 0x0 pixclock: 12714 left_margin: 128 right_margin: 32 upper_margin: 16 lower_margin: 4 hsync_len: 128 vsync_len: 4 sync: 0 vmode: 0 Frame Buffer Performance test... Average: 2508 usecs Bandwidth: 1196.172 MByte/Sec Max. FPS: 398.724 fps |
4、还可以通过fb在命令行模式下看视频,如:sudo mplayer –vo fbdev ./air_nessesity.mpg。
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
char *frameBuffer = 0;
//打印fb驱动中fix结构信息,注:在fb驱动加载后,fix结构不可被修改。
void
printFixedInfo ()
{
printf ("Fixed screen info:\n"
"\tid: %s\n"
"\tsmem_start: 0x%lx\n"
"\tsmem_len: %d\n"
"\ttype: %d\n"
"\ttype_aux: %d\n"
"\tvisual: %d\n"
"\txpanstep: %d\n"
"\typanstep: %d\n"
"\tywrapstep: %d\n"
"\tline_length: %d\n"
"\tmmio_start: 0x%lx\n"
"\tmmio_len: %d\n"
"\taccel: %d\n"
"\n",
finfo.id, finfo.smem_start, finfo.smem_len, finfo.type,
finfo.type_aux, finfo.visual, finfo.xpanstep, finfo.ypanstep,
finfo.ywrapstep, finfo.line_length, finfo.mmio_start,
finfo.mmio_len, finfo.accel);
}
//打印fb驱动中var结构信息,注:fb驱动加载后,var结构可根据实际需要被重置
void
printVariableInfo ()
{
printf ("Variable screen info:\n"
"\txres: %d\n"
"\tyres: %d\n"
"\txres_virtual: %d\n"
"\tyres_virtual: %d\n"
"\tyoffset: %d\n"
"\txoffset: %d\n"
"\tbits_per_pixel: %d\n"
"\tgrayscale: %d\n"
"\tred: offset: %2d, length: %2d, msb_right: %2d\n"
"\tgreen: offset: %2d, length: %2d, msb_right: %2d\n"
"\tblue: offset: %2d, length: %2d, msb_right: %2d\n"
"\ttransp: offset: %2d, length: %2d, msb_right: %2d\n"
"\tnonstd: %d\n"
"\tactivate: %d\n"
"\theight: %d\n"
"\t %d\n"
"\taccel_flags: 0x%x\n"
"\tpixclock: %d\n"
"\tleft_margin: %d\n"
"\tright_margin: %d\n"
"\tupper_margin: %d\n"
"\tlower_margin: %d\n"
"\thsync_len: %d\n"
"\tvsync_len: %d\n"
"\tsync: %d\n"
"\tvmode: %d\n"
"\n",
vinfo.xres, vinfo.yres, vinfo.xres_virtual, vinfo.yres_virtual,
vinfo.xoffset, vinfo.yoffset, vinfo.bits_per_pixel,
vinfo.grayscale, vinfo.red.offset, vinfo.red.length,
vinfo.red.msb_right, vinfo.green.offset, vinfo.green.length,
vinfo.green.msb_right, vinfo.blue.offset, vinfo.blue.length,
vinfo.blue.msb_right, vinfo.transp.offset, vinfo.transp.length,
vinfo.transp.msb_right, vinfo.nonstd, vinfo.activate,
vinfo.height, vinfo.width, vinfo.accel_flags, vinfo.pixclock,
vinfo.left_margin, vinfo.right_margin, vinfo.upper_margin,
vinfo.lower_margin, vinfo.hsync_len, vinfo.vsync_len,
vinfo.sync, vinfo.vmode);
}
//画大小为width*height的同色矩阵,8alpha+8reds+8greens+8blues
void
drawRect_rgb32 (int x0, int y0, int width, int height, int color)
{
const int bytesPerPixel = 4;
const int stride = finfo.line_length / bytesPerPixel;
int *dest = (int *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset);
int x, y;
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
dest[x] = color;
}
dest += stride;
}
}
//画大小为width*height的同色矩阵,5reds+6greens+5blues
void
drawRect_rgb16 (int x0, int y0, int width, int height, int color)
{
const int bytesPerPixel = 2;
const int stride = finfo.line_length / bytesPerPixel;
const int red = (color & 0xff0000) >> (16 + 3);
const int green = (color & 0xff00) >> (8 + 2);
const int blue = (color & 0xff) >> 3;
const short color16 = blue | (green << 5) | (red << (5 + 6));
short *dest = (short *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset);
int x, y;
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
dest[x] = color16;
}
dest += stride;
}
}
//画大小为width*height的同色矩阵,5reds+5greens+5blues
void
drawRect_rgb15 (int x0, int y0, int width, int height, int color)
{
const int bytesPerPixel = 2;
const int stride = finfo.line_length / bytesPerPixel;
const int red = (color & 0xff0000) >> (16 + 3);
const int green = (color & 0xff00) >> (8 + 3);
const int blue = (color & 0xff) >> 3;
const short color15 = blue | (green << 5) | (red << (5 + 5)) | 0x8000;
short *dest = (short *) (frameBuffer)
+ (y0 + vinfo.yoffset) * stride + (x0 + vinfo.xoffset);
int x, y;
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
dest[x] = color15;
}
dest += stride;
}
}
void
drawRect (int x0, int y0, int width, int height, int color)
{
switch (vinfo.bits_per_pixel)
{
case 32:
drawRect_rgb32 (x0, y0, width, height, color);
break;
case 16:
drawRect_rgb16 (x0, y0, width, height, color);
break;
case 15:
drawRect_rgb15 (x0, y0, width, height, color);
break;
default:
printf ("Warning: drawRect() not implemented for color depth %i\n",
vinfo.bits_per_pixel);
break;
}
}
#define PERFORMANCE_RUN_COUNT 5
void
performSpeedTest (void *fb, int fbSize)
{
int i, j, run;
struct timeval startTime, endTime;
unsigned long long results[PERFORMANCE_RUN_COUNT];
unsigned long long average;
unsigned int *testImage;
unsigned int randData[17] = {
0x3A428472, 0x724B84D3, 0x26B898AB, 0x7D980E3C, 0x5345A084,
0x6779B66B, 0x791EE4B4, 0x6E8EE3CC, 0x63AF504A, 0x18A21B33,
0x0E26EB73, 0x022F708E, 0x1740F3B0, 0x7E2C699D, 0x0E8A570B,
0x5F2C22FB, 0x6A742130
};
printf ("Frame Buffer Performance test...\n");
for (run = 0; run < PERFORMANCE_RUN_COUNT; ++run)
{
/* Generate test image with random(ish) data: */
testImage = (unsigned int *) malloc (fbSize);
j = run;
for (i = 0; i < (int) (fbSize / sizeof (int)); ++i)
{
testImage[i] = randData[j];
j++;
if (j >= 17)
j = 0;
}
gettimeofday (&startTime, NULL);
memcpy (fb, testImage, fbSize);
gettimeofday (&endTime, NULL);
long secsDiff = endTime.tv_sec - startTime.tv_sec;
results[run] =
secsDiff * 1000000 + (endTime.tv_usec - startTime.tv_usec);
free (testImage);
}
average = 0;
for (i = 0; i < PERFORMANCE_RUN_COUNT; ++i)
average += results[i];
average = average / PERFORMANCE_RUN_COUNT;
printf (" Average: %llu usecs\n", average);
printf (" Band %.03f MByte/Sec\n",
(fbSize / 1048576.0) / ((double) average / 1000000.0));
printf (" Max. FPS: %.03f fps\n\n",
1000000.0 / (double) average);
/* Clear the framebuffer back to black again: */
memset (fb, 0, fbSize);
}
int
main (int argc, char **argv)
{
const char *devfile = "/dev/fb0";
long int screensize = 0;
int fbFd = 0;
/* Open the file for reading and writing */
fbFd = open (devfile, O_RDWR);
if (fbFd == -1)
{
perror ("Error: cannot open framebuffer device");
exit (1);
}
//获取finfo信息并显示
if (ioctl (fbFd, FBIOGET_FSCREENINFO, &finfo) == -1)
{
perror ("Error reading fixed information");
exit (2);
}
printFixedInfo ();
//获取vinfo信息并显示
if (ioctl (fbFd, FBIOGET_VSCREENINFO, &vinfo) == -1)
{
perror ("Error reading variable information");
exit (3);
}
printVariableInfo ();
/* Figure out the size of the screen in bytes */
screensize = finfo.smem_len;
/* Map the device to memory */
frameBuffer =
(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,
fbFd, 0);
if (frameBuffer == MAP_FAILED)
{
perror ("Error: Failed to map framebuffer device to memory");
exit (4);
}
//测试virt fb的性能
performSpeedTest (frameBuffer, screensize);
printf ("Will draw 3 rectangles on the screen,\n"
"they should be colored red, green and blue (in that order).\n");
drawRect (vinfo.xres / 8, vinfo.yres / 8,
vinfo.xres / 4, vinfo.yres / 4, 0xffff0000);
drawRect (vinfo.xres * 3 / 8, vinfo.yres * 3 / 8,
vinfo.xres / 4, vinfo.yres / 4, 0xff00ff00);
drawRect (vinfo.xres * 5 / 8, vinfo.yres * 5 / 8,
vinfo.xres / 4, vinfo.yres / 4, 0xff0000ff);
sleep (5);
printf (" Done.\n");
munmap (frameBuffer, screensize); //解除内存映射,与mmap对应
close (fbFd);
return 0;
}