📄 linux中文化之linux,内核汉化大揭秘--tangxin991的博客.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0038)http://linuxmpc.bokee.com/2491168.html -->
<HTML><HEAD><TITLE>Linux中文化之Linux,内核汉化大揭秘--tangxin991的博客</TITLE>
<META http-equiv=Content-Type content="text/html; charset=GBK">
<META http-equiv=Pragma content=no-cache>
<META http-equiv=Cache-Control content=no-cache>
<META http-equiv=Expires content=0>
<META
content="应该用什么工具开发您的移动设备Linux中文化之Linux,内核汉化大揭秘立體聲小功率放大器 博客 博客中国 博客动力 blog blogdriver blogger 中国"
name=description>
<META
content="tangxin991的博客 应该用什么工具开发您的移动设备Linux中文化之Linux,内核汉化大揭秘立體聲小功率放大器 博客 博客中国 博客动力 blog blogdriver blogger 中国"
name=keywords><LINK href="Linux中文化之Linux,内核汉化大揭秘--tangxin991的博客.files/diary.css"
type=text/css rel=stylesheet>
<SCRIPT language=JavaScript
src="Linux中文化之Linux,内核汉化大揭秘--tangxin991的博客.files/UBB.js"></SCRIPT>
<SCRIPT src="Linux中文化之Linux,内核汉化大揭秘--tangxin991的博客.files/blog.js"
type=text/javascript></SCRIPT>
<META content="MSHTML 6.00.2900.3132" name=GENERATOR></HEAD>
<BODY>
<DIV id=container>
<DIV id=header>
<H1 class=title><A
href="http://linuxmpc.bokee.com/index.html">tangxin991的博客</A></H1></DIV>
<DIV id=category><A title=上一篇
href="http://linuxmpc.bokee.com/2491137.html">应该用什么工具开发您的移动设备</A>- -| <A
href="http://linuxmpc.bokee.com/index.html">回首页</A> | <A
href="http://linuxmpc.bokee.com/catalog_2005.html">2005年索引</A> | - -<A title=下一篇
href="http://linuxmpc.bokee.com/2529265.html">立體聲小功率放大器</A></DIV>
<DIV class=entity>
<H2 class=diaryTitle>Linux中文化之Linux,内核汉化大揭秘-
-</H2>
<P>
<P>在阐述“基于Linux核心的汉字显示”的技术细节之前,有必要介绍一下原有Linux的工作机制。这里主要涉及到两部分的知识,这是Linux下终端和帧缓冲的实现。
<BR> 控制台(console)
<BR> 通常我们在Linux下看到的控制台(console)是由几个设备构成的。分别是/dev/ttyN(其中tty0就是/dev/console,tty1、tty2就是不同的虚拟终端(virtual
console))。通常使用热键Alt+Fn来在这些虚拟终端之间进行切换。这些tty设备对应于linux/drivers/char/console.c和lvt.c。其中console.c负责绘制屏幕上的字符,vt.c负责管理不同的虚拟终端,并且负责提供console.c需要绘制的内容。Vt.c把不同虚拟终端下的需要交给console.c绘制的内容,放到不同的缓存中去。Vt.c管理者这样一个缓冲区的数组,并且负责在这些缓存之间切换,并指定哪一个缓冲区是被激活的。你所看到的虚拟终端就对应着被激活的缓冲区。Console.c同时也负责接收终端的输入,然后把接收到的输入的信息放到缓冲区。
<BR> 帧缓冲(framebuffer)
<BR> Framebuffer是把显存抽象后的一个种设备,可以通过这个设备的读写直接对显存进行操作。这种操作是抽象的、统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由Framebuffer设备驱动程序来完成的。
<BR> Framebuffer对应的源文件在linux/drivers/video/目录下。总的抽象设备文作为fbcon.c,在这个目录下还有与各种显卡驱动程序相关的源文件。
<BR> 在使用帧缓冲时,Linux是将显卡置于图形模式下的。<BR> 我们以一个简单的例子来说明字符显示的过程。我们假设是在虚拟终端1(/dev/tty1)下迁行如下的简单程序:
<BR> main () <BR> { <BR> puts(”hello,world. <BR> ”); <BR> }
<BR> pputs函数向缺省输出文件(/dev/tty)发出“写”的系统调用write(2)。系统调用到Linux核心对应的核心函数->——console.c中的con_write(
), con_write(
)最终会调用do_con_write(),在do_con_write()中负责把”hello,world.<BR> ”这个字符串放到tty1对应的缓冲区中去。
<BR> Do_con_write()还负责处理控制字符和光标的位置。让我们来看一下do_con-write()这个函数的声明: <BR> Static
int do_con_write(struct <BR> Tty_struct * tty, int <BR> from_user, const
unsigned <BR> char *buf, int count )
<BR> 其中tty是指向tty_struct结构的指针,这个结构里存放着关于这个tty的所有信息(请参照linux/include/linux/tty.h)。tty_srtuct结构中定义了?p>
用(或高层)tty的属性(例如宽度和高度等)。
<BR> 在do_con_write()函数中用到了tty_struct结构中的driver_data变量。Driver_data是一个vt_vt_stuct指针。在vt_struct结构中包含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的数组vc_cons,这个数组就是各虚拟终端的私有数据。
<BR> Static int do_write(struct <BR> Tty_struct * tty, int
<BR> From_user,const unsigned char <BR> *buf, int conut) <BR> { <BR> struct
vt_struct *vt = (struct <BR> vt_struct *)tty_>driver_data;
<BR> //我们用到了driver_data变量 <BR> ………… <BR> currcons = vt->_num;
<BR> //在这里的vc_nums就是1 <BR> ………… <BR> }
<BR> 要访问虚拟终端的私有数据,需使用vc_cons[currcons].d指针。这个指针指向的结构含有当前虚拟终端上光标的位置,缓冲区的起始地址、缓冲区大小等信息。
<BR> “hello,world.
<BR> ”中的每一个字符都要经过conv_uni_to_pc()这个函数转换成8位的显示字符。这样做的主要目的是使不同语言的国家能把16位的Unicode码映射到8位的显示字符集里,目前主要还是针对欧洲国家的语言,映射结果为8位,不包含双字节(double
byte)的范围。 <BR> 这种从Unicode到显示字符的映射表上,会把中文的字符映射到其他的字符上,这是我们不希望看到也是不需要的,所以我们有两种选择:
<BR> 1) 不进行conv_uni_to_pc()的转换 <BR> 2) 加载符合双字节处理的映射关系,即对蜚
控制字符进行一对一的不变映射,我们自己定制的符合这种映射关系的Unicode码表是direct.uni。
<BR> 要想看/装载当前系统的Unicode映射表,可使用外部命令loadunimap。
<BR> 经过conv_uni_to_pc()转换之后,”hello, world.
<BR> ”中的字符被一个一个地填写到tty的缓冲区中,然后do_con_write()调用底层的驱动程序,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA显存中去)
<BR> sw->con putcs(vc_cons[currcons].d, <BR> (u16 *)draw_from, (u16
*)draw_to_ <BR> (u16 *)draw_rwom, Y, draw_x);
<BR> 之所以要调用底层驱动程序,是因为存在不同的显示设备,其对应VGA显存的存取方式也不一样。
<BR> 上面的Sw->con_putcs()就会调用fbcon.c中的fbcon_putcs()函数(con_putcs是一个函数的指针,在
Framebuffer模式)下指向fbcon_putcs()函数,也就是说,在do_con_write()函数中是直接调用了fbcon_putcs()函数来进行字符的绘制,比如说在256色模式下,真正负责输出的函数是:void
fbcon_cfb8_putcs(struct vc_d <BR> ta *conp,struct display *p, const unsignde
short *s, int count, int YY, int xx ) <BR> 显示中文 <BR> 比如说我们试输出一句中文:putcs(你好
<BR> ”)(“你好”的内码为0xc4.0xe3.0ba.0xc3)。这时候会怎么样呢?有一点可以肯定,“你好”肯定不会出现在屏幕上,原因是:
<BR> 1、核心中没有汉字字库,中文显示就是无米之炊了。 <BR> 2、在负责字符显示的void
fbcon_cfb8_putcs()函数中,原有操作如下:
<BR> 对于每个要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(低位字节是ASCII码,高8位是字符的属性)。由于汉字是双字节编码方式,所以这种操作是不可能显示出汉字的,只能显示出xxxx_putcs()输出的是一个一个的VGA字符。
<BR> 因此,要解决的问题:确保在调用do_con_write()时进行uni_pc转换不会改变原有编码,一个很直接的实现方式就是加载一个我们自己定制的Unicode映射表,loadunimap
dirdct.uni,或者进接把direct.uni设置为核心的缺省映射表。 <BR> 针对以上问题,我们要做的第一个尝试方案如下:
<BR> 首先需要在核心中加载汉字字库,然后修改fbcon_cfb8_putcs()函数,在fbcon_cfb8_putcs()中一次读两个WORD,检查这两个WORD的低位字节是否能拼成一个汉字,如果发现能拼成一个汉字,就算出这个汉字在汉字字库的的偏移,然后把它当成个16×16的VGA字符来显示。
<BR> 试验的结果表明:
<BR> 1、能够输出汉字,但仍有许多不理想的地方,比如说,输出以半个汉字开始的一串汉字,则这半个汉字后面的汉字都会是乱码,这是“半个汉字”的问题<BR> 2、光标移动会破坏汉字的显示,表现为,光标移动过的汉字会变成乱码,这是因为光标的更新是通过xxxx_putc()函数来完成的。
<BR> xxxx_putc()函数与xxxx_putcs()函数实现的功能够类似,但是xxxx_()函数只刷新一个字符而不是一个字符串,因而xxxx_putc()的输入参数是一个整数,而不是一个字符串的地址,xxxx_putc()函数的声明如下:
<BR> void fbcon_cfb8_putc(struct vc_data *conp, struct display *p, int c, int
YY, int xx)
<BR> 下一个尝试方案就是同时修改xxxx_putc()函数和xxxx_putc()函数为了解决半个汉字的问题,每一次输出之前,都从屏幕当前行的起始位置开始打措,以确定要输出的字符是否落在半个汉字的位置上,如果是在半个汉字的位置上,如果是在半个汉字的位置,则进行相应的调整,即从向前移动一个字节的位置开始输出。
<BR> 这个方案有一个困难,即xxxx_putc()函数不用缓冲区的地址,而是用一个整数作为参数,所以xxx<BR> _putc()无法直接利用相邻的字符来判别该字符是否是汉字。
<BR> 解决方案是,利用xxxx_putc()的光标们置参数(yy,xx),可以逆推出该字符在缓冲区中的位置,但仍一些小麻烦,在Linux的虚拟终端下,用户可能会上卷该屏幕(Shift+Pageup),导致光标的y座标和相应字符在缓冲区的行数不一致,相应的解决方案是,在逆推的过程中,考虑在屏的参量。<BR> 这样一来,我们就又进了一步,得到了一个相对更好的版本。但仍有问题没有解决,敲入turbonetcfg,会发现菜单的边框字符也被当成汉字显示,这是因为,这种边框字符是扩展字符,也使用了字符的低8位,因而被当成汉字显示,这是因为,这种边框字符是扩展字符,也使用了字符的低8位,因而被当作汉字来赤示。例如,单线“—”的制表符内码为0xC4,当连成一条长线时就是由一连串0xC4组成的,而0Xc4c4正是汉字“哪”,于是水平的制表符被一连串的“哪”字替代了,因为制表符的种类比较多,而且垂直制表符与其后面字符的组合形式又多种多样,因而很难判断出相应位置的字符是不是制表符,从理论上说,无论采取什么样的排除算法,都必然存在误判的情况,因为总存在二义性,没有充足的条件来推断出当前字符究竟是制表符还是汉字。
<BR> 我们一方面寻找更好的排除组合算法,一方面试图寻找其他的解决方案,要想从根本上解决这个问题,必须利用其他的辅助信息,仅仅利用缓冲区的字符来判断是不够的。
<BR> 经过一番努力,我们发现,在UNIX中使用扩展字符时,都要先输出字符转义序列(Escape
sepuence)来切换当前字符集。字符转义序列是以控制字符Ecs为首的控制命令,在UNIX的虚拟终端中完成终端控制命令,这种命令包括移动光标座标、卷屏、删除、切换字符集等等。也就是说,在输出代表制表的字符串之前,通常是要先输出特定的字符转义序列,在console.c里,有根据字符转义序列命令来记录字符状态的变量,结合该变量提供的信息,就可以非常准确地把制表符与汉字区别开来。
<BR> 在如上思路的指引下,我们又产生了新的解决方案,经过改动得到了另一版本。
<BR> 在这个新的版本上,turbonetcfg在初次绘制的时候,制表符与汉字被清晰地区分开,但还有问题:turbonetcfg在重绘的时候(如切换虚拟终端或是移动鼠标光标的),制表符还是变成了汉字,因为重绘完全领带于缓冲区,而这时用来记录字符集状态的变量并不反映当前字符集状态。问题还是没有最终解决,我们又回到了起点。
<BR> 看来问题的最终解决手段必须是把字符集的状态伴随着每一个字符在 <BR> 撼迩
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -