?? minigui體系結構之一 體系結構概覽.htm
字號:
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/linux/embed/minigui/minigui-4/index.shtml#resources"><FONT
face=helvetica,helv,arial size=-2>參考資料</FONT></A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/linux/embed/minigui/minigui-4/index.shtml#author"><FONT
face=helvetica,helv,arial size=-2>關于作者</FONT></A></TD></TR>
<TR>
<TD bgColor=#000000><FONT face=helvetica,helv,arial size=-3><IMG
height=3 alt="" src="MiniGUI體系結構之一 體系結構概覽.files/c.gif"
width=137></FONT></TD></TR></TBODY></TABLE><BR><BR><!-- End Table of Contents --><!-- comments #6: html content of the paper -->
<P><EM>為了幫助更多軟件開發(fā)人員理解 MiniGUI及其編程,同時幫助更多的自由軟件開發(fā)人員加入 MiniGUI
的開發(fā),我們將撰寫一系列文章介紹 MiniGUI 的體系結構。本文是系列文章的第一篇,將在整體上對 MiniGUI
的體系結構作一介紹。其中主要包括:線程的基本概念;基于 POSIX Thread
的微客戶/服務器結構;用來同步微客戶/服務器動作的關鍵數(shù)據(jù)結構――消息隊列;面向對象技術在 MiniGUI 中的應用等等。最后,文章展望了我們計劃在
MiniGUI 2.0 版開發(fā)中采用的體系結構。</EM></P><A id=1 name=1></A>
<P><STRONG class=subhead>1 引言</STRONG></P>
<P>到目前為止,MiniGUI 的最新發(fā)布版本是 0.9.96。我們將 0.9.xx 系列版本定位為 MiniGUI 1.0 版本的預覽版。在
0.9.xx 版本足夠穩(wěn)定時,我們將發(fā)布 MiniGUI 1.0 版本,同時,目前的代碼不會再進行重大調整。在 MiniGUI 1.0
版本發(fā)布之后,我們將立即著手開發(fā) MiniGUI 2.0 版本。該版本預期將在體系結構上進行重大調整。為了吸引更多的自由軟件程序員加入
MiniGUI 2.0 的開發(fā),也為了更好地幫助 MiniGUI 程序員進行程序開發(fā),我們將撰寫一系列的文章介紹 MiniGUI 1.0
版本的體系結構,重點分析其中的一些缺點以及需要在 2.0 版本當中進行優(yōu)化和改造的地方。介紹體系結構的文章計劃如下:</P>
<OL>
<LI>體系結構概覽(本文)。將在整體上對 MiniGUI 1.0
的體系結構進行介紹。重點包括:線程的基本概念;多線程的微客戶/服務器體系、多線程通訊的關鍵數(shù)據(jù)結構――消息隊列;面向對象技術在 MiniGUI
中的應用等等。
<LI>MiniGUI 的多窗口管理。將介紹 MiniGUI 的多窗口機制以及相關的窗口類技術。其中涉及到窗口剪切處理和 Z
序,消息傳遞,控件類設計和輸入法模塊設計等等。
<LI>MiniGUI 的圖形設備管理。重點介紹 MiniGUI
是如何處理窗口繪制的。其中主要包括圖形上下文的概念,坐標映射,圖形上下文的局部、全局和有效剪切域的概念等等。
<LI>圖形抽象層和輸入抽象層。圖形抽象層(GAL)和輸入抽象層(IAL)大大提高了 MiniGUI
的可移植性,并將底層圖形設備和上層接口分離開來。這里將重點介紹 MiniGUI 的 GAL 和 IAL 接口,并以 EP7211
等嵌入式系統(tǒng)為例,說明如何將 MiniGUI 移植到新的嵌入式平臺上。
<LI>多字體和多字符集支持。MiniGUI
采用邏輯字體實現(xiàn)多字體和多字符集處理。這一技術成功應用了面向對象技術,通過單一的邏輯接口,可以實現(xiàn)對各種字符集以及各種字體的支持。
</LI></OL><A id=2 name=2></A>
<P><STRONG class=subhead>2 POSIX 線程</STRONG></P>
<P>MiniGUI 是一個基于線程的窗口系統(tǒng)。為了理解 MiniGUI 的體系結構,我們有必要首先對線程作一番了解。</P><STRONG>2.1
什么是線程</STRONG>
<P>線程通常被定義為一個進程中代碼的不同執(zhí)行路線。也就是說,一個進程中,可以有多個不同的代碼路線在同時執(zhí)行。例如,常見的字處理程序中,主線程處理用戶輸入,而其他并行運行的線程在必要時可在后臺保存用戶的文檔。我們也可以說線程是“輕量級進程”。在
Linux 中,每個進程由五個基本的部分組成:代碼、數(shù)據(jù)、棧、文件I/O
和信號表。因此,系統(tǒng)對進程的處理要花費更多的開支,尤其在進行進程調度和任務切換時。從這個意義上,我們可以將一般的進程理解為重量級進程。在重量級進程之間,如果需要共享信息,一般只能采用管道或者共享內存的方式實現(xiàn)。如果重量級進程通過
fork() 派生了子進程,則父子進程之間只有代碼是共享的。</P>
<P>而我們這里提到的線程,則通過共享一些基本部分而減輕了部分系統(tǒng)開支。通過共享這些基本組成部分,可以大大提高任務切換效率,同時數(shù)據(jù)的共享也不再困難――因為幾乎所有的東西都可以共享。</P>
<P>從實現(xiàn)方式上劃分,線程有兩種類型:“用戶級線程”和“內核級線程”。</P>
<P>用戶線程指不需要內核支持而在用戶程序中實現(xiàn)的線程,這種線程甚至在象 DOS 這樣的操作系統(tǒng)中也可實現(xiàn),但線程的調度需要用戶程序完成,這有些類似
Windows 3.x
的協(xié)作式多任務。另外一種則需要內核的參與,由內核完成線程的調度。這兩種模型各有其好處和缺點。用戶線程不需要額外的內核開支,但是當一個線程因 I/O
而處于等待狀態(tài)時,整個進程就會被調度程序切換為等待狀態(tài),其他線程得不到運行的機會;而內核線程則沒有各個限制,但卻占用了更多的系統(tǒng)開支。</P>
<P>Linux 支持內核級的多線程,同時,也可以從 Internet 上下載一些 Linux 上的用戶級的線程庫。Linux
的內核線程和其他操作系統(tǒng)的內核實現(xiàn)不同,前者更好一些。大多數(shù)操作系統(tǒng)單獨定義線程,從而增加了內核和調度程序的復雜性;而 Linux
則將線程定義為“執(zhí)行上下文”,它實際只是進程的另外一個執(zhí)行上下文而已。這樣,Linux
內核只需區(qū)分進程,只需要一個進程/線程數(shù)組,而調度程序仍然是進程的調度程序。Linux 的 clone
系統(tǒng)調用可用來建立新的線程。</P><STRONG>2.2 POSIX 線程</STRONG>
<P>POSIX 標準定義了線程操作的 C 語言接口。我們可以將 POSIX 線程的接口劃分如下:</P>
<UL>
<LI>線程的建立和銷毀。用來創(chuàng)建線程,取消線程,制造線程取消點等等。
<LI>互斥量操作接口。提供基本的共享對象互斥訪問機制。
<LI>信號量操作接口。提供基本的基于信號量的同步機制。不能與 System V IPC 機制的信號量相混淆。
<LI>條件量操作接口。提供基本的基于條件量的同步機制。盡管信號量和條件量均可以劃分為同步機制,但條件量比信號量更為靈活一些,比如可以進行廣播,設置等待超時等等。但條件量的操作比較復雜。
<LI>信號操作接口。處理線程間的信號發(fā)送和線程信號掩碼。
<LI>其他。包括線程局部存儲、一次性函數(shù)等等。 </LI></UL>
<P>目前,Linux 上兼容 POSIX 的線程庫稱為 LinuxThreads,它已經(jīng)作為 glibc 的一部分而發(fā)布。這些函數(shù)的名稱均以
pthread_ 開頭(信號量操作函數(shù)以 sem_ 開頭)。</P>
<P>為了對線程有一些感性認識,我們在這里舉兩個例子。</P>
<P>第一個例子在進入 main () 函數(shù)之后,調用 pthread_create 函數(shù)建立了另一個線程。pthread_create
的參數(shù)主要有兩個,一個是新線程的入口函數(shù)(thread_entry),另一個是傳遞給入口函數(shù)的參數(shù)(data),而新線程的標識符通過引用參數(shù)返回(new_thread)。見清單
1。</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>清單 1 新線程的創(chuàng)建
void* thread_entry (void* data)
{
... // do something.
return NULL;
}
int main (void)
{
pthread_t new_thread;
int data = 2;
pthread_create (&new_thread, NULL, thread_entry, &data);
pthread_join (new_thread, NULL);
}
</PRE></TD></TR></TBODY></TABLE>
<P>main () 函數(shù)在建立了新線程之后,調用 pthread_join 函數(shù)等待新線程執(zhí)行結束。pthread_join 類似進程級的
wait 系統(tǒng)調用。當所等待的線程執(zhí)行結束之后,該函數(shù)返回。利用 pthread_join
可用來實現(xiàn)一些簡單的線程同步。注意在上面的例子中,我們忽略了函數(shù)調用返回值的錯誤檢查。</P>
<P>第二個例子是利用信號量進行同步的兩個線程。這里所使用的例子利用信號量解決了經(jīng)典的“生產者/消費者”問題(清單
2)。我們首先解釋信號量的基本概念。</P>
<P>信號量的概念由 E. W. Dijkstra 于 1965
年首次提出。信號量實際是一個整數(shù),進程(也可以是線程)在信號量上的操作分兩種,一種稱為 DOWN,而另外一種稱為 UP。DOWN
操作的結果是讓信號量的值減 1,UP 操作的結果是讓信號量的值加 1。在進行實際的操作之前,進程首先檢查信號量的當前值,如果當前值大于
0,則可以執(zhí)行 DOWN 操作,否則進程休眠,等待其他進程在該信號量上的 UP 操作,因為其他進程的 UP 操作將讓信號量的值增加,從而它的
DOWN
操作可以成功完成。某信號量在經(jīng)過某個進程的成功操作之后,其他休眠在該信號量上的進程就有可能成功完成自己的操作,這時,系統(tǒng)負責檢查休眠進程是否可以完成自己的操作。</P>
<P>為了理解信號量,我們想象某機票定購系統(tǒng)。最初旅客在定票時,一般有足夠的票數(shù)可以滿足定票量。當剩余的機票數(shù)為
1,而某個旅客現(xiàn)在需要定兩張票時,就無法滿足該顧客的需求,這時售票小姐讓這個旅客留下他的電話號碼,如果其他人退票,就可以優(yōu)先讓這個旅客定票。如果最終有人退票,則售票小姐打電話通知上述要定兩張票的旅客,這時,該旅客就能夠定到自己的票。</P>
<P>我們可以將旅客看成是進程,而定票可看成是信號量上的 DOWN 操作,退票可看成是信號量上的 UP
操作,而信號量的初始值為機票總數(shù),售票小姐則相當于操作系統(tǒng)的信號量管理器,由她(操作系統(tǒng))決定旅客(進程)能不能完成操作,并且在新的條件成熟時,負責通知(喚醒)登記的(休眠的)旅客(進程)。</P>
<P>在操作系統(tǒng)中,信號量的最簡單形式是一個整數(shù),多個進程可檢查并設置信號量的值。這種檢查并設置操作是不可被中斷的,也稱為“原子”操作。檢查并設置操作的結果是信號量的當前值和設置值相加的結果,該設置值可以是正值,也可以是負值。根據(jù)檢查和設置操作的結果,進行操作的進程可能會進入休眠狀態(tài),而當其他進程完成自己的檢查并設置操作后,由系統(tǒng)檢查前一個休眠進程是否可以在新信號量值的條件下完成相應的檢查和設置操作。這樣,通過信號量,就可以協(xié)調多個進程的操作。</P>
<P>信號量可用來實現(xiàn)所謂的“關鍵段”。關鍵段指同一時刻只能有一個進程執(zhí)行其中代碼的代碼段。也可用信號量解決經(jīng)典的“生產者/消費者”問題,“生產者/消費者”問題和上述的定票問題類似。這一問題可以描述如下:</P>
<P>兩個進程共享一個公共的、固定大小的緩沖區(qū)。其中的一個進程,即生產者,向緩沖區(qū)放入信息,另外一個進程,即消費者,從緩沖區(qū)中取走信息(該問題也可以一般化為
m 個生產者和 n
個消費者)。當生產者向緩沖區(qū)放入信息時,如果緩沖區(qū)是滿的,則生產者進入休眠,而當消費者從緩沖區(qū)中拿走信息后,可喚醒生產者;當消費者從緩沖區(qū)中取信息時,如果緩沖區(qū)為空,則消費者進入休眠,而當生產者向緩沖區(qū)寫入信息后,可喚醒消費者。</P>
<P>清單 2 中的例子實際是“生產者/消費者”問題的線程版本。</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>清單 2 利用信號量解決“生產者/消費者”問題
/* The classic producer-consumer example, implemented with semaphores.
All integers between 0 and 9999 should be printed exactly twice,
once to the right of the arrow and once to the left. */
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 16
/* Circular buffer of integers. */
struct prodcons {
int buffer[BUFFER_SIZE]; /* 實際數(shù)據(jù) */
int readpos, writepos; /* 讀取和寫入的位置 */
sem_t sem_read; /* 可讀取的元素個數(shù) */
sem_t sem_write; /* 可寫入的空位個數(shù) */
};
/* 初始化緩沖區(qū) */
void init(struct prodcons * b)
{
sem_init(&b->sem_write, 0, BUFFER_SIZE - 1);
sem_init(&b->sem_read, 0, 0);
b->readpos = 0;
b->writepos = 0;
}
/* 在緩沖區(qū)中保存一個整數(shù) */
void put(struct prodcons * b, int data)
{
/* Wait until buffer is not full */
sem_wait(&b->sem_write);
/* Write the data and advance write pointer */
b->buffer[b->writepos] = data;
b->writepos++;
if (b->writepos >= BUFFER_SIZE) b->writepos = 0;
/* Signal that the buffer contains one more element for reading */
sem_post(&b->sem_read);
}
/* 從緩沖區(qū)讀取并刪除數(shù)據(jù) */
int get(struct prodcons * b)
{
int data;
/* Wait until buffer is not empty */
sem_wait(&b->sem_read);
/* Read the data and advance read pointer */
data = b->buffer[b->readpos];
b->readpos++;
if (b->readpos >= BUFFER_SIZE) b->readpos = 0;
/* Signal that the buffer has now one more location for writing */
sem_post(&b->sem_write);
return data;
}
/* 測試程序: 一個線程插入 1 到 10000 的整數(shù),另一個線程讀取并打印。*/
#define OVER (-1)
struct prodcons buffer;
void * producer(void * data)
{
int n;
for (n = 0; n < 10000; n++) {
printf("%d --->\n", n);
put(&buffer, n);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -