構造Linux的圖形化安裝程序(2)

火星人 @ 2014-03-09 , reply:0


圖形環境的啟動及中文化
於辰濤 (scu_yct@263.net)
2001 年 9 月

本文是構造Linux的圖形化安裝程序系列文章的第二部分,主要介紹在安裝程序裝入內存之後,如何啟動圖形環境並設置中文語言環境.文中除了介紹安裝程序對這兩個功能的具體編程實現,還介紹了Linux中文化的一些基本知識,包括如何定製安裝程序顯示中所需要的Locale環境(Glibc和X Window系統兩部分),po文件的處理.
在安裝程序的第二階段運行結束之後,整個安裝程序已經被調入了內存,這時控制從安裝程序的載入程序轉到了安裝程序的主程序執行.這時的安裝程序就像是一個從Linux的控制台環境啟動的程序,這個程序進行自動檢測並進行基本的設備配置.為了進行圖形化安裝,安裝程序的主程序會啟動XFree86子系統並設置,同時設置當前的語言環境為中文環境.

1. 多語言環境支持
為了在Linux下實現多語言支持,在定製安裝程序時,引入與glibc和圖形環境兩者對應的多語言環境支持.對於glibc環境而言,它是整個Linux中文化的基礎,我們需要在/usr/share/locale目錄下保存Locale信息,在/usr/share/consolefonts目錄下保存字體信息,在/usr/lib/gconv目錄下保存字元轉換模塊的信息.

對於X Windows環境,我們需要在/usr/X11R6/lib/X11/locale目錄下保存X Locale的配置,同時在/usr/X11R6/lib/X11/fonts目錄下保存正常顯示所需要的字體集.

在安裝程序啟動之後,正確設置LC_*變數同時調用setlocale函數.為了能顯示中文,安裝程序還載入正確的中文字體.

1.1. 國際化的基本概念
國際化(Internationalization,簡寫為I18N)是指軟體在設計結構和機制上支持多語言的擴展特性,其功能和代碼設計不針對某一特定語言和地域.Locale是ANSI C語言中最基本的支持國際化的標誌,對中文Linux來說,支持中文Locale是最基本的要求.



1.1.1. Locale環境
Locale的命名規則:<語言>_<地區名>.<字元編碼名稱>

對於zh_CN.GB2312而言,zh表示中文,CN表示大陸地區,GB2312表示使用的字符集為GB2312.

Locale使用一組分類,用戶可以獨立的操縱每一組分類.用戶既能用設置環境變數的方法,也能使用setlocale設置它們.這些分類都保存在/usr/share/locale下.它們包含了:


LC_COLLATE
用於比較和排序.
LC_CTYPE
用於字元分類和字元串處理,控制所有字元的處理方式,包括字元編碼,字元是單位元組還是多位元組,如何列印等.
LC_MONETORY
用于格式化貨幣單位.
LC_NUMERIC
用于格式化非貨幣的數字顯示.
LC_TIME
用于格式化時間和日期.
LC_MESSAGES
用於控制程序輸出時所使用的語言,主要是提示信息,錯誤信息, 狀態信息, 標題,標籤, 按鈕和菜單等.
LC_ALL
它不是環境變數,只是一個宏,可使用setlocale設置所有的LC_*環境變數.這個變數設置之後,可以廢除LC_*和LANG的設置值,這些變數的設置值與LC_ALL的值一致.
LANG
它的值用於指定上面環境變數沒有設置的所有變數值.如果指定了上面任何一個變數的值,則會廢除對應的LANG值的預設設置.

還可以包括其他的環境變數LC_ADDRESS,LC_IDENTIFICATION,LC_PAPER,LC_NAME,LC_TELEPHONE,LC_MEASUREMENT.

標準Locale:

"C"
這是標準的C Locale.它所指定的屬性和行為由ISO C標準所指定.它是程序啟動時預設使用Locale.

"POSIX"
這是標準的POSIX Locale.它是標準的C Locale的別名.

""
空名字是讓程序選擇當前環境設置值.

設置一個中文環境需要正確的設置上述Locale變數,舉例來說,在使用zh_CN.GB2312環境時,使用locale命令,所見到的系統環境為:


