现在的位置: 首页 > 综合 > 正文

framebuffer

2017年12月20日 ⁄ 综合 ⁄ 共 16364字 ⁄ 字号 评论关闭

FrameBuffer
是出现在
2.2.xx
内核当中的一种驱动程序接口。

Linux
是工作在保护模式下,所以用户态进程是无法象
DOS
那样使用显卡
BIOS
里提供的中断调用来实现直接写屏,
Linux
抽象出
FrameBuffer
这个设备来供用户态进程实现直接写屏。
Framebuffer
机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过
Framebuffer
的读写直接对显存进行操作。用户可以将
Framebuffer
看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由
Framebuffer
设备驱动来完成的。


Framebuffer
本身不具备任何运算数据的能力
,
就只好比是一个暂时存放水的水池
.CPU
将运算后的结果放到这个水池
,
水池再将结果流到显示器
.
中间不会对数据做处理
.
应用程序也可以直接读写这个水池的内容
.
在这种机制下,尽管
Framebuffer
需要真正的显卡驱动的支持,但所有显示任务都有
CPU
完成
,
因此
CPU
负担很重
.

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);

FrameBuffer
设备还提供了若干
ioctl
命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。

通过
FrameBuffer
设备,还可以获得当前内核所支持的加速显示卡的类型(通过固定信息得到),这种类型通常是和特定显示芯片相关的。比如目前最新的内核(
2.4.9
)中,就包含有对
S3

Matrox

nVidia

3Dfx
等等流行显示芯片的加速支持。在获得了加速芯片类型之后,应用程序就可以将
PCI
设备的内存
I/O

memio
)映射到进程的地址空间。这些
memio
一般是用来控制显示卡的寄存器,通过对这些寄存器的操作,应用程序就可以控制特定显卡的加速功能。

PCI
设备可以将自己的控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问,给变成了对物理内存的访问。因此,这些寄存器又被称为
"memio"
。一旦被映射到物理内存,
Linux
的普通进程就可以通过
mmap
将这些内存
I/O
映射到进程地址空间,这样就可以直接访问这些寄存器了。

当然,因为不同的显示芯片具有不同的加速能力,对
memio
的使用和定义也各自不同,这时,就需要针对加速芯片的不同类型来编写实现不同的加速功能。比如大多数芯片都提供了对矩形填充的硬件加速支持,但不同的芯片实现方式不同,这时,就需要针对不同的芯片类型编写不同的用来完成填充矩形的函数。

FrameBuffer
只是一个提供显示内存和显示芯片寄存器从物理内存映射到进程地址空间中的设备。所以,对于应用程序而言,如果希望在
FrameBuffer
之上进行图形编程,还需要自己动手完成其他许多工作。

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportLists]-->二、
<!--[endif]-->FrameBuffer

LINUX
中实现和机制

Framebuffer
对应的源文件在
linux/drivers/video/
目录下。总的抽象设备文件为
fbcon.c
,在这个目录下还有与各种显卡驱动相关的源文件。

(

)
、分析
Framebuffer
设备驱动

   

需要特别提出的是在
INTEL
平台上,老式的
VESA 1.2
卡,如
CGA/EGA
卡,是不能支持
Framebuffer
的,因为
Framebuffer
要求显卡支持线性帧缓冲,即
CPU
可以访问显缓冲中的每一位,但是
VESA 1.2
卡只能允许
CPU
一次访问
64K
的地址空间。

FrameBuffer
设备驱动基于如下两个文件:

1) linux/include/linux/fb.h
2) linux/drivers/video/fbmem.c

下面分析这两个文件。

1

fb.h

  

几乎主要的结构都是在这个中文件定义的。这些结构包括:

1

fb_var_screeninfo

 
 

这个结构描述了显示卡的特性:

struct fb_var_screeninfo

{
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible resolution */
__u32 yoffset;

__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Gray levels instead of colors */

struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */

__u32 nonstd; /* != 0 Non standard pixel format */

__u32 activate; /* see FB_ACTIVATE_* */

__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */

__u32 accel_flags; /* acceleration flags (hints) */

/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 reserved[6]; /* Reserved for future compatibility */
};

2) fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。

struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Type of acceleration available */
__u16 reserved[3]; /* Reserved for future compatibility */
};

3) fb_cmap
描述设备无关的颜色映射信息。可以通过FBIOGETCMAP 和 FBIOPUTCMAP 对应的ioctl操作设定或获取颜色映射信息.

struct fb_cmap {
__u32 start; /* First entry */
__u32 len; /* Number of entries */
__u16 *red; /* Red values */
__u16 *green;
__u16 *blue;
__u16 *transp; /* transparency, can be NULL */
};

4) fb_info
定义当显卡的当前状态;fb_info结构仅在内核中可见,在这个结构中有一个fb_ops指针, 指向驱动设备工作所需的函数集。

