段 哲
(中國船舶重工集團公司第七二二研究所 武漢 430079)
在計算機和信息技術(shù)高速發(fā)展的今天,計算機和計算機技術(shù)大量地應(yīng)用在我們的日常生活中,廣泛應(yīng)用的嵌入式系統(tǒng)便是其中的一種。在PC市場已趨于穩(wěn)定的今天,嵌入式系統(tǒng)的發(fā)展速度正在加快,嵌入式系統(tǒng)不僅廣泛應(yīng)用于工業(yè)、交通、通信、科研、醫(yī)療衛(wèi)生、等日常生活等領(lǐng)域,而且很多應(yīng)用于各種航天、軍工與醫(yī)療等高安全的工作需求的工作環(huán)境中,系統(tǒng)出現(xiàn)問題可能帶來巨大的經(jīng)濟損失,甚至危及人的生命。另一方面由于航天、軍工與醫(yī)療等工作環(huán)境中的特殊性,要求的設(shè)備必須滿足一些環(huán)境或者電磁兼容或者可靠性的要求。嵌入式Linux系統(tǒng)具有高實時性和高可靠性的特點,因而越來越被廣泛地應(yīng)用在上述領(lǐng)域。
在應(yīng)用中,Linux系統(tǒng)下的通信無疑是值得研究的重點和難點。本文比較了在Linux系統(tǒng)下進程之間相互通信的幾種IPC(InterProcess Communication)技術(shù),同時結(jié)合C語言的一些語言特性,針對采用Linux幀緩沖設(shè)備的嵌入式系統(tǒng)給出了一個有效的通信機制。該實現(xiàn)對含有需要實時處理的多個功能模塊進行分時控制,在犧牲系統(tǒng)資源的基礎(chǔ)上,保證了系統(tǒng)的高實時性和高可靠性,從而滿足了各種航天、軍工與醫(yī)療等高安全的工作需求的工作環(huán)境的要求。
把從一個進程連接到另一個進程的一個數(shù)據(jù)流稱為管道,它是UNIX系統(tǒng)IPC的最古老形式,并且所有UNIX系統(tǒng)都提供此種通信機制。管道有下面的特點:
1)半雙工:即數(shù)據(jù)只能在一個方向上流動,需要通信時,需要建立起兩個管道;
2)只能在具有公共祖先的進程之間使用,即只能用于父子進程或者兄弟進程之間;
3)管道對于管道兩端的進程而言,就是一個文件但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨構(gòu)成一種文件系統(tǒng),并且只存在于內(nèi)存中。
信號是為了使進程獲得某項重要的通知而發(fā)送給它的重要事件。這時進程必須立即停止當前的工作,轉(zhuǎn)而處理該信號。每一個信號都用一個整數(shù)代表信號的類型。這些信號定義在系統(tǒng)文件/usr/include/asm/signal.h中,我們在日常使用Linux的過程中經(jīng)常接觸到信號操作,比如當某個程序正在運行時為了終止程序的運行按下Ctrl-C鍵,或使用Kill命令把該進程殺掉,實際上都是使用了信號作進程間通信。當系統(tǒng)捕獲了某信號時,就會響應(yīng)該信號指定的動作,系統(tǒng)才對它進行處理,沒有發(fā)出信號的進程就處于等待狀態(tài)。
信號是軟件層次上對中斷機制的一種模擬,在實際應(yīng)用中,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。通常來說信號的生命周期分為以下四個階段:
1)信號產(chǎn)生:信號事件的發(fā)生主要有兩個來源:硬件來源和軟件來源;
2)信號注冊:信號在進程中注冊指的是使進程知道需要處理某個信號;
3)信號注銷:信號在進程中注銷指進程等待處理某個信號,且該信號沒有被進程阻塞,則在運行相應(yīng)的信號處理函數(shù)前,進程把信號從未決信號鏈中卸載;
4)信號處理:進程注銷信號后,立即執(zhí)行相應(yīng)的信號處理函數(shù),執(zhí)行完畢后,信號的本次發(fā)送對進程的影響徹底結(jié)束。
管道應(yīng)用的一個重大限制是它沒有名字,因此,它只能用于具有親緣關(guān)系的進程間通信;FIFO不同于管道之處在于它提供一個路徑名與之關(guān)聯(lián),以FIFO的文件形式存在于文件系統(tǒng)中,這樣,即使與FIFO的創(chuàng)建不存在親緣關(guān)系的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信。值得提出的是FIFO嚴格遵循先進先出(First In First Out),對管道及FIFO的讀總是從開始處返回數(shù)據(jù)。
共享內(nèi)存可以說是最有效的進程間通信方式。最顯而易見的好處就是效率高,進程可以直接讀寫內(nèi)存,而不需要任何數(shù)據(jù)的復(fù)制。對于管道和消息隊列等通信方式,需要在內(nèi)核和用戶空間進行四次數(shù)據(jù)復(fù)制,而共享內(nèi)存只復(fù)制兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。Linux從2.2內(nèi)核開始支持多種共享內(nèi)存方式,如mmap()系統(tǒng)調(diào)用等。
消息隊列就是一個消息的鏈表??梢园严⒖醋鲆粋€記錄,具有特定的格式以及特定的優(yōu)先級,對消息隊列有寫權(quán)限的進程可以向其中按照一定的規(guī)則添加新消息。對消息隊列有讀權(quán)限的進程則可以從消息隊列中讀走消息。
由于嵌入式系統(tǒng)自身的優(yōu)勢,有多種通信方式,結(jié)合實際項目的需要,所以提出了本文的設(shè)計方案。
傳統(tǒng)的設(shè)計機制是基于圖1所示的各功能模塊的系統(tǒng)組成結(jié)構(gòu)。該系統(tǒng)包括了顯示模塊、串口模塊、網(wǎng)絡(luò)模塊、鍵盤模塊、數(shù)據(jù)處理模塊,以及主控制模塊6個模塊。其他類型的嵌入式系統(tǒng)都可在此基礎(chǔ)上進行縮減或擴展,其基本的軟件設(shè)計思路是完全相同的。系統(tǒng)的一致性為研究和開發(fā)統(tǒng)一的軟件實現(xiàn)機制提供了廣闊的應(yīng)用空間。
在很多嵌入式系統(tǒng)設(shè)計中,通常是在主函數(shù)Main(*argv,*argn)中,創(chuàng)建多個線程,進行數(shù)據(jù)的交互,從而滿足實時多任務(wù)的處理。雖然我們可以使用互斥量來解決線程之間互相破壞的問題,但當一個互斥量已經(jīng)被別的線程鎖定后,如果一直沒有被解鎖,等待它的線程將一直被掛著,程序就陷入死鎖狀態(tài),這時,所有線程都因等待互斥量而被掛起,它們中任何一個都不可能恢復(fù)運行,程序無法繼續(xù)運行下去。這樣的設(shè)計模式會導(dǎo)致各模塊的錯綜復(fù)雜的糾纏,使系統(tǒng)各功能模塊具有極高的耦合性。一旦某個線程出現(xiàn)問題,就會導(dǎo)致系統(tǒng)癱瘓,甚至死機現(xiàn)象,造成不可估量的后果。
由于很多嵌入式系統(tǒng)應(yīng)用于各種航天設(shè)備、軍工設(shè)備等高安全需求的環(huán)境中,系統(tǒng)出現(xiàn)問題可能帶來巨大的經(jīng)濟損失,甚至危及人的生命;而一個不合理的實現(xiàn)機制很難甚至不可能保證其行為,尤其是現(xiàn)在的系統(tǒng)越來越復(fù)雜,實現(xiàn)的功能越來越多,包含的模塊越來越多,該問題就越來越嚴重。只有系統(tǒng)在一個合理的實現(xiàn)機制下運行,才能保證系統(tǒng)的可靠性與穩(wěn)定性。
在進程間通信采用信號、消息機制,可以很好地處理具有多模塊功能的嵌入式系統(tǒng)的實時要求,多任務(wù)需求,從而滿足更復(fù)雜功能的設(shè)備的需求。
系統(tǒng)采用中央集中控制策略,主控制模塊執(zhí)行各種決策控制(參見圖1),主動向外圍設(shè)備(子模塊)寫信息,而采用信號(軟中斷)的方式接收外圍設(shè)備(子模塊)發(fā)送的信息。在主控制模塊與外圍設(shè)備(子模塊)之間存在如圖2所示的數(shù)據(jù)交互界面。
圖2 主控制模塊與外圍設(shè)備的數(shù)據(jù)交互界面
各模塊通過進程間通信即消息、信號機制,完成設(shè)備所需的功能。
其中主控制模塊設(shè)定為父進程,其它模塊為子進程。利用fork()函數(shù)可以創(chuàng)建新的子進程。利用Linux多進程地址空間的獨立性,使中央控制模塊與各外圍設(shè)備控制模塊相互隔離,以免相互影響。但是在實現(xiàn)時必須注意多進程的同步問題,解決這個問題的辦法是在程序中設(shè)置信號量,允許進程通過檢測和設(shè)置它的值來實現(xiàn)同步,保證在此期間其他進程不能進行類似的操作。通過Linux系統(tǒng)的信號機制,給中央控制模塊與各外圍設(shè)備控制模塊提供實時通信,提高CPU的處理效率。通過Linux系統(tǒng)的消息機制,給中央控制模塊與各外圍設(shè)備控制模塊提供數(shù)據(jù)通信。通過消息隊列可靠地傳遞各模塊發(fā)送或接收的數(shù)據(jù)。
新機制采用的多進程分配空間各自獨立,空間消耗上比多線程大,但與整個系統(tǒng)的高安全性和高可靠性相比,我們可以在系統(tǒng)資源允許的情況下,以犧牲系統(tǒng)資源為代價,來滿足各種航天設(shè)備、軍工設(shè)備等高安全需求的環(huán)境要求。所以本文提出的方案是適宜的。
為了更好的說明問題,在本例中我們打開兩個子進程,進行多進程操作與進程間通信演示,軟件框圖如圖3所示。
圖3 多進程通信框圖
if(pid1==0)
{
int times=0;
if(signal(SIGPATOCH1,sig_usr)==SIG_USR)
{
printf("can'tcatch SIGPATOCH1");
return;
}
for(;;)
{
sleep(3);
times++;
memset(buf,0,100);
sprintf(buf,"msgtype1,msginfo%d",times);
msgwrite(ctop_queueid,buf,strlen(buf),1);
kill(getppid(),SIGCH1TOPA);
}
}
子進程2完成功能:每隔5秒向父進程發(fā)一個消息,然后發(fā)信號SIGCH2TOPA,父進程收到該信號后讀取該消息隊列,實現(xiàn)代碼如下:
if(pid2==0)
{
int times=0;
for(;;)
{
sleep(5);
times++;
memset(buf,0,100);
sprintf(buf,"msg type 2,msginfo%d",times);
msgwrite(ctop_queueid,buf,strlen(buf),2);
kill(getppid(),SIGCH1TOPA);
}
}
父進程實例代碼:
if(signo==SIGCH1TOPA)
{
memset(buf,0,100);
sprintf(buf,"%s",ch_to_par_msg.mtext);
strcat(buf,tempbuf);
msgwrite(ptoc_queueid,buf,strlen(buf),1);
//取消SIGPATOCH1信號的發(fā)送,
kill(pidsub1,SIGPATOCH1);
}
else if(signo==SIGCH2TOPA)
{
memset(buf,0,100);
sprintf(buf,"%s",ch_to_par_msg.mtext);
strcat(buf,tempbuf);
msgwrite(ptoc_queueid,buf,strlen(buf),1);
//取消SIGPATOCH1信號的發(fā)送,
kill(pidsub2,SIGPATOCH2);
}
}
本文利用嵌入式系統(tǒng)提供的進程通信機制:信號、消息隊列,給出了一種適合于經(jīng)典嵌入式系統(tǒng)的系統(tǒng)實現(xiàn),并給出了一些簡單范例代碼;該機制在系統(tǒng)資源允許的情況下,以犧牲系統(tǒng)資源為代價來保障系統(tǒng)的高安全性和高可靠性,從而滿足各種航天設(shè)備、軍工設(shè)備等高安全需求的環(huán)境中要求,并在實際應(yīng)用中取得了良好的實現(xiàn)效果。
[1]W.RICHARD STEVENS BILL FENNER.UNIX網(wǎng)絡(luò)編程[M].楊繼張,譯.北京:清華大學(xué)出版社,2006,1
[2][美]W.Richard Stevens Stephen A.Rago.UNIX環(huán)境高級編程[M].第二版.尤晉元,張亞英,戚正偉,譯.北京:人民郵電出版社,2006,5
[3]于明儉,陳向陽,方漢.Linux程序設(shè)計權(quán)威指南[M].北京:機械工業(yè)出版社,2001,4
[4]楊水清,張劍,施云飛,等.ARM嵌入式系統(tǒng)開發(fā)技術(shù)詳解[M].北京:電子工業(yè)出版社,2008,11
[5][美]K.Wall,M.Watson,M.Whitis,et al.GNU/Linux編程指南[M].王勇,王一川,林花軍,等譯.北京:清華大學(xué)出版社,2000,7