📄 00000021.htm
字号:
<HTML><HEAD> <TITLE>BBS水木清华站∶精华区</TITLE></HEAD><BODY><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER>发信人: GoldenEagle (鹫*只想飞), 信区: Linux <BR>标 题: 基于Linux核心的汉字显示的尝试 (转载) <BR>发信站: BBS 水木清华站 (Fri Nov 19 20:53:36 1999) <BR> <BR> <BR>基于Linux核心的汉字显示的尝试 <BR> <BR>利启诚讲述(<A HREF="mailto:chrisl@turbolinux.com.cn)">chrisl@turbolinux.com.cn)</A> <BR>孙喜明整理(<A HREF="mailto:scotts@turbolinux.com.cn)">scotts@turbolinux.com.cn)</A> <BR>原理 <BR> <BR>在阐述“基于Linux核心的汉字显示”的技术细节之前,有必要介绍一下原有 <BR>linux的工作 <BR>机制。这里主要涉及到两部分的知识,就是Linux下终端和帧缓冲的实现. <BR>控制台(console) <BR>通常我们在linux下看到的控制台(console)是由几个设备完成的。分别是 <BR>/dev/ttyN(其 <BR>中tty0就是/dev/console,tty1,tty2就是不同的虚拟终端(virtual console) <BR>).通常 <BR>使用热键alt+Fn来在这些虚拟终端之间进行切换。所有的这些tty设备都是由 <BR>linux/drivers/char/console.c和vt.c对应。其中console.c负责绘制屏幕上的字 <BR>符, <BR>vt.c负责管理不同的虚拟终端,并且负责提供console.c需要绘制的内容。vt.c把 <BR>不同虚 <BR>拟终端下需要交给console.c绘制的内容放到不同的缓存中去。vt.c管理着这样一 <BR>个缓冲 <BR>区的数组,并且负责在其间切换,以指定哪一个缓冲区是被激活的。你所看到的虚 <BR>拟终端 <BR>就对应着被激活的缓冲区。console.c同时也负责接收终端的输入,然后把接收到 <BR>的输入 <BR>放到缓冲区。 <BR>帧缓冲(framebuffer) <BR>Framebuffer是把显存抽象后的一种设备,可以通过这个设备的读写直接对显存进 <BR>行操作 <BR>。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体 <BR>细节。 <BR>这些都是由Framebuffer设备驱动来完成的。 <BR>Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文件为 <BR>fbcon.c <BR>,在这个目录下还有与各种显卡驱动相关的源文件。 <BR>在使用帧缓冲时,Linux是将显卡置于图形模式下的. <BR> <BR> <BR>试验 <BR> <BR>我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1( <BR>/dev/tty1)下 <BR>运行一个如下的简单程序。 <BR>main ( ) <BR>{ <BR>puts("hello, world.\n"); <BR>} <BR>puts函数向缺省输出文件(/dev/tty1)发出“写”的系统调用write(2)。系统调用 <BR>到linux <BR>核心里面对应的核心函数是console.c中的con_write( ), con_write()最终会调 <BR>用 <BR>do_con_write( )。在do_con_write( )中负责把"hello, world.\n"这个字符串放 <BR>到tty1 <BR>对应的缓冲区中去。 <BR>do_con_write( )还负责处理控制字符和光标的位置。让我们来看一下 <BR>do_con_write()这 <BR>个函数的声明。 <BR>static int do_con_write(struct tty_struct * tty, int from_user, const <BR>unsigned char *buf, int count) <BR>其中tty是指向tty_struct结构的指针,这个结构里面存放着关于这个tty的所有信 <BR>息(请 <BR>参照linux/include/linux/tty.h)。tty_struct结构中定义了通用(或高层) <BR>tty的属性 <BR>(例如宽度和高度等)。 <BR>在do_con_write( )函数中用到了tty_struct结构中的driver_data变量。 <BR>driver_data是一个vt_struct指针。在vt_struct结构中包含这个tty的序列号(我 <BR>们正使 <BR>用tty1,所以这个序号为1)。vt_struct结构中有一个vc结构的数组vc_cons,这 <BR>个数组 <BR>就是各虚拟终端的私有数据。 <BR>static int do_con_write(struct tty_struct * tty, int from_user,const <BR>unsigned <BR>char *buf, int count) <BR>{ <BR>struct vt_struct *vt = (struct vt_struct *)tty->driver_data;//我们用到了 <BR> <BR>driver_data变量 <BR>. . . . . . <BR>currcons = vt->vc_num; //我们在这里的vc_nums就是1 <BR>. . . . . . <BR>} <BR>要访问虚拟终端的私有数据,需使用vc_cons[currcons].d指针。这个指针指向的 <BR>结构含 <BR>有当前虚拟终端上光标的位置、缓冲区的起始地址、缓冲区大小等等。 <BR>"hello, world.\n" 中的每一个字符都要经过conv_uni_to_pc( )这个函数转换成 <BR>8位的 <BR>显示字符。这要做的主要目的是使不同语言的国家能把16位的UniCode码映射到 <BR>8位的显 <BR>示字符集上,目前还是主要针对欧洲国家的语言,映射结果为8位,不包含对双字 <BR>节( <BR>double byte)的范围。 <BR>这种UNICODE到显示字符的映射关系可以由用户自行定义。在缺省的映射表上,会 <BR>把中文 <BR>的字符映射到其他的字符上,这是我们不希望看到也是不需要的。所以我们有两个 <BR>选择∶ <BR> <BR>1 不进行conv_uni_to_pc( )的转换。 <BR>2 加载符合双字节处理的映射关系,即对非控制字符进行1对1的不变映射。我们自 <BR>己定制 <BR>的符合这种映射关系的UNICODE码表是direct.uni。 <BR>要想 查看 / 装载 当前系统的unicode映射表,可使外部命令loadunimap。 <BR>经过conv_uni_to_pc( )转换之后,"hello, world.\n"中的字符被一个一个地填写 <BR>到tty1 <BR>的缓冲区中。然后do_con_write( )调用下层的驱动,把缓冲区中的内容输出到显 <BR>示器上 <BR>(也就相当于把缓冲区的内容拷贝到VGA显存中去)。 <BR> <BR>sw->con_putcs(vc_cons[currcons].d, (u16 *)draw_from, (u16 *)draw_to-(u16 <BR> <BR>*)draw_from, y, draw_x); <BR> <BR>之所以要调用底层驱动,是因为存在不同的显示设备,其对应VGA显存的存取方式 <BR>也不一 <BR>样。 <BR>上面的Sw->con_putcs( )就会调用到fbcon.c中的fbcon_putcs( )函数( <BR>con_putcs是一个 <BR>函数的指针,在Framebuffer模式下指向fbcon_putcs( )函数)。也就是说在 <BR>do_con_write( )函数中是直接调用了fbcon_putcs( )函数来进行字符的绘制。比 <BR>如说在 <BR>256色模式下,真正负责输出的函数是 <BR> <BR>void fbcon_cfb8_putcs(struct vc_data *conp, struct display *p,const <BR>unsigned <BR>short *s, int count, int yy, int xx) <BR>显示中文 <BR>比如说我们试图输出一句中文∶putcs(“你好\n”);(“你好”的内码为 <BR>0xc4,0xe3,0xba,0xc3)。这时候会怎么样呢,有一点可以肯定,"你好"肯定不 <BR>会出现 <BR>在屏幕上,原因有∶ <BR> 核心中没有汉字字库,中文显示就是无米之炊了. <BR>1 在负责字符显示的void fbcon_cfb8_putcs( )函数中,原有操作如下∶ <BR>对于每个要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(低位字节是 <BR>ASCII <BR>码,高8位是字符的属性),由于汉字是双字节编码方式,所以这种操作是不可能 <BR>显示出 <BR>汉字的,只能显示出xxxx_putcs()是一个一个VGA字符. <BR>要解决的问题∶ <BR>确保在do_con_write( )时uni□pc转换不会改变原有编码。一个很直接的实现方式 <BR>就是加 <BR>载一个我们自己定制的UNICODE映射表,loadunimap direct.uni,或者直接把 <BR>direct.uni <BR>置为核心的缺省映射表。 <BR> <BR>针对如上问题,我们要做的第一个尝试方案是如下。 <BR>首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs( )函数,在 <BR>fbcon_cfb8_putcs( )中一次读两个WORD,检查这两个WORD的低位字节是否能拼成 <BR>一个汉 <BR>字,如果发现能拼成一个汉字,就算出这个汉字在汉字字库中的偏移,然后把它当 <BR>成一个 <BR>16 x 16的VGA字符来显示。 <BR>试验的结果表明∶ <BR>1 能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一 <BR>串汉字 <BR>,则这半个汉字后面的汉字都会是乱码。这是“半个汉字”的问题。 <BR>2 光标移动会破坏汉字的显示。表现为,光标移动过的汉字会变成乱码。这是因 <BR>为光标 <BR>的更新是通过xxxx_putc( )函数来完成的。 <BR>xxxx_putc( )函数与xxxx_putcs( )函数实现的功能类似,但是xxxx_putc( )函数 <BR>只刷新 <BR>一个字符而不是一个字符串,因而xxxx_putc( )的输入参数是一个整数,而不是一 <BR>个字符 <BR>串的地址。 xxxx_putc( )函数的声明如下∶ <BR>void fbcon_cfb8_putc(struct vc_data *conp, struct display *p, int c, int <BR> yy, <BR>int xx) <BR>下一个尝试方案就是同时修改xxxx_putcs( )函数和xxxx_putc( )函数。为了解决 <BR>半个汉 <BR>字的问题,每一次输出之前,都从屏幕当前行的起始位置开始扫描,以确定要输出 <BR>的字符 <BR>是否落在半个汉字的位置上。如果是半个汉字的位置,则进行相应的调整,即从向 <BR>前移动 <BR>一个字节的位置开始输出。 <BR>这个方案有一个困难,即xxxx_putc( )函数不用缓冲区的地址,而是用一个整数作 <BR>为参数 <BR>。所以xxxx_putc( ) 无法直接利用相邻的字符来判别该定符是否是汉字。 <BR>解决方案是,利用xxxx_putc( )的光标位置参数(yy, xx),可以逆推出该字符在 <BR>缓冲区 <BR>中的位置。但仍有一些小麻烦,在Linux的虚拟终端下,用户可能会上卷该屏幕( <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -