在探讨“基于Linux核心的汉字显示”的技术细节之前,有必要介绍一下原有Linux的工作机制。这儿主要涉及到两部份的知识,这是Linux下终端和帧缓冲的实现。

%A

%A控制台(console)

%A

%A一般我们在Linux下看见的控制台(console)是由几个设备构成的。分别是/dev/ttyN(其中tty0就是/dev/console,tty1、tty2就是不同的虚拟终端(virtualconsole))。一般使用键位Alt+Fn来在这种虚拟终端之间进行切换。这种tty设备对应于linux/drivers/char/console.c和lvt.c。其中console.c负责勾画屏幕上的字符,vt.c负责管理不同的虚拟终端,但是负责提供console.c须要勾画的内容。Vt.c把不同虚拟终端下的须要交给console.c勾画的内容,放在不同的缓存中去。Vt.c管理者这样一个缓冲区的字段,而且负责在这种缓存之间切换,并指定哪一个缓冲区是被激活的。你所看见的虚拟终端就对应着被激活的缓冲区。Console.c同时也负责接收终端的输入,之后把接收到的输入的信息放在缓冲区。

%A

%A帧缓冲(framebuffer)

%A

%AFramebuffer是把内存具象后的一个种设备,可以通过这个设备的读写直接对内存进行操作。这些操作是具象的、统一的。用户毋须关心化学内存的位置、换页机制等等具体细节,这种都是由Framebuffer设备驱动程序来完成的。

%A

%AFramebuffer对应的源文件在linux/drivers/video/目录下。总的具象设备文作为fbcon.c,在这个目录下还有与各类主板驱动程序相关的源文件。

%A

%A在使用帧缓冲时,Linux是将主板放在图形模式下的。

%A

%A我们以一个简单的反例来说明字符显示的过程。我们假定是在虚拟终端1(/dev/tty1)下迁行如下的简单程序:

%A

%Amain()

%A

%A{

%A

%Auts(”hello,world.\n”);

%A

%A}

%A

%Auts函数向缺省输出文件(/dev/tty)发出“写”的系统调用write(2)。系统调用到Linux核心对应的核心函数->――console.c中的con_write(),con_write()最终会调用do_con_write(),在do_con_write()中负责把”hello,world.\n”这个字符串放在tty1对应的缓冲区中去。

%A

%ADo_con_write()还负责处理控制字符和光标的位置。让我们来看一下do_con-write()这个函数的申明:

%A

%AStaticintdo_con_write(struct

内核参数修改_linux修改内核参数生效_linux修改内核参数

%A

%ATty_struct*tty,int

%A

%Afrom_user,constunsigned

%A

%Achar*buf,intcount)

%A

%A其中tty是指向tty_struct结构的表针,这个结构里储存着关于这个tty的所有信息(请参照linux/include/linux/tty.h)。tty_srtuct结构中定义了通用(或高层)tty的属性(比如长度和高度等)。

%A

%A在do_con_write()函数中用到了tty_struct结构中的driver_data变量。Driver_data是一个vt_vt_stuct表针。在vt_struct结构中包含这个tty的序列号(我们正使用tty1,所以这个序号为1)。Vt_struct结构中有一个vc结构的链表vc_cons,这个字段就是各虚拟终端的私有数据。

%A

%AStaticintdo_write(struct

%A

%ATty_struct*tty,int

%A

%AFrom_user,constunsignedchar

%A

%A*buf,intconut)

%A

%A{

%A

%Atructvt_struct*vt=(struct

%A

%Avt_struct*)tty_>driver_data;

%A

%A//我们用到了driver_data变量

%A

%A…………

%A

%Acurrcons=vt->_num;

%A

%A//在这儿的vc_nums就是1

%A

%A…………

%A

%A}

%A

%A要访问虚拟终端的私有数据,需使用vc_cons[currcons].d表针。这个表针指向的结构富含当前虚拟终端上光标的位置,缓冲区的起始地址、缓冲区大小等信息。

%A

%A“hello,world.\n”中的每一个字符都要经过conv_uni_to_pc()这个函数转换成8位的显示字符。这样做的主要目的是使不同语言的国家能把16位的Unicode码映射到8位的显示字符集里,目前主要还是针对亚洲国家的语言,映射结果为8位,不包含双字节(doublebyte)的范围。

%A

%A这些从Unicode到显示字符的映射表上,会把英文的字符映射到其他的字符上,这是我们不希望见到也是不须要的,所以我们有两种选择:

%A

%A1)不进行conv_uni_to_pc()的转换

%A

%A2)加载符合双字节处理的映射关系,即对蜚控制字符进行一对一的不变映射,我们自己订制的符合这些映射关系的Unicode码表是direct.uni。

%A

%A要想看/装载当前系统的Unicode映射表,可使用外部命令loadunimap。

%A

%A经过conv_uni_to_pc()转换以后,”hello,world.\n”中的字符被一个一个地填写到tty的缓冲区中嵌入式linux,之后do_con_write()调用底层的驱动程序,把缓冲区中的内容输出到显示器上(也就相当于把缓冲区的内容拷贝到VGA内存中去)

%A

%Aw->conputcs(vc_cons[currcons].d,

%A

%A(u16*)draw_from,(u16*)draw_to_

linux修改内核参数_内核参数修改_linux修改内核参数生效

%A

%A(u16*)draw_rwom,Y,draw_x);

%A

%A之所以要调用底层驱动程序,是由于存在不同的显示设备,其对应VGA内存的存取方法也不一样。

%A

%A前面的Sw->con_putcs()都会调用fbcon.c中的fbcon_putcs()函数(con_putcs是一个函数的表针,在Framebuffer模式)下指向fbcon_putcs()函数,也就是说,在do_con_write()函数中是直接调用了fbcon_putcs()函数来进行字符的勾画,譬如说在256色模式下,真正负责输出的函数是:voidfbcon_cfb8_putcs(structvc_data*conp,structdisplay*p,constunsigndeshort*s,intcount,intYY,intxx)

%A

%A显示英文

%A

%A例如说我们试输出一句英文:putcs(您好\n”)(“你好”的内码为0xc4.0xe3.0ba.0xc3)。这时侯会怎样样呢?有一点可以肯定,“你好”肯定不会出现在屏幕上,缘由是:

%A

%A1、核心中没有汉字字库,英文显示就是无米之炊了。

%A

%A2、在负责字符显示的voidfbcon_cfb8_putcs()函数中,原有操作如下:

%A

%A对于每位要显示的字符,依次从虚拟终端缓冲区中以WORD为单位读取(高位字节是ASCII码,高8位是字符的属性)。因为汉字是双字节编码方法,所以这些操作是不可能显示出汉字的,只能显示出xxxx_putcs()输出的是一个一个的VGA字符。

%A

%A为此,要解决的问题:确保在调用do_con_write()时进行uni_pc转换不会改变原有编码,一个很直接的实现方法就是加载一个我们自己订制的Unicode映射表,loadunimapdirdct.uni,或则进接把direct.uni设置为核心的缺省映射表。

%A

%A针对以上问题,我们要做的第一个尝试方案如下:

%A

%A首先须要在核心中加载汉字字库,之后更改fbcon_cfb8_putcs()函数,在fbcon_cfb8_putcs()中一次读两个WORD,检测这两个WORD的高位字节是否能拼成一个汉字,假如发觉能拼成一个汉字,即使出这个汉字在汉字字库的的偏斜,之后把它当作个16×16的VGA字符来显示。

%A

%A试验的结果表明:

%A

%A1、能够输出汉字,但仍有许多不理想的地方,例如说,输出以半个汉字开始的一串汉字,则这半个汉字旁边的汉字就会是乱码,这是“半个汉字”的问题。

%A

%A2、光标联通会破坏汉字的显示,表现为,光标联通过的汉字会弄成乱码,这是由于光标的更新是通过xxxx_putc()函数来完成的。

%A

%Axxxx_putc()函数与xxxx_putcs()函数实现的功才能类似,而且xxxx_()函数只刷新一个字符而不是一个字符串,从而xxxx_putc()的输入参数是一个整数,而不是一个字符串的地址,xxxx_putc()函数的申明如下:

%A

