1 引言:為什麼要開發Lite版本 現在,大多數UNIX系統采用X 窗口系統作為圖形用戶界面,MS Windows 則采用 Microsoft公司自己設計的GUI系統。這兩種GUI系統也代表著目前通用GUI系統的兩種實現。比如,著名的自由軟件MicroWindows就同時實現了類似於MS Windows的MicroWindows API 和類似於X Window的NanoX API。 MiniGUI 原來就采用了類似於MS Windows的體系結構,並且建立了基於線程的消息傳遞和窗口管理機制。然而,它是基於POSIX 線程的,這種實現提供最大程度上的數據共享,但也同時造成MiniGUI體系結構上的脆弱。如果某個線程因為非法的數據訪問而終止運行,則整個系統都將受到影響。 另一種方法是采用UNIX進程間通信機制建立窗口系統,即類似 X Window 的客戶/服務器體系。但是這種體系結構也有它的先天不足,主要是通常的 IPC 機制無法提供高效的數據復制,大量的 CPU 資源用於各進程之間復制數據。在 PDA 等設備中,這種 CPU 資源的浪費將最終導致系統性能的降低以及設備耗電量的增加。 為了解決以上各種問題,同時也為了讓 MiniGUI更加適合於嵌入式系統,我們開發了MiniGUI Lite 版本。
2 Lite版本簡介 在MiniGUI Lite 版本中,我們可以同時運行多個 MiniGUI 應用程序。首先我們啟動一個服務器程序 mginit,然後我們可以從中啟動其他做為客戶運行的 MiniGUI 應用程序。如果因為某種原因客戶終止,服務器可以繼續運行。mginit程序建立了一個虛擬控制台窗口。我們可以從這個虛擬控制台的命令行啟動其他的程序,甚至可以通過 gdb 調試這些程序。 這大大方便了MiniGUI應用程序的調試。 MiniGUI-Lite 區別於 MiniGUI 原有版本的最大不同在於我們可以在 MiniGUI-Lite 程序中創建多個窗口,但不能啟動新的線程建立窗口。除此之外,其他幾乎所有的 API 都和 MiniGUI 原有版本是兼容的。因此,從 MiniGUI 原有版本向 MiniGUI-Lite 版本的移植是非常簡單的。象mglite-exec 包中的程序,其中所有的程序均來自 miniguiexec 包,而每個源文件的改動不超過 5 行。
3 Lite版本的設計 設計之初,我們確定MiniGUI Lite 版本的開發目的: 保持與原先 MiniGUI 版本在源代碼級 98% 以上的兼容。 不再使用 LinuxThreads。 可以同時運行多個基於 MiniGUI Lite 的應用程序,即多個進程,並且提供前後台進程的切換。 顯然,要滿足這三個設計目的,如果采用傳統的 C/S 結構對現有 MiniGUI 進行改造,應該不難實現。但傳統 C/S 結構的缺陷卻無法避免。經過對 PDA 等嵌入式系統的分析,我們發現,某些 PDA 產品具有運行多個任務的能力,但同一時刻在屏幕上進行繪制的程序,一般不會超過兩個。因此,只要確保將這兩個進程的繪制相互隔離,就不需要采用復雜的 C/S 結構處理多個進程窗口之間的互相剪切。在這種產品中,如果采用基於傳統 C/S 結構的多窗口系統,實際是一種浪費。 因此,我們對 MiniGUI-Lite 版本進行了如下簡化設計: 每個進程維護自己的主窗口 Z 序,同一進程創建的主窗口之間互相剪切。也就是說,除了只有一個線程,只有一個消息循環之外,一個進程與原有的 MiniGUI 版本之間沒有任何區別。每個進程在進行屏幕繪制時,不需要考慮其他進程。 建立一個簡單的客戶/服務器體系,但確保最小化進程間的數據復制功能。因此,在服務器和客戶之間傳遞的數據僅限於輸入設備的輸入數據,以及客戶和服務器之間的某些請求和響應數據。 有一個服務器進程(mginit),它負責初始化一些輸入設備,並且通過 UNIX Domain 套接字將輸入設備的消息發送到前台的 MiniGUI Lite 客戶進程。 服務器和客戶被分別限定在屏幕的某兩個不相交矩形內進行繪制,同一時刻,只能有一個客戶及服務器進行屏幕繪制。其他客戶可繼續運行,但屏幕輸入被屏蔽。服務器可以利用 API 接口將某個客戶切換到前台。同時,服務器和客戶之間采用信號和 System V 信號量進行同步。 服務器還采用 System V IPC 機制提供一些資源的共享,包括位圖、圖標、鼠標、字體等等,以便減少實際內存的消耗。
4 Lite版本的一些實現細節 4.1 系統初始化 應用程序的入口點為main()函數,而MiniGUI應用程序的入口點為MiniGUIMain,在這兩個入口點之間,是MiniGUI的初始化部分和結束部分。如圖 1所示。 圖 1 MiniGUI應用程序流程 在系統初始化時,MiniGUI區分兩種情況:服務器(Server)和客戶(Client)。針對這兩種不同的情況,隨後的各項操作均有不同的處理,這主要依據全局變量mgServer。由於僅僅根據名稱判斷是否為服務器,所以服務器的名字只能是"mginit"。 InitGUI()是對MiniGUI進行初始化的函數,它主要負責: 獲取有關終端的信息。 初始化圖形抽象層。 如果是服務器,則裝入共享資源,若為客戶則與共享資源建立連接。 建立與窗口活動有關的運行環境。 如果為服務器,則初始化事件驅動抽象層(IAL),如果為客戶,則打開與服務器事件驅動器的通道。 如果為服務器,則設定空閒處理為IdleHandler4Server,如果為客戶,則設定空閒處理為IdleHandle4Client。 流程如圖 2(為突出重點,我們忽略了一些細節): 圖 2 InitGUI流程 4.2 共享資源初始化 共享資源是客戶服務器模型中的重要元素,它由服務器負責創建和釋放,而提供所有客戶程序共享的數據資源。它的初始化過程由圖 3所示的調用流完成。 點擊查看大圖 圖 3 InitGUI調用流 如果是服務器,則初始化此結構,src/kernel/sharedres.c/LoadSharedResource()負責完成這一任務,它的執行流如圖 4所示: 圖 4 LoadSharedResource 流程 對於客戶,則只需要與此結構進行連接即可,它在src/kernel/sharedres.c/AttachSharedResource()實現,參見圖 5 圖 5 AttachSharedResource 流程 4.3 服務器客戶通信連接初始化 在客戶服務器模型的討論中,我們還將詳細的討論服務器客戶的通信機制,這裡只給出初始化的調用關系。見圖 6。 圖 6 通信連接的初始化 ServerStartUp實現流程如圖 7所示: 圖 7 ServerStartUp 流程 ClientStartUp()實現如圖 8所示: 圖 8 ClientStartUp流程 4.4 多進程模型 Lite版本是支持客戶服務器(C/S)方式的多進程系統,在運行過程中有且僅有一個服務器程序在運行,它的全局變量mgServer被設為TRUE,其余的MiniGUI應用程序為客戶,mgServer變量被設為FALSE。各個應用程序分別運行於各自不同的進程空間,如圖 9所示: 圖 9 多進程模型 目前的程序結構使每個加載的進程擁有一個自已的桌面模型及其消息隊列,進程間的通信依靠以下所提到的進程通信模型來完成。 4.5 進程通信模型 這裡我們所指的進程通信包括通過共享內存實現的數據交換和通過套接字實現的客戶服務器通信模型。先看在MiniGUI中使用Socket的通信模型結構,如圖 10: 點擊查看大圖 圖 10 基於Socket 的通信模型 下面再看看MiniGUI進程間的資源共享問題。見圖 11。 點擊查看大圖 圖 11 內存共享通信模型 如上圖所示,服務器負責裝入共享資源,其中包括系統圖標、位圖、字體等,客戶則通過AttachSharedResource()獲取指向共享資源的指針,初始化一塊共享內存及與使用已有共享內存的方法在前面的描述中已提到,在此不再贅述。 4.6 各進程之間的同步 這裡所指的進程同步主要是指各進程繪制的同步,顯然,同時不可能有兩個進程向屏幕繪制。傳統的GUI實現大多是只有一個進程負責繪制,而在我們Lite版本中,各進程負責自己的繪制。同時,我們的Lite 版本還支持虛屏切換,當我們切換出去的時候,誰也不能夠向屏幕繪制。 Lite 版本利用Unix 信號解決了繪制同步問題。系統定義了兩個信號:SIG_SETSCR 和 SIG_DNTDRAW,它們其實是重定義了的信號SIGUNUSED和 SIGSTKFLT。每個進程都定義了兩個變量dont_draw和cant_draw。 服務器利用SIG_SETSCR和SIG_DNTDRAW來控制各客戶程序誰有權對屏幕繪制,而不是自己全權代理。這也使得進程間通信量大大減少:當服務器希望一個客戶程序不要向屏幕繪制時,就向它發送SIG_DNTDRAW信號,當讓其繪制時,則發送SIG_SETSCR。從而實現了各進程間的屏幕繪制同步。