荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Lg (终身努力便成天才), 信区: Linux
标  题: 基于Linux核心的汉字显示的尝试 (转载)(转寄)
发信站: BBS 荔园晨风站 (Sat Nov 20 11:01:49 1999), 站内信件

【 以下文字转载自 Lg 的信箱 】
【 原文由 liugang.bbs@bbs.net.tsinghua.edu.cn 所发表 】
发信人: GoldenEagle (鹫*只想飞), 信区: Linux
标  题: 基于Linux核心的汉字显示的尝试 (转载)(转寄)
发信站: BBS 水木清华站 (Fri Nov 19 20:53:36 1999)


基于Linux核心的汉字显示的尝试

利启诚讲述(chrisl@turbolinux.com.cn)
孙喜明整理(scotts@turbolinux.com.cn)
原理

在阐述“基于Linux核心的汉字显示”的技术细节之前,有必要介绍一下原有
linux的工作
机制。这里主要涉及到两部分的知识,就是Linux下终端和帧缓冲的实现.
控制台(console)
通常我们在linux下看到的控制台(console)是由几个设备完成的。分别是
/dev/ttyN(其
中tty0就是/dev/console,tty1,tty2就是不同的虚拟终端(virtual console)
).通常
使用热键alt+Fn来在这些虚拟终端之间进行切换。所有的这些tty设备都是由
linux/drivers/char/console.c和vt.c对应。其中console.c负责绘制屏幕上的字
符,
vt.c负责管理不同的虚拟终端,并且负责提供console.c需要绘制的内容。vt.c把
不同虚
拟终端下需要交给console.c绘制的内容放到不同的缓存中去。vt.c管理着这样一
个缓冲
区的数组,并且负责在其间切换,以指定哪一个缓冲区是被激活的。你所看到的虚
拟终端
就对应着被激活的缓冲区。console.c同时也负责接收终端的输入,然后把接收到
的输入
放到缓冲区。
帧缓冲(framebuffer)
Framebuffer是把显存抽象后的一种设备,可以通过这个设备的读写直接对显存进
行操作
。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体
细节。
这些都是由Framebuffer设备驱动来完成的。
Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为
fbcon.c
,在这个目录下还有与各种显卡驱动相关的源文件。
在使用帧缓冲时,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; //我们在这里的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映射表,loadunimap direct.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的信息来标志是否是汉字就足
够了,
为什么还要开辟与原缓冲区大小相同的双倍缓冲区,是不是太浪费呢?
我们先放下这个问题,稍后再作回答。
其实,如果再添加一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,以实现动态
加载,
从而使得扩展新的编码方式不需要重新编译核心。


小结

通过这次针对Linux核心的探索,我们发现,目前Linux的核心设计中,完全没有考
虑到双
字节编码字符的显示。我们在这种情况下摸索出一套解决核心下汉字显示的方法,
并编码
实现了该方案.
遵循核心的GPL版权声明,我们同时公布了实现这一技术的源代码,当然,这些改
动仍然
是GPL的.如果能对研究核心的朋友有所帮助,减少一些大家对核心的神秘感,将
是我们
最大的收获。
但是对核心和中文化来说,这仅仅是一种尝试,远不是终点.这种改动多少带有一
些hack
的色彩,不太可能融合进权威的核心里去.我们仍在积极探索圆满解决这一问题的
方法,
相信这一结果必然需要通过国内外Linux群体的共同努力才能实现.我们也非常欢
迎大家
和我们共同讨论这一问题.

测试

本文实现的Kernel Patch文件(patch.kernel.chinese)可以从http://www.
turbolinux.
com.cn下载。
cd /usr/src/(该目录下应有Linux核心源程序所在的目录linux/)
patch -p0 -b < patch.kernel.chinese
make 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



------------------------------------------------------------------------
------
--

Document converted from word 8 by MSWordView (mswordview 0.5.2)
MSWordView written by Caolan McNamara

--
※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 166.111.34.143]
--
※ 转载:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 210.39.3.93]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店