%Avoidfbcon_cfb8_putc(structvc_data*conp,structdisplay*p,intc,intYY,intxx)

%A

%A下一个尝试方案就是同时更改xxxx_putc()函数和xxxx_putc()函数为了解决半个汉字的问题,每一次输出之前,都从屏幕当前行的起始位置开始打措,以确定要输出的字符是否落在半个汉字的位置上,假如是在半个汉字的位置上,假如是在半个汉字的位置,则进行相应的调整,即从往前联通一个字节的位置开始输出。

%A

%A这个方案有一个困难,即xxxx_putc()函数不用缓冲区的地址,而是用一个整数作为参数,所以xxxx_putc()难以直接借助相邻的字符来判断该字符是否是汉字。

%A

%A解决方案是,借助xxxx_putc()的光标们置参数(yy,xx),可以逆推出该字符在缓冲区中的位置,但仍一些小麻烦,在Linux的虚拟终端下,用户可能会下卷该屏幕(Shift+Pageup),造成光标的y坐标和相应字符在缓冲区的行数不一致,相应的解决方案是,在逆推的过程中,考虑在屏的热阻。

%A

%A这样一来,我们就又进了一步,得到了一个相对更好的版本。但仍有问题没有解决,敲入turbonetcfg,会发觉菜单的边框字符也被当作汉字显示,这是由于,这些边框字符是扩充字符,也使用了字符的低8位,因此被当做汉字显示,这是由于,这些边框字符是扩充字符,也使用了字符的低8位,因此被当成汉字来赤示。诸如,单线“―”的制表符内码为0xC4,当连成一条长线时就是由一连串0xC4组成的,而0Xc4c4正是汉字“哪”,于是水平的制表符被一连串的“哪”字代替了,由于制表符的种类比较多,但是垂直制表符与其前面字符的组合方式又多种多样,因此很难判别出相应位置的字符是不是制表符,从理论上说,无论采取哪些样的排除算法,都必然存在错判的情况,由于总存在二义性,没有充足的条件来推算出当前字符到底是制表符还是汉字。

%A

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

%A

%A经过一番努力,我们发觉,在UNIX中使用扩充字符时,都要先输出字符通配符序列(Escapesepuence)来切换当前字符集。字符通配符序列是以控制字符Ecs为首的控制命令,在UNIX的虚拟终端中完成终端控制命令,这些命令包括联通光标坐标、卷屏、删除、切换字符集等等。也就是说,在输出代表制表的字符串之前,一般是要先输出特定的字符通配符序列,在console.c里,有按照字符通配符序列命令来记录字符状态的变量,结合该变量提供的信息,就可以十分确切地把制表符与汉字区别开来。

%A

%A在如上思路的指引下,我们又形成了新的解决方案,经过改动得到了另一版本。

%A

%A在这个新的版本上,turbonetcfg在初次勾画的时侯,制表符与汉字被清晰地分辨开,但还有问题:turbonetcfg在重画的时侯(如切换虚拟终端或是联通键盘光标的),制表符还是弄成了汉字linux修改内核参数,由于重画完全衬衫于缓冲区,而这时拿来记录字符集状态的变量并不反映当前字符集状态。问题还是没有最终解决,我们又回到了起点。

%A

%A看来问题的最终解决手段必须是把字符集的状态伴随着每一个字符在缓冲区中,让我们来研究一下缓冲区的结构。

%A

%A每一个字符占用16位的缓冲区,低6、8位是ASCII值,完全被借助,高8位饮食前量颜色和背景颜色的属性,也没有多余的空间可以借助,因此只能另外开辟新的缓冲区。为了保持一致性,我们决定在原先的缓冲区前面添加相同大小的缓冲区,拿来储存是否汉字的信息。

%A

%A似乎有读者会问,只须要为每位字符添加一位信息来标志是否是汉字就足够了,为何还要开辟与原缓冲区大小相同的双倍缓冲区,这是不是太浪费呢?

%A

%A我们先放下这个问题,稍后再作回答。

%A

%A虽然,假如再添加一位来标志当前字符是汉字的左半边还是历半边的话,还会省去扫描屏幕上当前整行字符串的工作,这样一来,编程会更简单,并且有读者会问,虽然是这样,使用8位总够用了吧?为何还要使用16位呢?