LANG="zh_CN.GB2312"
LC_CTYPE="zh_CN.GB2312"
LC_NUMERIC="zh_CN.GB2312"
LC_TIME="zh_CN.GB2312"
LC_COLLATE="zh_CN.GB2312"
LC_MONETARY="zh_CN.GB2312"
LC_MESSAGES="zh_CN.GB2312"
LC_PAPER="zh_CN.GB2312"
LC_NAME="zh_CN.GB2312"
LC_ADDRESS="zh_CN.GB2312"
LC_TELEPHONE="zh_CN.GB2312"
LC_MEASUREMENT="zh_CN.GB2312"
LC_IDENTIFICATION="zh_CN.GB2312"
LC_ALL=




1.1.2. 創建Locale環境

為了建立locale環境,我們具備下面的描述文件:
locale-data
這個文件定義了Locale環境(LC_*)的所有細節,包括字元的分類與轉換,字元排序,區域顯示時間,貨幣顯示格式等等.通常是保存在系統的/usr/share/i18n/locales目錄下.

charmap
這個文件定義了Locale中所有字元與內碼的對應關係.通常是保存在系統的/usr/share/i18n/charmaps目錄下.

這兩個文件都是純文本文件,可以使用文本編輯器直接察看和修改.通過這兩個文件就可以生成對應的locale環境.預設條件下,生成的locale環境是以二進位的形式保存在/usr/share/locale目錄下.把這兩個文本文件生成locale環境的工作是由localedef程序實現的.舉例來說,生成zh_CN.GB2312的locale環境:
mkdir /home/usr/share/locale/zh_CN.gb2312
localedef -I zh_CN -f GB2312 zh_CN.GB2312 --prefix=/home
cd /home/usr/share/locale/
mv zh_CN.gb2312 zh_CN.GB2312



這幾條命令在/home目錄下,生成Locale環境zh_CN.GB2312.按照POSIX標準,一個Locale的編碼名稱是大小寫無關的.雖然我們指定的是大寫的GB2312,但是glibc為了統一起見,它會生成一個小寫的編碼名稱.但是很多程序依賴於zh_CN.GB2312,因此對這個文件進行了改名.

除了Locale環境之外,您還需要gconv文件.這一組文件是用來定義glibc的gconv系統在遇到GB2312編碼的字元時,應使用哪一個模塊來處理.gconv-modules文件描述了字元編碼和處理模塊文件對應關係.例如,在/usr/lib/gconv/gonv-modules文件中,需要包含下列行:


alias GB2312// EUC-CN//

1.1.3. X Window系統的多語言環境
X Window系統的多語言環境是在系統底層libc的Locale架構的基礎上建立起來的.X函數庫需要利用libc提供的函數來進行字元之間的轉換.因此,為了使X Window應用程序的Locale正確工作,您設置一個正確的libc Locale環境,同時正確設置LC_CTYPE這個類別.

指定了編碼方式並且將字元的辨識和轉換用libc的函數處理之後,X Window系統的多語言處理的主要問題就變為圖形顯示和輸入了.在X系統下,多語言環境能做到多語言字元圖形化輸出和字元輸入.字元的圖形化輸出還要處理字型,這又與字型的設定方式有關.

與libc一樣,在X Window系統下也有關於Locale的設置部分,稱為X-Locale.XFree86系統都把它保留在/usr/X11R6/lib/X11/locale目錄下.在這個目錄下的每個Locale都有一個目錄,存放各自的X-locale,一般這個文件的名字是XLC_LOCALE. 這個文件裡面包含了跟該區域編碼相關的設定,而文件中以#字型大小開頭的是註釋.以安裝程序上簡體中文XLC_LOCALE的內容為例:
XLC_FONTSET
# fs0 class (7 bit ASCII)
fs0 {
charset {
name ISO8859-1:GL
}
font {
primary ISO8859-1:GL
vertical_rotate all
}
}
# fs1 class
fs1 {
charset {
name GB2312.1980-0:GL
}
font {
primary GB2312.1980-0:GL
}
}
END XLC_FONTSET


以上內容定義的是顯示Locale時使用的字符集.在多語言環境中,為了同時顯示中英文,系統往往需要多種字體.例如,上例中表示:在顯示簡體中文時要使用兩種不同編碼的字體,其中一個是使用了GB2312編碼的中文字體,其字體名稱以GB2312.1980-0結束,另一個是ISO8859-1編碼的英文字體,其字體名稱以ISO8859-1結束.

XLC_LOCALE的下一部分定義了Locale中的字體在X系統中的處理方式:


XLC_XLOCALE

encoding_name zh.euc
mb_cur_max 2
state_depend_encoding False
wc_encoding_mask x30000000
wc_shift_bits 7

cs0 {
side GL:Default
length 1
wc_encoding x00000000
ct_encoding ISO8859-1:GL
}

cs1 {
side GR:Default
length 2
wc_encoding x30000000
ct_encoding GB2312.1980-0:GL; GB2312.1980-0:GR
}

END XLC_XLOCALE




cs0定義的部分為iso8859-1編碼的英文字元,每個字元佔用一個位元組.cs1定義的部分為GB2312編碼的中文字元,每個字元佔用兩個位元組,這兩個位元組都用GB2312.1980-0的字體來表示.注意,對於上面的X設置,GB2312.1980-1類型的字體是無法正常顯示的,除非用戶修改X Locale.

除此之外,/usr/X11R6/lib/X11/locale還使用locale.dir和locale.alias文件定義可用X-Locale的名稱和位置.其中,locale.dir定義了每個X-Locale文件的位置以及實際的Locale名稱.locale.alias文件則定義了每個Locale可能的別名.

X Window 的字體存放在/usr/X11R6/lib/X11/fonts目錄下,一般使用PCF的字體.在將新的字體文件拷貝到此目錄之後,運行

mkfontdir

由這條命令更新改目錄下的fonts.dir的內容.例如,

fzs14.pcf.Z fzs14

helvR12_iso01.pcf.gz -adobe-helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1

上面兩行的信息是從字體文件中抽取出來的,比如,helvR12_iso01.pcf.gz文件中就包含iso8859-1編碼的文件.但是,字體文件fzs14.pcf.Z就不包含類似的字體說明信息,因此我們還需要使用fonts.alias文件建立字體別名.

-hlc-song-medium-r-normal--14-140-75-75-c-140-gb2312.1980-0 fzs14

這樣在安裝程序使用下面的資源時,X Window系統就知道要載入fzs14字體了.
style "font" {
fontset="-*-helvetica-*-r-normal-*-14-*-*-*-*-*-iso8859-1, -*-medium-*-14-*-*-*-*-*-*-gb2312.1980-0"


}



1.2. 安裝程序的國際化實現


自動生成po文件

在安裝程序中,定義了兩個函數_()和__(),前者是對傳入的串進行翻譯,後者是直接返回傳入的串,凡是要進行多語言支持的串使用這兩個函數.它們兩者在安裝程序中的用法是不同的.前者是用於一些可以進行重建的界面控制項,並且它們的值一般作為局部變數出現;而後者一般用於全局變數,為了讓這個串顯示多種語言,需要使用translate()函數進行處理.使用這兩個函數的主要目的是可以很方便的使用xgettext程序自動生成po文件.

xgettext只對c語言的源程序文件生效,因此在使用它之前先將perl源程序轉換為c程序,然後再運行xgettext自動提出需要進行多語言處理的文本串.下面的程序段是po目錄下Makefile文件的一部分,它可以自動生成空的po文件.


PMSFILES = $(wildcard *.pm)
PMSCFILES = $(PMSFILES:%=%_.c)
POFILES = $(shell ls *.po)

all: $(PMSCFILES) DrakX.pot

clean:
rm -f empty.po messages $(POFILES:%=%t) $(PMSCFILES)

verif:
perl -ne '/^s*#/ or $$i = my @l = /b__?(/g; END { print "$$in" }' $(PMSFILES)
perl -ne '$$i = my @l = /.c:/g; END { print "$$in" }' DrakX.pot

DrakX.pot: $(PMSFILES)
xgettext -F -n --keyword=_ --keyword=__ -o $@ $(PMSCFILES)
rm $(PMSCFILES)

$(PMSCFILES): %_.c: %
perl -pe 's|^(__?()| $$1|; s|#([^ ].*)|/*1*/|; s|$$|\n\|' $< > $@




處理po文件

在安裝程序中po文件並不進行編譯.perl語言處理文本文件的能力非常強,它直接作為文本文件形式存在.在調入po文件時,perl根據msgid和msgstr的內容生成一個hash結構,然後由此結構進行文本串的處理.

#- 此函數載入指定語言的po文件

sub load_po {


my ($lang) = @_;
my ($s, $from, $to, $state, $fuzzy);

#- 將$s中的內容作為一個perl文件存在,在這個文件中生成一個巨大的hash結構
#- 當進行多語言處理時,根據這個hash結構獲得翻譯的文本串.
$s .= "package po::I18N;n";
$s .= "no strict;n";
$s .= "%{'$lang'} = (";

$f = "po/$lang.po";
local *F;
my $pid;
unless ($f && -e $f) {
… …
#- 如果文件不存在,則從壓縮包中取出此文件,並打開
}
local $_;
while () {
/^msgstr/ and $state = 1;
/^msgid/ && !$fuzzy and $state = 2;
s/@/\@/g;

if (/^(#|$)/ && $state != 3) {
$state = 3;
if (my @l = $to =~ /%(d )$/g) {
$to =~ s/%(d )$/%/g;
$to = qq([ "$to", ] . join(",", map { $_ - 1 } @l) . " ),";
} else {
$to = qq("$to");
}
#- 形成hash結構,msgid的內容保存在$from中,msgstr的內容保存在$to中
$s .= qq("$from" => $to,n) if $from;
$from = $to = '';
}
$to .= (/"(.*)"/)[0] if $state == 1;
$from .= (/"(.*)"/)[0] if $state == 2;

$fuzzy = /^#, fuzzy/;
}
$s .= ");";
no strict "vars";
eval $s;
$pid and waitpid $pid, 0;
!$@;
}



文本串翻譯

#- 文本串的翻譯過程主要是訪問讀入po文件所形成的hash結構,由它的值返回翻譯后的文本串.
sub translate {
my ($s) = @_;
#- 如果Locale環境未設置,則設置環境為英語
my ($lang) = $ENV{LANGUAGE} || $ENV{LC_MESSAGES} || $ENV{LC_ALL} || $ENV{LANG} || 'en';

require lang;
foreach (split ':', $lang) {
#- 如果hash結構未定義,則調入語言對應的po文件
lang::load_po($_) unless defined $po::I18N::{$_};
#- 如果未能正確載入po文件或者待翻譯的串不存在,則返回源串$s.
if (%{$po::I18N::{$_}}) {
return if $s eq '_I18N_';


return ${$po::I18N::{$_}}{$s} || $s
}
}
$s;
}
l 設置Locale環境
設置Locale環境主要就是設置Locale變數的值.
#- [ 'English|United States', 'iso-8859-1', 'en', 'en_US:en' ]這個數組表示
#- 第一個元素是語言環境的文本描述.
#- 通過第二個元素的值查詢%charset結構,可以獲得對應的字體.
#- 第三個元素是LANG變數的設置值.
#- 第四個元素是LANGUAGE變數的設置值.
my %languages = (
'en_US' => [ 'English|United States', 'iso-8859-1', 'en', 'en_US:en' ],
'zh_CN.GB2312' => [ 'Chinese|Simplified', 'gb2312', 'zh_CN.GB2312', 'zh_CN.GB2312:zh_CN.gb2312:zh_CN:zh' ]
);
my %charsets = (
"iso-8859-1" => [ "lat0-sun16", undef, "iso15",
"iso8859-1", "850", sub { std("iso8859-1", @_) } ],
"gb2312" => [ undef, undef, undef,
"gb2312", "936", "-*-*-*-*-*-*-*-*-*-*-*-*-gb2312.1980-1" ],
);
unless (-e "$ENV{SHARE_PATH}/locale/$languages{$lang}[2]") {
@ENV{qw(LANG LC_ALL LANGUAGE LINGUAS)} = ();
eval { commands::rm("-r", "$ENV{SHARE_PATH}/locale") };
eval {
#- 取出Locale環境
require packdrake;
my $packer = new packdrake("$ENV{SHARE_PATH}/locale.cz2");
$packer->extract_archive("$ENV{SHARE_PATH}/locale", "UTF-8", $languages{$lang}[2]);
};
}
#- 明確設置Locale的環境變數
$ENV{LC_NUMERIC} = "C";
$ENV{LC_TIME} = "C";
$ENV{LC_COLLATE} = "C";
$ENV{LC_MONETARY} = "C";
$ENV{LC_MESSAGES} = "C";
$ENV{LC_PAPER} = "C";
$ENV{LC_NAME} = "C";
$ENV{LC_ADDRESS} = "C";
$ENV{LC_TELEPHONE} = "C";
$ENV{LC_MEASUREMENT} = "C";
$ENV{LC_IDENTIFICATION} = "C";
$ENV{LC_CTYPE} = $lang;
$ENV{LANG} = $languages{$lang}[2];
$ENV{LANGUAGE} = $languages{$lang}[3];


設置Locale變數之後,調用Gtk::Rc->parse_string()裝入所需要的字體資源,然後調用Gtk->set_locale()使Locale設置生效就可以了.



2. 啟動圖形環境
啟動圖形環境,實際上就是在系統中運行X服務程序.為了使一個X伺服器正常運行,生成正確的XFree86配置文件.在安裝程序的install_gtk.pm的createXconf函數中創建XFree86配置文件.關於XFree86的詳細配置過程您也可以參看《如何在Linux下實現設備的配置》.


#- 這是安裝程序的createXconf函數,它的主要功能是生成XFree86的配置文件.為了使下面
#- 的代碼段更清晰,刪除了一些根據晶元類型和滑鼠類型進行配置的代碼.因此,下面的代碼和真
#- 實的安裝程序代碼略有不同只支持x86體系結構下的配置文件.

#- 建立真實滑鼠設備的別名鏈接文件/dev/mouse.
symlinkf($mouse_dev, "/dev/mouse");
#- 建立imlib庫配置文件和調色板文件的鏈接文件
symlink("/tmp/stage2/etc/imrc", "/etc/imrc");
symlink("/tmp/stage2/etc/im_palette.pal", "etc/im_palette.pal");

#- 生成XFree86配置文件$file
local *F;
open F, ">$file" or die "can't create X configuration file $file";
print F <#- 下面的文本行,實際上是XFree86的配置文件.這個配置文件包含字體路徑設置、鍵盤設置、
#- 滑鼠設備設置、監視器設置和屏幕設置部分.
Section "Files"
FontPath "/usr/X11R6/lib/X11/fonts:unscaled"
EndSection

#- 鍵盤設置部分,設置當前鍵盤為標準鍵盤.
Section "Keyboard"
Protocol "Standard"
AutoRepeat 0 0
LeftAlt Meta
RightAlt Meta
ScrollLock Compose
RightCtl Control
XkbDisable
END
EndSection

#- 設置滑鼠配置,包括設置滑鼠的通訊協議類型以及滑鼠設備對應的設備文件.
Section "Pointer"
Protocol "$mouse_type"
Device "/dev/mouse"
ZAxisMapping 4 5
EndSection

#- 設置監視器配置.在定製安裝環境時,只使用了3.3.6的X伺服器XF86_VGA16和


#- XF86_FBDev,這兩種伺服器的X配置文件都是無法指定顯示解析度的.對於XF86_VGA16,
#- 它只能支持640x480的16色顯示方式.對於XF86_FBDev,它支持的顯示方式是由當時內核
#- Framebuffer的工作模式決定的.比如,內核以0x311方式啟動,那麼對應的解析度是
#- 640x480的16位色,那麼在使用XF86_FBDev作為X伺服器時,X的對應配置也與此完#- 全一致.
Section "Monitor"
Identifier "My Monitor"
VendorName "Unknown"
ModelName "Unknown"
HorizSync 31.5-35.5
VertRefresh 50-70
Modeline "640x480" 25.175 640 664 760 800 480 491 493 525
Modeline "640x480" 28.3 640 664 760 800 480 491 493 525
ModeLine "800x600" 36 800 824 896 1024 600 601 603 625
EndSection

#- 使用XF86_VGA16作為X伺服器時,對應的X配置節.
Section "Screen"
Driver "vga16"
Device "Generic VGA"
Monitor "My Monitor"
Subsection "Display"
Modes "640x480"
ViewPort 0 0
EndSubsection
EndSection

#- 使用XF86_FBDev作為X伺服器時,對應的X配置節.設置Modes為"default",表示選擇
#- 的模式與內核啟動時設置的模式相同.
Section "Screen"
Driver "fbdev"
Device "Generic VGA"
Monitor "My Monitor"
Subsection "Display"
Depth 16
Modes "default"
ViewPort 0 0
EndSubsection
EndSection
END




將上述程序段運行之後,X配置腳本就生成了,這時就可以使用這個配置腳本文件啟動X服務程序了.下面這段啟動X的程序是來自於文件install_steps_gtk.pm.
devices::make("/dev/kbd");

local (*T1, *T2);
open T1, ">/dev/tty5";
open T2, ">/dev/tty6";

#- 執行X伺服器程序,返回1表示運行成功.


my $launchX = sub {
my $ok = 1;
#- 接管信號SIGCHLD的處理程序
#- 在進程終止時,SIGCHLD信號被發送給它的父進程.此程序在子進程結束時調用.
local $SIG{CHLD} = sub { $ok = 0 if waitpid(-1, c::WNOHANG()) > 0 };
#- 子進程執行
#- XF86_FBDev -kb -dpms -s 240 -allowMouseOpenFail -xf86config $f
#- allowMouseOpenFail允許滑鼠打開失敗
unless (fork) {
exec $_[0], "-kb", "-dpms","-s" ,"240",
"-allowMouseOpenFail", "-xf86config", $f or exit 1;
}
#- 測試X伺服器是否成功激活
foreach (1..60) {
sleep 1;
log::l("Server died"), return 0 if !$ok;
return 1 if c::Xtest($ENV{DISPLAY});
}
log::l("Timeout!!");
0;
};
#- 準備測試的X伺服器
my @servers = qw(FBDev VGA16);
foreach (@servers) {
log::l("Trying with server $_");
my $dir = "/usr/X11R6/bin";
my $prog = "XF86_$_";
#- 如果X服務程序不可執行
unless (-x "$dir/$prog") {
#- 刪除/usr/X11R6/bin/下的所有X服務程序
unlink $_ foreach glob_("$dir/X*");
#- 從安裝盤上拷貝對應的X服務程序
install_any::getAndSaveFile("HappyLinux/happyinst$dir/$prog", "$dir/$prog") or die "failed to get server $prog: $!";
chmod 0755, "$dir/$prog";
}
#- 根據/proc/fb文件的內容檢測內核是否啟動了Framebuffer方式.
if (/FB/) {
!$o->{vga16} && $o->{allowFB} or next;
$o->{allowFB} = &$launchX($prog) and goto OK;
} else {
$o->{vga16} = 1 if /VGA16/;
&$launchX($prog) and goto OK;
}
}



註: (我們在此專題中介紹的是HappyLinux 3.0的安裝程序,它的安裝程序源碼您可以在安裝盤的/HappyLinux/happyinst/usr/bin/perl-install目錄下找到.安裝程序的源碼是由Perl編製的,使用PerlGtk繪製圖形化用戶介面.)

作者介紹
於辰濤,聯想(北京)電腦公司軟體工程師.目前主要從事Linux系統安裝程序的開發工作,主要研究興趣是操作系統的工作機制和開發底層系統程序.您可以通過電子郵件 scu_yct@263.net 跟他聯繫.





[火星人 via ] 構造Linux的圖形化安裝程序(2)已經有136次圍觀

http://www.coctec.com/docs/linux/show-post-56409.html