在闡述“基於 Linux 核心的漢字顯示”的技術細節之前,有必要介紹一下原有Linux的工作機制。這裡主要涉及到兩部分的知識,這是Linux下終端和幀緩沖的實現。 控制台(console) 通常我們在Linux下看到的控制台(console)是由幾個設備構成的。分別是/dev/ttyN
在闡述“基於
Linux核心的漢字顯示”的技術細節之前,有必要介紹一下原有Linux的工作機制。這裡主要涉及到兩部分的
知識,這是Linux下終端和幀緩沖的實現。
控制台(console) 通常我們在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 同時也負責接收終端的輸入,然後把接收到的輸入的信息放到緩沖區。
幀緩沖(framebuffer) Framebuffer是把顯存抽象後的一個種設備,可以通過這個設備的讀寫直接對顯存進行操作。這種操作是抽象的、統一的。用戶不必關心物理顯存的位置、換頁機制等等具體細節,這些都是由Framebuffer設備驅動程序來完成的。
Framebuffer對應的源文件在linux/drivers/video/目錄下。總的抽象設備文作為fbcon.c,在這個目錄下還有與各種顯卡驅動程序相關的源文件。
在使用幀緩沖時,Linux是將顯卡置於圖形模式下的。
我們以一個簡單的例子來說明字符顯示的過程。我們假設是在虛擬終端1(/dev/tty1)下遷行如下的簡單程序:
main ()
{
puts(”hello,world.
”);
}
pputs函數向缺省輸出文件(/dev/tty)發出“寫”的系統調用write(2)。系統調用到Linux核心對應的核心函數->—— console.c中的con_write( ), con_write( )最終會調用do_con_write(),在do_con_write()中負責把”hello,world.
”這個字符串放到tty1對應的緩沖區中去。
Do_con_write()還負責處理控制字符和光標的位置。讓我們來看一下do_con-write()這個函數的聲明:
Static int do_con_write(struct
Tty_struct * tty, int
from_user, const unsigned
char *buf, int count )
其中tty是指向tty_struct結構的指針,這個結構裡存放著關於這個tty的所有信息(請參照linux/include/linux/tty.h)。tty_srtuct結構中定義了?p> 用(或高層)tty的屬性(例如寬度和高度等)。
在do_con_write()函數中用到了tty_struct結構中的driver_data變量。Driver_data是一個 vt_vt_stuct指針。在vt_struct結構中包含這個tty的序列號(我們正使用tty1,所以這個序號為1)。Vt_struct結構中有一個vc結構的數組vc_cons,這個數組就是各虛擬終端的私有數據。
Static int do_write(struct
Tty_struct * tty, int
From_user,const unsigned char
*buf, int conut)
{
struct vt_struct *vt = (struct
vt_struct *)tty_>driver_data;
//我們用到了driver_data變量
…………
currcons = vt->_num;
//在這裡的vc_nums就是1
…………
}
要訪問虛擬終端的私有數據,需使用vc_cons[currcons].d指針。這個指針指向的結構含有當前虛擬終端上光標的位置,緩沖區的起始地址、緩沖區大小等信息。
“hello,world.
”中的每一個字符都要經過conv_uni_to_pc()這個函數轉換成8位的顯示字符。這樣做的主要目的是使不同語言的國家能把16位的 Unicode碼映射到8位的顯示字符集裡,目前主要還是針對歐洲國家的語言,映射結果為8位,不包含雙字節(double byte)的范圍。
這種從Unicode到顯示字符的映射表上,會把中文的字符映射到其他的字符上,這是我們不希望看到也是不需要的,所以我們有兩種選擇:
1) 不進行conv_uni_to_pc()的轉換
2) 加載符合雙字節處理的映射關系,即對蜚 控制字符進行一對一的不變映射,我們自己定制的符合這種映射關系的Unicode碼表是direct.uni。
要想看/裝載當前系統的Unicode映射表,可使用外部命令loadunimap。
經過conv_uni_to_pc()轉換之後,”hello, world.
”中的字符被一個一個地填寫到tty的緩沖區中,然後do_con_write()調用底層的驅動程序,把緩沖區中的內容輸出到顯示器上(也就相當於把緩沖區的內容拷貝到VGA顯存中去)
sw->con putcs(vc_cons[currcons].d,
(u16 *)draw_from, (u16 *)draw_to_
(u16 *)draw_rwom, Y, draw_x);
之所以要調用底層驅動程序,是因為存在不同的顯示設備,其對應VGA顯存的存取方式也不一樣。
上面的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
ta *conp,struct display *p, const unsignde short *s, int count, int YY, int xx )
顯示中文
比如說我們試輸出一句中文:putcs(你好
”)(“你好”的內碼為0xc4.0xe3.0ba.0xc3)。這時候會怎麼樣呢?有一點可以肯定,“你好”肯定不會出現在屏幕上,原因是:
1、核心中沒有漢字字庫,中文顯示就是無米之炊了。
2、在負責字符顯示的void fbcon_cfb8_putcs()函數中,原有操作如下:
對於每個要顯示的字符,依次從虛擬終端緩沖區中以WORD為單位讀取(低位字節是ASCII碼,高8位是字符的屬性)。由於漢字是雙字節編碼方式,所以這種操作是不可能顯示出漢字的,只能顯示出xxxx_putcs()輸出的是一個一個的VGA字符。