%A

%A我们的做法是:用低8位来储存汉字另外一半的内码,用高8位中的2位来储存前面所讲的辅助信息,高8位的剩余6位可以拿来储存汉字或其他编码方法(如BIG5或英文、韩文)的信息,进而使我们可以实现同屏显示多种双字节语言的字符而不会互相干扰。另外,在编程时,双倍缓冲也比较容易估算。这样我们就回答了如上的两个问题。

%A

%A迄今为止,我们有了一套彻底解决汉字和制表符互相干扰,半个汉字的刷新、重绘等问题的方案。剩下的就是具体编程来实现的问题了。

%A

%A并且,因为Framebuffer的驱动程序好多,更改每一个驱动程序的xxxx_putc()函数和xxxx_putcs()函数会是一项不小的工作,但是,改动驱动程序后,每种驱动程序的测试也是很麻烦的linux修改内核参数,尤其是对于有硬件加速的主板,更改和测试会更不容易。

%A

%A这么,是否存在一种不须要更改主板驱动程序的方式呢?经过一番努力,我们发觉,可以调用xxxx_putcs()或xxxx_putc()函数输出汉字之前,更改VGA字库表针使其指向所需显示的汉字在汉字字库中的位置,即把一个汉字当作两个VGAASCII字符输出。也就是说,在内核中存在两个字库,一个是原有的VGA字符字库,另一个是汉字字库,当我们须要输出汉字的时侯,就把VGA字库的表针指向汉字字库的相应位置,汉字输出完以后,再把该表针指向VGA字库的原有位置。

%A

%A这样一来,我们就只须要更改fbcon..c和console.c,其中console.c负责维护双倍缓冲区,把每一个字符的信息存入附加的缓冲区中;而fbcon.c负责借助双倍缓冲区中的附加的信息,调养VGA字库的表针,调用底层的显示驱动程序。

%A

%A这儿还有几个须要注意的地方:

%A

%A1、由于屏幕重画等诱因,调用底层xxxx_putc()和xxxx_putcs()的地方有多处,我们做了两个函数分别馐这两上调用,完成替换字库、调用xxxx_putcs()或xxxx_putc()、恢复字库等功能。

%A

%A2、为了实现向下滚屏时也能见到汉字,我们须要作另外的更改。Linux在设计虚拟终端的时侯,提供了回顾被滚粗屏幕以外的信息的功能,这就是用键位来向下滚屏(Shift+Pageup)。当前被使用的虎虚拟终端的时侯,公共缓冲区的内容会被消除而被新的虚拟终端使用,向下滚屏的时侯,显示的是公共缓冲区中的内容。为此,假如我们想在向下滚屏的时侯见到汉字,则公共缓冲区也必须加倍,以确保没有信息遗失。当滚粗屏幕的住处向公共缓冲区填写的时侯,必须把盯应的附加信息也填写进公共缓冲区的附加区域中,这就要求fbcon.c必须懂得借助公共缓冲区的附加信息。其实,另外有一处偷懒的方式,那就是不容许用户向下滚屏,进而防止对公区缓冲区的处理。

%A

%A3、把不同的编码方法(GB、BIG5、日文和英文)写成不同的模块,以实现动态加载,进而促使扩充新的编码方法不须要重新编译核心。

%A

%A小结

%A

%A通过此次针对inux核心的探求linux操作系统论文,我们发觉,目前Linux的核心设计中,完全没有考虑到双字节编码字符的显示,我们在这些情况下摸索出一套解决核心汉字显示的方式,并编码实现了该方案。遵守核心的GPL版权申明,我们同时公布了实现这一技术的源代码,其实,这种改动一直是GPL的。倘若能对研究核心的同学有所帮助,养活一些你们对核心的神秘感,将是我们最大的收获。

%A

%A并且对核心和英文化来说,这仅仅是一种尝试,远不是终点。这些改动多少带有一些黑客的色调,不太可能融合进权威的核心里去。

%A

本文原创地址:https://www.linuxprobe.com/jylhxdhzxsdj.html编辑:刘遄,审核员:暂无