一、FrameBuffer的原理
framebuffer的设备文件一般是
可以用命令: #dd if=/dev/zero of=/dev/fb
如果显示模式是
用命令: #dd if=/dev/fb of=fbfile
可以重新写回屏幕: #dd if=fbfile of=/dev/fb;
在使用Framebuffer时,Linux是将显卡置于图形模式下的.
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在Linux中的实现和机制
Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为fbcon.c,在这个目录下还有与各种显卡驱动相关的源文件。
(一)、分析Framebuffer设备驱动
FrameBuffer设备驱动基于如下两个文件:
1) linux/include/linux/fb.h
2) linux/drivers/video/fbmem.c
下面分析这两个文件。
1、fb.h
1)fb_var_screeninfo
NOTE::::
struct fb_var_screeninfo
{
__u32 xres;
__u32 yres;
__u32 xres_virtual;
__u32 yres_virtual;
__u32 xoffset; //可视区域的偏移
__u32 yoffset;
__u32 bits_per_pixel;
__u32 grayscale; //等于零就成黑白
struct fb_bitfield red; 真彩的bit机构
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
__u32 nonstd;
__u32 activate;
__u32 height;
__u32 width;
__u32 accel_flags;
时序-_-这些部分就是显示器的显示方法了,可以找相关的资料看看
__u32 pixclock;
__u32 left_margin;
__u32 right_margin;
__u32 upper_margin;
__u32 lower_margin;
__u32 hsync_len;
__u32 vsync_len;
__u32 sync;
__u32 vmode;
__u32 reserved[6];
};
2) fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
struct fb_fix_screeninfo {
char id[16]; ID
unsigned long smem_start;
__u32 smem_len;
__u32 type;
__u32 type_aux; 插入区域?
__u32 visual;
__u16 xpanstep; 没有硬件设备就为零
__u16 ypanstep;
__u16 ywrapstep;
__u32 line_length;
unsigned long mmio_start; 内存映射的I/O起始
__u32 mmio_len; I/O的大小
__u32 accel;
__u16 reserved[3];
};
3) fb_cmap
描述设备无关的颜色映射信息。可以通过FBIOGETCMAP
struct fb_cmap {
__u32 start;
__u32 len;
__u16 *red;
__u16 *green;
__u16 *blue;
__u16 *transp;
};
4) fb_info
定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针,
struct fb_info {
char modename[40];
kdev_t node;
int flags;
int open;
#define FBINFO_FLAG_MODULE 1
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_monspecs monspecs;
struct fb_cmap cmap;
struct fb_ops *fbops;
char *screen_base;
struct display *disp; 初始化
struct vc_da
char fontname[40]; 默认的字体
devfs_handle_t devfs_handle;
devfs_handle_t devfs_lhandle; 兼容
int (*changevar)(int);
int (*switch_con)(int, struct fb_info*);
int (*updatevar)(int, struct fb_info*);
void (*blank)(int, struct fb_info*); 告诉fb使用黑白模式(或者不黑)
arg=0的时候黑白模式
arg>0时候选择VESA模式
void *pseudo_palette;
void *par;
};
5) struct fb_ops
用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。
struct fb_ops {
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con,
struct fb_info *info);
int (*fb_get_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
int (*fb_set_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
int (*fb_pan_display)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg, int con, struct fb_info *info);
int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma);
int (*fb_rasterimg)(struct fb_info *info, int start);
};
6) structure map
struct fb_info_gen | struct fb_info | fb_var_screeninfo
[编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了]
这个结构
2、
fbmem.c
1)
struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;
这两变量记录了所有fb_info
static struct {
const char *name;
int (*init)(void);
int (*setup)(void);
} fb_drivers[] __initdata= { ....};
如果FrameBuffer设备被静态链接到内核,其对应的入口就会添加到这个表中;如果是动态加载的,即使用insmod/rmmod,就不需要关心这个表。
static struct file_operations fb_ops ={
owner: THIS_MODULE,
read: fb_read,
write: fb_write,
ioctl: fb_ioctl,
mmap: fb_mmap,
open: fb_open,
release: fb_release
};
这是一个提供给应用程序的接口.
2)fbmem.c
register_framebuffer(struct fb_info *fb_info);
unregister_framebuffer(struct fb_info *fb_info);
这两个是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两函数向系统注册或注销自己。几乎底层设备驱动所要做的所有事情就是填充fb_info结构然后向系统注册或注销它。
(二)一个LCD显示芯片的驱动实例
1)在系统内存中分配显存
在fbmem.c文件中可以看到, file_operations
2)实现
用户应用程序通过ioctl()系统调用操作硬件,fb_ops
FBIOGET_VSCREENINFO fb_get_var
FBIOPUT_VSCREENINFO fb_set_var
FBIOGET_FSCREENINFO fb_get_fix
FBIOPUTCMAP fb_set_cmap
FBIOGETCMAP fb_get_cmap
FBIOPAN_DISPLAY fb_pan_display
如果我们定义了fb_XXX_XXX
文件linux/drivers/video/fbgen.c或者linux/drivers/video目录下的其它设备驱动是比较好的参考资料。在所有的这些函数中fb_set_var()是最重要的,它用于设定显示卡的模式和其它属性,下面是函数fb_set_var()的执行步骤:
1)检测是否必须设定模式
2)设定模式
3)设定颜色映射
4)
第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到
三、FrameBuffer的应用
(一)、一个使用FrameBuffer的例子
FrameBuffer主要是根据VESA标准的实现的,所以只能实现最简单的功能。
由于涉及内核的问题,FrameBuffer是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动的话,是可以实现的).
对FrameBuffer的操作,会直接影响到本机的所有控制台的输出,包括XWIN的图形界面。
在struct fb_info
就可以实现显示的中文化----难道
好,现在可以让我们开始实现直接写屏:
1、打开一个FrameBuffer设备
2、通过mmap调用把显卡的物理内存空间映射到用户空间
3、直接写内存。
#ifndef _FBTOOLS_H_
#define _FBTOOLS_H_
#include <linux/fb.h>
//a framebuffer device structure;
typedef struct fbdev{
} 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
#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
#define FALSE
#define MAX(x,y)
#define MIN(x,y)
//open & init a frame buffer
int fb_open(PFBDEV pFbdev)
{
}
//close 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)
{
}
//use by test
#define DEBUG
#ifdef DEBUG
main()
{
}
(二)基于Linux核心的汉字显示的尝试
我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下运行一个如下的简单程序。
main ( )
{
puts("hello, world.\n");
}
puts
do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下do_con_write()这个函数的声明。
static int do_con_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
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_da
. . . . .
currcons = vt->vc_num; file://我们在这里的vc_nums就是1
. . . . .
}
不进行conv_uni_to_pc( )的转换。
加载符合双字节处理的映射关系,即对非控制字符进行1对1的不变映射。我们自己定制的符合这种映射关系的UNICODE码表是direct.uni。要想查看/装载当前系统的unicode映射表,可使外部命令loadunimap。
经过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模式下指向
显示中文
要解决的问题∶
确保在do_con_write( )时uni_pc转换不会改变原有编码。一个很直接的实现方式就是加载一个我们自己定制的UNICODE映射表,loadunimapdirect.uni,或者直接把direct.uni置为核心的缺省映射表。
针对如上问题,我们要做的第一个尝试方案是如下。
首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs()函数,在
试验的结果表明∶
能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一串汉字,则这半个汉字后面的汉字都会是乱码。这是半个汉字的问题。
光标移动会破坏汉字的显示。表现为,光标移动过的汉字会变成乱码。这是因为光标的更新是通过xxxx_putc( )函数来完成的。
xxxx_putc( )函数与xxxx_putcs( )函数实现的功能类似,但是xxxx_putc()函数只刷新一个字符而不是一个字符串,因而xxxx_putc()的输入参数是一个整数,而不是一个字符串的地址。Xxxx_putc( )函数的声明如下∶void fbcon_cfb8_putc(struct vc_da
由于屏幕重绘等原因,调用底层驱动xxxx_putc( )和xxxx_putcs()的地方有多处。我们作了两个函数分别包装这两个调用,完成替换字库、调用xxxx_putcs( )或xxxx_putc( )、恢复字库等功能。
为了实现向上滚屏(shift + pageup)时也能看到汉字,我们需要作另外的修改。
把不同的编码方式(GB、BIG5、日文和韩文)写成不同的module,以实现动态加载,从而使得扩展新的编码方式不需要重新编译核心。
测试
本文实现的Kernel Patch文件(patch.kernel.chinese)可以从http://www.turbolinux.com.cn下载。Cd /usr/src/(该目录下应有Linux核心源程序所在的目录linux/)
〔*〕
〔*〕
〔*〕
<*> Virtual Frame Buffer support (ON
<*> 8 bpp packed pixels support
<*> 16 bpp packed pixels support
<*> VGA characters/attributes support
〔*〕
〔*〕VGA 8x8 font
〔*〕VGA 8x16 font
make dep
make bzImage
make modules
make install
make modules_install
然后用新的核心启动。
Insmod encode-gb.o
四、其它
(一)
进入FrameBuffer可以简单地在系统启动时向kernel传送vga=mode-number的参数来激活FrameBuffer设备,如:
lilo:linux vga=305
将会启动1024x768x8bpp模式。
(二)
vga=0x303
退出编辑,执行:
lilo -v
重新启动linux,可以使其进入800x600的256色模式。
grub也是一样,在grub.conf中的kernel行后面写上vga=xxx就行了,也可以用vga=ask,让系统启动的时候询问你用多大的分辨率
(三)我编译内核时,选择framebuffer模式,启动时屏幕上有一企鹅图片,不知这是如何造成的这个图片可以去掉或改动吗?
可以将drivers/video/fbcon.c: fbcon_setup()中if (logo) { }