作者:王守华 本文首先介绍了国家最新颁布的GB 18030-2000标准的结构和编码规则,对Linux的国际化和本地化机制进行了简要的概述,然后从Glibc、 Locale两个方面具体讨论了如何在Linux上实现对GB 18030-2000标准的支持,并且对下一步需要进行的工作进行了展望。 一、GB18030-2000编码标准简介 GB18030-2000编码标准是由信息产业部和国家质量技术监督局在2000年 3月17日联合发布的,并且将作为一项国家标准在明年的1月正式强制执行。 1.概述 GB18030-2000编码标准是在原来的 GB2312-1980 编码标准和 GBK 编码标准的基础上进行扩充,增加了四字节部分的编码。它可以完全映射ISO10646的基本平面和所有辅助平面,共有150多万个码位。在ISO10646的基本平面内,它在原来的2万多汉字的基础上增加了7000 多个汉字的码位和字型,从而使基本平面的汉字到达 27000多个。它的主要目的是为了解决一些生、偏、难字的问题,以及适应出版、邮政、户政、金融、地理信息系统等迫切需要的人名、地名用字问题。 GB18030-2000作为GB2311体系的编码字符标准,规定了信息交换用的图形字符及其二进制编码的十六进制表示。它支持 GB 13000.1-1993的全部中日韩(CJK)统一汉字字符和全部中日韩统一汉字Extension A和Extension B的字符。 2.字汇 GB18030-2000编码标准收录的字符分别以单字节、双字节和四字节编码。下面简要列举一下它们各自包括的内容: 1) 单字节部分 单字节部分收录了GB11383的0x00到0x7E全部 128个字符及单字节编码的欧元符号。 2) 双字节部分 双字节部分收录的内容如下: GB13000.1的全部CJK统一汉字字符; GB13000.1的CJK兼容区挑选出来的21个汉字; GB13000.1中收录而GB2312未收录的我国台湾地区使用的图形字符139个; GB13000.1收录的其它字符31个; GB2312中的非汉字符号; GB12345的竖排标点符号19个; Gb2312未收录的10个小写罗马数字; Gb2312未收录的带音调的汉语拼音字母5个以及 a和g; 汉字数字“O”; 表意文字描述符13个; 增补汉字和部首/构件80个; 双字节编码的欧元符号。 3) 四字节部分 四字节部分收录了上述双字节字符之外的,包括 CJK 统一汉字扩充 A 在内的 GB13000.1 中的全部字符。 3.编码规则 GB18030-2000标准采用单字节、双字节和四字节三种方式对字符编码。单字节部分采用GB 11383的编码结构与规则,使用 0x00至0x80码位。双字节部分采用两个字节表示一个字符,其首字节码位从 0x81至0xFE,尾字节码位分别是0x40至0x7E和0x80至0xFE。四字节部分第一、三字节仍为0x81~0xFE,第二、四字节采用GB 11383未使用的0x30到0x39作为对双字节编码扩充的后缀,这样扩充的四字节编码,其范围为0x81308130 到 0xFE39FE39。码位范围分配如下表所示: 表1 码位范围分配 字节数 码位空间 码位数 单字节 0x00~0x80 129 个码位 双字节 第一字节 第二字节 23940 个码位 0x81~0xfe 0x40~0x7e, 0x80~0xfe 四字节 第一字节 第二字节 第三字节 第四字节 1587600 个码位 0x81~0xfe 0x30~0x39 0x81~0xfe 0x30~0x39 四字节字符的编码自第四个字节开始,编码码位为 0x30至0x39;其次是第三个字节,编码码位为0x81至0xFE;再次是第二个字节,编码码位为0x30至0x39;最后是第一个字节,编码码位为0x81至0xFE。即: 0x81308130至0x81308139; 0x81308230至0x81308239; ...... 0x8130FE30至0x8130FE39; 0x81318130至0x81318139; ...... 0x8131FE30至0x8131FE39; ...... 0x82308130至0x82308139; ...... 0xFE308130至0xFE308139; ...... 0xFE39FE30至0xFE39FE39。 二、Linux的国际化和本地化机制 Linux的国际化和本地化机制非常灵活方便,这使得我们在Linux增加一种新的本地化支持非常容易,甚至基本上不需要去编译应用程序包,而只需要在系统核心的某些地方进行扩充和修改就可以了。 1.NLS简介 为了更好地对国际化和本地化进行支持,Linux系统 提供了符合Posix标准的 NLS(National Language Support)子系统。该子系统建筑在基于ASCII码的Linux核心上,为世界上不同地域、不同语言环境的应用提供国际化本地化支持。系统中所有支持多国语言的实用程序,包括X Window都是建立在这个基础上。在此基础上,可以建立支持各种不同的语言文化的民族特征数据库(LOCALE)、输入方法(IM)、字体(FONT)和消息机制(MESSAGE)等。在NLS子系统中,Glibc函数库中提供的与代码集相关的多字节字符与宽字符处理函数是Linux实用程序支持国际化本地化的核心,通过这些函数,实用程序把英文与各种本地文字同样处理;而 Locale 则是本地化工作的一个基石,因为不管是Glibc,还是系统的其它部分,都是通过读取系统当前的 Locale 设定来识别 当前的本地化区域,从而使用正确的字符映射表和消息函数。 2.Glibc的转换模式概念 libc对多字节编码的支持非常灵活和自由,它通过一种"转换模式"来实现。目前的Glibc采用UTF-8作为处理码。当接收到外部输入的多字节字符时,系统首先根据当前的 Locale 确定所使用的字符集,然后查找从当前字符集到UTF-8之间的转换模式,根据这种转换模式把输入的多字节字符转换为相应的UTF-8字符。相反,当需要输出字符时,系统再查找从UTF-8到当前字符集之间的转换模式,按照这种模式把要输出的UTF-8字符转换为当前字符集中的多字节字符。在Glibc内部,有一个gconv_modules的转换模式列表。这个列表作为一个文件的形式存在于/usr/lib/gconv 目录下。对于每种具体的转换模式,Glibc把它们的转换表和转换函数编译为一个独立的共享文件(.so),比如支持GB的转换模式的共享文件名为libGB.so,支持Big5的共享文件名为 libBIG5.so。当应用程序调用到Glibc中有关编码转换部分的函数时,Glibc首先从gconv_modules 这个转换模式列表中查找所应该采用的转换模式,然后把相应转换模式的共享文件调入内存。这种动态装载的方式,使得添加或者删除转换模式非常自由和方便,而且当Glibc 支持越来越多的编码时,libc.so这个文件本身并不会变得异常庞大。 3.Glibc对多字节字符和宽字符的转换处理 Glibc中提供了一种新的字符类型——宽字符。宽字符的好处就是它可以把这个字符作为一个单字来处理,而不会导致多字节字符在处理时遇到的删除半个字符的现象。为了更好地使用宽字节字符, Glibc提供了多字节字符和宽字符之间的转换函数以及其它的对宽字符处理的函数。前面介绍的当系统要将本地化编码集中的多字节字符转化为系统的处理内码UTF-8 时,使用的就是这类处理函数中的mBTowc()函数。下面就以这个函数的工作流程为例进行详细介绍:支持国际化本地化的应用程序在需要将一个多字节字符转换UTF-8处理码时,调用 mbtowc()函数进行处理。但是,mbtowc()函数只是一个外壳,它主要是调用 mbrtowc()函数。而在mbrtowc函数中,首先调用 update_conversion_ptrs() 函数来更改当前的转换函数,然后调用 _wcsmbs_gconv_fcts 结构的 towc 成员 的 fct 成员函数来完成实际的转换。而在 update_conversion_ptrs() 函数中, 主要是通过调用 __wcsmbs_load_conv() 函数来完成的。__wcsmbs_load_conv() 函数函数又调用了getfct() 函数来取得当前转换模式的结构信息,同时根据转换模式的结构信息调用相应的共享文件。getfct() 函数调用 gconv_find_transform()函数来查找当前应该采用的转换函数,并且把转换函数的名字放在转换模式的结构中一起返回。 mbtowc()函数实现的具体步骤如图1所示: 从上面的转换过程可以看出,要想在Linux上增加对GB18030-2000编码的支持,需要对Glibc进行一定的扩充。这种扩充重要就是增加新的支持GB18030-2000编码标准的转换模式。由于目前Linux源码的开放性,使得我们无论是了解程序的工作流程,还是进行具体的修改都十分方便。至于对Glibc所做的扩充,在第三部分将会详细介绍。 4.Locale简介 Locale是Linux本地化工作的基础,针对不同的区域, 它在 /usr/share/locales 目录下面有不同的Locale。 一般来说,Locale是由三个文件创建得到的,它们分别 是Charmap Source 文件、Locale Source文件和Method文件。其中Charmap Source 文件主要用来定义该Locale所支持的字符集中的每个字符; Locale Source文件的主要作用是定义Locale的六个域,它们分别是LC_CTYPE、LC_COLLATE、LC_TIME、 LC_NUMERIC、LC_MONETARY、LC_MESSAGES,在新的Glibc2.2版本中将会支持更多的 Locale域;而所谓Method文件,其实就是针对多字节处理所提供的一些函数。 Glibc 提供了 localedef 函数来生成具体的 Locale, localedef 的用法如下所示: localedef -f charmapsourcefile -i localesourcefile -m methodfile localename 其中-f, -i 和 -m 参数分别表示指定 charmap source 文件, locale source 文件和 method 文件,而最后的一个参数表示所要生成的 locale 的名字。 三、为支持GB18030-2000所做的工作 GB18030-2000标准在Linux上面的实现可以分为三个步骤:内核级支持、基本系统和完全支持。其中内核级支持就是指系统能够正确识别 GB18030-2000编码,并且有关多字节字符和宽字节字符处理的函数能够正常工作;而基本系统就是指在这之上字符界面和X Window平台下能够正确显示和输入 GB18030-2000编码,同时有一些常用应用程序能够正确处理GB18030-2000编码;完全实现就是指除了上述支持之外,其它的所有应用程序、数据库和网络都能够正确支持 GB18030-2000 编码,另外,所有支持国际化的应用程序都应该能够不做修改地在该系统上面运行。 我们完成的工作已经达到了内核级的支持,也就是从 Glibc 和Locale两个对系统进行扩充,下一步的工作就是实现一个基本系统。 1.Glibc方面 在Glibc中,定义了许多编码和内码之间的转换模式,这种 模式的定义在 iconvdata/gcong-modules文件中。如果要增加对 GB18030-2000 编码标准的支持,首先就需要在这个文件中增加两个新的转换模式:从 GB18030-2000 编码到内码和从内码到GB18030-2000编码。增加的几行代码如下所示: # from to module cost alias GB13000// GB18030// module GB18030// INTERNAL GB18030 2 module INTERNAL GB18030// GB18030 2 为了完成具体的转换,仅仅定义转换模式还是远远不够的,接下来要做的工作就是实现具体的转换函数和转换编码表。实现GB18030-2000编码遇到的一个主要困难就在这里,由于 GB18030-2000 编码是单字节、双字节和四字节混合的,而以前的GB2312-1980和GBK编码都只有单字节和双字节两种编码方式,这样就给系统的处理带来了一定的麻烦。唯一值得庆幸的是具体的转换函数可以自己实现,只需要按照一定的格式就可以了。 Glibc的这套固定格式其实就是一系列宏的定义,只要安装这种规则来正确定义这些宏,Glibc就能够正确地进行编码转换处理,比如下面是一套从 GB18030-2000 到系统内码的转换函数实现轮廓: #define CHARSET_NAME "GB18030//" #define FROM_LOOP from_gb18030 #define TO_LOOP to_gb18030 #define DEFINE_INIT 1 #define DEFINE_FINI 1 #define MIN_NEEDED_FROM 1 #define MAX_NEEDED_FROM 4 #define MIN_NEEDED_TO 4 #define MIN_NEEDED_INPUT MIN_NEEDED_FROM #define MAX_NEEDED_INPUT MAX_NEEDED_FROM #define MIN_NEEDED_OUTPUT MIN_NEEDED_TO #define LOOPFCT FROM_LOOP #define BODY { ...... } 从上面可以看出,这段代码主要是定义字符集的名字、转换前后的字符集以及其它的一些转换信息。其中最主要的工作就是BODY内的转换函数,它用来定义如何把一个 GB18030 的编码转换为系统内码。下面是完整的代码段: uint32_t ch = *inptr; if (ch <= 0x7f) ++inptr; else if (ch <= 0x80 ch > 0xfe) { /* This is illegal. */ if (! ignore_errors_p ()) { result = __GCONV_ILLEGAL_INPUT; break; } ++inptr; ++*irreversible; continue; } else { /* Two or more byte character. First test whether the next character is also available. */ uint32_t ch2; unsigned long int idx; if (inptr + 1 >= inend) { /* The second character is not available. Store the intermediate result. */ result = __GCONV_INCOMPLETE_INPUT; break; } ch2 = inptr[1]; /* All second bytes of a multibyte character must be >= 0x30. */ if (ch2 < 0x30) { if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } ++inptr; ++*irreversible; continue; } if (ch2 >= 0x30 && ch2 <= 0x39) { uint32_t ch3; uint32_t ch4; if (inptr + 3 >= inend) { /* Not all characters are available. Store the intermediate result. */ result = __GCONV_INCOMPLETE_INPUT; break; } ch3 = inptr[2]; if (ch3 < 0x81 ch3 > 0xfe) { /* This is an illegal character. */ if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } inptr += 3; ++*irreversible; continue; } ch4 = inptr[3]; if (ch4 < 0x30 ch4 > 0x39) { /* This is an illegal character. */ if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } inptr += 4; ++*irreversible; continue; } idx = ((((ch - 0x81) * 10 + (ch2 - 0x30)) * 126 + (ch3 - 0x81)) * 10 + (ch4 - 0x30)); if (idx >= (sizeof (__gb18030_to_UCs) / sizeof (__gb18030_to_ucs[0])) (ch = __gb18030_to_ucs[idx], ch == 0 && *inptr != '\0')) { /* This is an illegal character. */ if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } inptr += 4; ++*irreversible; continue; } inptr += 4; } else if (__builtin_eXPect (ch2, 0x40) >= 0x40) { idx = (ch - 0x81) * 192 + (ch2 - 0x40); if (idx >= sizeof (__gbk_to_ucs) / sizeof (__gbk_to_ucs[0]) (ch = __gbk_to_ucs[idx], ch == 0 && *inptr != '\0')) { /* This is an illegal character. */ if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } inptr += 2; ++*irreversible; continue; } inptr += 2; } else { /* This is an illegal character. */ if (! ignore_errors_p ()) { /* This is an illegal character. */ result = __GCONV_ILLEGAL_INPUT; break; } inptr += 2; ++*irreversible; continue; } } *((uint32_t *) outptr)++ = ch; 需要注意的是上面的每行语句后面都有一个“\”符号,这个续行符是为了保证整个函数是一个完整的宏而添加的。为了实现这种转换,还需要相应的编码转换表,而转换函数的具体代码就是从编码转换表中查找到当前GB18030编码对应的系统内码,然后返回这个系统内码。编码转换表一般是从编码的字符映射表生成的,在这个程序中,它应该作为一个或者多个数组的形式而存在。相应地,从系统内码到GB18030编码的转换函数的实现也是类似的,但是有一点区别就是转换后得到的GB18030编码是不定长的,它有单字节、双字节和四字节三种情况,从而会导致这部分的编码转换函数相对复杂得多。在编码转换表方面,需要按照返回的 GB18030 编码长度,对编码转换表进行分段,然后再根据各个段的情况进行分别处理;在编码转换程序方面,需要在返回GB18030编码的同时返回编码的长度,根据编码长度进行相应的处理。这部分的转换函数代码比较复杂,就不再附在本文中,如果想了解可以到http://sources.redhat.com/cgi-bin/cvsweb.cgi/libc/iconvdata/gb18030.c?rev= 1.3&content-type=text/x-cvsweb-markup&cvsroot=glibc页面中得到想要的内容。定义了转换模块、具体的转换函数以及它们使用的转换表,系统就能够正确识别相应字符集的编码了,最后还需要在Makefile中添加GB18030这个编码模块,同时指明它有由哪几个文件编译得到的。对Glibc的这些修改工作完成之后,还需要重新编译整个Glibc,就可以得到支持GB18030编码标准的Glibc库。 2.Locle方面 相对来说,在Locale方面所做的工作就相对简单一些。我们只需要编写一套新的关于GB18030-2000编码标准的charmap Source文件、Locale Source文件 和 Method 文件就可以了。其中Charmap Source文件可以从有关GB18030-2000编码标准的 一些文档中得到;Locale Source文件可以按照原来的GB2312或者GBK的Locale Source文件编写;Method文件也可以使用GB2312或者GBK所使用的Method文件。这三个文件完成之后,使用前面介绍过的localedef工具,就可以生成一个新的locale,我们不妨把它命名为zh_CN.GB18030。 Glibc所做的扩充结合新的zh_CN.GB18030 Locale,在Linux系统上就可以做到内码级地支 持 GB18030-2000编码标准了。现在可以试着把Locale设定为zh_CN.GB18030,然后在程序中调用mbtowc()、wctomb()、mblen()等函数,发现它们均可以正常工作,而且能够识别诸如 0x81308130之类的四字节编码。 3.进一步的工作 前面已经介绍过,GB18030-2000标准在Linux上的完全实现绝对不仅仅局限于内核级的实现,而应该包括基本系统的实现和应用程序的完全支持。在这方面还有许多工作要做,比如: * 字符界面下中文平台的显示和输入 * X Window下GB18030编码文字的显示和输入 * 系统基本命令的支持 * 应用程序的支持 * 网络支持 * 数据库方面的支持 四、小结 Glibc和Locale的扩充综合起来,就可以在Linux上面实现对 GB18030-2000编码的内码级支持,也就是说,Linux就可以正确识别和转换 GB 18030-2000的编码,在内核级别上支持GB18030-2000编码标准。如果想要实现一个完全支持GB18030-2000编码标准的Linux系统,还需要进一步的工作,本文也对它们进行了规划和展望。 参考资料 参考文献 关于本篇文章的参考资料,都是文献书籍。并列出如下: [1] 《信息技术 信息交换用汉字编码字符集基本集的扩充》,中国标准出版社,2000,北京 [2] Internationalization of AIX Software - A Programmer’s Guide, IBM 1992, USA [3] Yufang Sun, Jian Wu, Design and Implementation of CJK Character Set on UNIX system, 9th International Unicode Conference, San Jose, CA, USA, 1996.9 [4] 陈季雷、杨裕衡、林守铿,《洞悉UNIX——中文系统篇》,和硕科技文化有限公司,1994年7月,台北 [5] 吴健、孙玉芳、李国华、李祥凯,《“炎黄”中文平台的本地化处理》,已投《中文信息学报》
[1] [2] 下一页
(出处:http://www.sheup.com)