struct fb_info {
char modename[40]; /* default video mode */
kdev_t node;
int flags;
int open; /* Has this been open already ? */
#define FBINFO_FLAG_MODULE 1 /* Low-level driver is a module */
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct fb_cmap cmap; /* Current cmap */
struct fb_ops *fbops;
char *screen_base; /* Virtual address */
struct display *disp; /* initial display variable */
struct vc_data *display_fg; /* Console visible on this display */
char fontname[40]; /* default font name */
devfs_handle_t devfs_handle; /* Devfs handle for new name */
devfs_handle_t devfs_lhandle; /* Devfs handle for compat. symlink */
int (*changevar)(int); /* tell console var has changed */
int (*switch_con)(int, struct fb_info*);
/* tell fb to switch consoles */
int (*updatevar)(int, struct fb_info*);
/* tell fb to update the vars */
void (*blank)(int, struct fb_info*); /* tell fb to (un)blank the screen */
/* arg = 0: unblank */
/* arg > 0: VESA level (arg-1) */
void *pseudo_palette; /* Fake palette of 16 colors and
the cursor's color for non
palette mode */
/* From here on everything is device dependent */
void *par;
};

5) struct fb_ops
用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用一支持ioctl()的这些操作的。

struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* get non settable parameters */
int (*fb_get_fix)(struct fb_fix_screeninfo *fix, int con,
struct fb_info *info);
/* get settable parameters */
int (*fb_get_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* set settable parameters */
int (*fb_set_var)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* get colormap */
int (*fb_get_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
/* set colormap */
int (*fb_set_cmap)(struct fb_cmap *cmap, int kspc, int con,
struct fb_info *info);
/* pan display (optional) */
int (*fb_pan_display)(struct fb_var_screeninfo *var, int con,
struct fb_info *info);
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg, int con, struct fb_info *info);
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct file *file, struct vm_area_struct *vma);
/* switch to/from raster image mode */
int (*fb_rasterimg)(struct fb_info *info, int start);
};

6) structure map
struct fb_info_gen | struct fb_info | fb_var_screeninfo
| | fb_fix_screeninfo
| | fb_cmap
| | modename[40]
| | fb_ops ---|--->ops on var
| | ... | fb_open
| | | fb_release
| | | fb_ioctl
| | | fb_mmap
| struct fbgen_hwswitch -|-> detect
| | encode_fix
| | encode_var
| | decode_fix
| | decode_var
| | get_var
| | set_var
| | getcolreg
| | setcolreg
| | pan_display
| | blank
| | set_disp

[编排有点困难,第一行的第一条竖线和下面的第一列竖线对齐,第一行的第二条竖线和下面的第二列竖线对齐就可以了]
这个结构 fbgen_hwswitch抽象了硬件的操作.虽然它不是必需的,但有时候很有用.

2、 fbmem.c
fbmem.c
处于Framebuffer设备驱动技术的中心位置.它为上层应用程序提供系统调用
也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己. fbmem.c
为所有支持FrameBuffer的设备驱动提供了通用的接口,避免重复工作.

1) 全局变量

struct fb_info *registered_fb[FB_MAX];
int num_registered_fb;



两变量记录了所有fb_info 结构的实例,fb_info 结构描述显卡的当前状态,所有设备对应的fb_info
结构都保存在这个数组中,当一个FrameBuffer设备驱动向系统注册自己时,其对应的fb_info
结构就会添加到这个结构中,同时num_registered_fb 为自动加1.

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
显示芯片的驱动实例

Skeleton LCD
控制器驱动为例,在LINUX中存有一个/fb/skeleton.c的skeleton的Framebuffer驱动程序,很简单,仅仅是填充了
fb_info结构,并且注册/注销自己。设备驱动是向用户程序提供系统调用接口,所以我们需要实现底层硬件操作并且定义file_operations
结构来向系统提供系统调用接口,从而实现更有效的LCD控制器驱动程序。

1)在系统内存中分配显存
在fbmem.c文件中可以看到, file_operations
结构中的open()和release()操作不需底层支持,但read()、write()和
mmap()操作需要函数fb_get_fix()的支持.因此需要重新实现函数fb_get_fix()。另外还需要在系统内存中分配显存空间,大多数
的LCD控制器都没有自己的显存空间,被分配的地址空间的起始地址与长度将会被填充到fb_fix_screeninfo 结构的smem_start
和smem_len 的两个变量中.被分配的空间必须是物理连续的。

2
)实现
fb_ops
中的函数


用户应用程序通过
ioctl()
系统调用操作硬件,
fb_ops
中的函数就用于支持这些操作。(注:
fb_ops
结构与
file_operations
结构不同,
fb_ops
是底层操作的抽象
,

file_operations
是提供给上层系统调用的接口,可以直接调用
.
 
ioctl()

系统调用在文件
fbmem.c
中实现,通过观察可以发现
ioctl()
命令与
fb_ops’s
中函数的关系
:
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
方法,用户程序就可以使用
FBIOXXXX
宏的
ioctl()
操作来操作硬件。

文件
linux/drivers/video/fbgen.c
或者
linux/drivers/video
目录下的其它设备驱动是比较好的参考资料。在所有的这些函数中
fb_set_var()
是最重要的,它用于设定显示卡的模式和其它属性,下面是函数
fb_set_var()
的执行步骤:

1)
检测是否必须设定模式

2)

设定模式

3)
设定颜色映射

4)
根据以前的设定重新设置
LCD
控制器的各寄存器。

第四步表明了底层操作到底放置在何处。在系统内存中分配显存后,显存的起始地址及长度将被设定到
LCD
控制器的各寄存器中(一般通过
fb_set_var()
函数),显存中的内容将自动被
LCD
控制器输出到屏幕上。另一方面,用户程序通过函数
mmap()
将显存映射到用户进程地址空间中,然后用户进程向映射空间发送的所有数据都将会被显示到
LCD
显示器上。

<!--[if !supportEmptyParas]--> <!--[endif]-->

<!--[if !supportLists]-->三、
<!--[endif]-->FrameBuffer
的应用

(一)、一个使用
FrameBuffer
的例子

1

FrameBuffer
主要是根据
VESA
标准的实现的,所以只能实现最简单的功能。

2
、由于涉及内核的问题,
FrameBuffer
是不允许在系统起来后修改显示模式等一系列操作。(好象很多人都想要这样干,这是不被允许的,当然如果你自己写驱动的话,是可以实现的)
.

3
、对
FrameBuffer
的操作,会直接影响到本机的所有控制台的输出,包括
XWIN
的图形界面。

好,现在可以让我们开始实现直接写屏:

1
、打开一个
FrameBuffer
设备

2
、通过
mmap
调用把显卡的物理内存空间映射到用户空间

3
、直接写内存。

/********************************

File name : fbtools.h

*/

#ifndef _FBTOOLS_H_

#define _FBTOOLS_H_

#i nclude <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

<!--[if !supportEmptyParas]--> <!--[endif]-->

/******************

File name : fbtools.c

*/

#i nclude <stdio.h>

#i nclude <stdlib.h>

#i nclude <fcntl.h>

#i nclude <unistd.h>

#i nclude <string.h>

#i nclude <sys/ioctl.h>

#i nclude <sys/mman.h>

#i nclude <asm/page.h>

#i nclude "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_write()

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
映射表,可使外部命令
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
模式下指向
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
组成,而
0xC4C4

是汉字哪。于是水平的制表符被一连串的哪字替代了。要解决这个问题就非常不容易了,因为制表符的种类比较多,而且垂直制表符与其后面字符的组合型式又多种
多样,因而很难判断出相应位置的字符是不是制表符,从理论上说,无论采取什么样的排除算法,都必然存在误判的情况,因为总存在二义性,没有充足的条件来推
断出当前字符究竟是制表符还是汉字。

我们一方面寻找更好的排除组合算法,一方面试图寻找其它的解决方案。要想从根本上解决定个问题,必须利用其它的辅助信息,仅仅从缓冲区的字符来判断是不够的。

经过一番努力,我们发现,在
UNIX
中使用扩展字符时,都要先输出字符转义序列(
Escape sequence
)来切换当前字符集。字符转义序列是以控制字符
Esc
为首的控制命令,在
UNIX
的虚拟终端中完成终端控制命令,这种命令包括,移动光标座标、卷屏、删除、切换字符集等等。也就是说在输出代表制表符的字符串之前,通常是要先输出特定的字符转义序列。在
console.c
里,有根据字符转义序列命令来记录字符状态的变量。结合该变量提供的信息,就可以非常干净地把制表符与汉字区别开来。

在如上思路的指引下,我们又产生了新的解决方案。经过改动得到了另一各版本.

在这个新版本上,
turbonetcfg
在初次绘制的时候,制表符与汉字被清晰地区分开来,结果是非常正确的。但还有新的问题存在∶
turbonetcfg
在重绘的时候(如切换虚拟终端或是移动鼠标光标的时候),制表符还是变成了汉字,因为重绘完全依赖于缓冲区,而这时用来记录字符集状态的变量并不反映当前字符集状态。问题还是没有最终解决。我们又回到了起点。∶
(
看来问题的最终解决手段必须是把字符集的状态伴随每一个字符存在缓冲区中。让我们来研究一下缓冲区的结构。每一个字符占用
16bit
的缓冲区,低
8
位是
ASCII
值,完全被利用,高
8
位包含前景颜色和背景颜色的属性,也没有多余的空间可以利用。因而只能另外开辟新的缓冲区。为了保持一致性,我们决定在原来的缓冲区后面添加相同大小的缓冲区,用来存放是否是汉字的信息。

也许有读者会问,我们只需要为每个字符添加一
bit
< TD>

 

 原文地址

http://blog.21ic.com/user1/2216/archives/2006/17074.html

【上篇】
【下篇】

抱歉!评论已关闭.