衛洪春

摘 要: 為了實現線程間消息的有效傳遞,在探討進程和線程的概念、進程和線程的區別、同步和異步處理機制、同步訪問技術、Linux下的軟中斷等技術的基礎上,提出一種實現線程間有效傳遞消息的方法。該方法可以傳遞任意類型、大小和格式的消息,并且完全由實現者決定和控制,具有較好的實用價值。
關鍵詞: 進程; 線程; 異步處理; 同步訪問技術; 消息傳遞
中圖分類號: TN911?34; TP391 文獻標識碼: A 文章編號: 1004?373X(2015)14?0074?04
0 引 言
基于Linux操作系統的嵌入式開發和應用已非常廣泛,在嵌入式系統或子系統中大部分應用都采用單個CPU,應用層軟件開發大多使用多線程開發技術。在同一個功能模塊中使用多個線程來實現各個子功能模塊,線程間的通信就不可避免。本文研究了一種在同一進程中的2個不同線程間消息傳遞的實現方法。該方法的優點是實現了消息的透明傳遞。傳遞消息與其傳遞過程無關。在這種方法中,待傳遞消息的具體內容、構成形式、信息量等都不受限制,可根據實際需要來構建消息。這就能實現信息與載體的完全無關性和分離。利用該方法,待傳遞的消息可以是字符或整形數據,也可以是滿足實際需要的復雜構造型數據,攜帶的具體信息數據等都不受限制,可根據實際需要進行定義和構造。
1 進程、線程與信號量
進程是具有一定獨立功能的程序在一定的內存地址空間中,屬于這個程序的數據集合上的一次運行活動,它具有產生、運行、死亡的動態變化的活動過程。在進程產生時,獲得了地址空間的系統資源才能運行,這就需要系統給予它所需的資源分配,所以進程是系統進行資源分配的一個獨立單位,也是用于組織和管理系統資源的單位。進程將相關的資源組織在一起,這些資源包括:內存地址空間、程序、數據等。將這些資源以進程的形式組織起來,可以使操作系統更容易管理這些資源。
線程是進程的一個執行實體,它必須屬于某一個進程。線程是CPU調度和分派的基本單位,也是進程中能獨立運行的基本單位。線程不獨占處理CPU之外的系統資源,它與同屬一個進程的其他線程共享進程所擁有的這些資源,但只擁有一個線程的進程情況除外。線程基本上只獨占在它運行時必不可少的資源(如程序計數器、一組寄存器和棧),但是由于線程比進程更小,擁有的系統資源更少,基本上不擁有系統資源。在線程間切換時,對運行的上下文處理工作就變得更少,故對它的調度所付出的開銷就小得多,能更高效地提高系統內多個任務間并發執行的程度[1?5]。
1.1 進程與線程的主要區別
進程和線程存在以下幾個方面的主要區別。
(1) 地址空間和其他資源:進程獨占系統分配給它的地址空間,它有自己的數據空間、代碼和數據。不同進程間的這些資源是隔離的,并受到系統的保護,不允許一個進程直接訪問2個進程的資源。若需要則通過進程間的通信來實現。而同屬于一個進程的多個線程則共享該系統分配給該進程的地址空間、代碼和數據,每個線程都可以直接訪問這些共享的資源。線程間不可共享的資源是它自己的堆棧、程序計數器及其執行的上下文。CPU資源無論對于進程還是線程都不是獨占的,而是共享的。進程不能直接參與CPU資源的分配和調度,而線程是CPU資源分配和調度的單位,可以直接競爭分配和調度。進程必須依賴它的線程參與到CPU資源的競爭分配和調度中,從而獲得CPU資源。多線程主要是為了減少在CPU調度切換時消耗的時間,充分發揮CPU的效率。
(2) 通信:由于進程的地址空間、代碼空間和數據都受到保護,相互之間都是透明的。所以要想在進程間進行信息的傳遞通信,就需要使用IPC來實現。屬于一個進程的不同線程間的通信相當容易,直接讀/寫進程數據段(如全局變量)就可實現通信,通信變簡單了,但容易引起通信數據的不完整或不一致性的問題。這就需要在進行線程間通信時,保證通信時的原子操作。
(3) 調度和切換:進程進行上下文切換時,需要保存切換出去進程的地址空間、代碼空間、數據、當前堆棧和程序計數器等資源信息,還需恢復已調度進程的資源信息。而在同一個進程間的不同線程的上下文切換時,僅需要保存切換出去的線程的堆棧和程序計數器,恢復調度過的線程的相關資源。因線程不需要維護和管理數據空間和運行空間,在進行切換時,需要保存和恢復的內容就比進程少得多,所以線程上下文切換比進程上下文切換要快得多,線程更能提高CPU的利用率。
1.2 同步與異步處理
同步處理,就是共同實現完成某一特定功能的多個進程或線程間存在嚴格的運行次序,且運行的結果及返回具有依賴性。這些進程或線程之間是相互配合、協助與合作的關系。在這一特定功能的協作尚未完成之前,是不會響應其他的請求。
異步處理,就是多個進程或線程間不存在運行次序問題和依賴關系。他們的運行不需要相互協同和配合,而是各自獨立運行。在這種處理方式下,就會產生同步訪問的問題。
同步訪問過程所面臨的問題是:共享數據或代碼段被多個線程并發或同時訪問,會出現共享數據的不完整性和不一致性。導致功能錯誤或無法實現功能,或引起異常甚至系統崩潰。保證線程對共享數據的原子操作,以達到時刻維護數據完成性和一致性的目的,就是使用同步訪問技術。
1.3 同步訪問技術
(1) 臨界區:是指一段不可重入的代碼,對于一個線程必須對臨界區執行原子操作,即要么不執行臨界區的代碼,要么在執行臨界區代碼時一次性執行完成且中間不得被其他線程執行。在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖訪問臨界區,那么在有一個線程進入后,其他試圖訪問公共資源的線程將被掛起,并一直等到進入臨界區的線程離開,臨界區在被釋放后,其他線程才可以搶占。
(2) 互斥量:采用互斥對象機制。只有擁有互斥對象的線程才有訪問公共資源的權限,因為互斥對象只有一個,所以能保證公共資源不會同時被多個線程訪問。互斥不僅能實現同一應用程序的公共資源安全共享,還能實現不同應用程序的公共資源安全共享。
(3) 信號量:其是一個互斥的資源,在任何時刻只能有一個線程擁有它。當信號量被一個線程持有后,其他線程就獲取不到該信號量的持有權,而被掛起等待,一直等待持有這個信號量的線程將他釋放出來,再進行競爭這個信號量的持有權。信號量保證了在某一時間段內只有一個線程訪問臨界區,而其他需要訪問的線程進行等待。保證了線程對臨界區的原子操作。
(4) 事件:通過通知的方式來保持線程的同步,可以方便地實現對多個線程的異步處理操作。
1.4 Linux下的軟中斷信號signal
軟中斷信號(signal,簡稱為信號)用來通知一個線程,有其他線程向它發送了異步事件,需要它進行適當的處理。信號只是用來通知某線程發生了什么事件,并不會給該線程傳遞任何有關該事件的數據,線程對收到的各種信號如何處理,由該線程自己決定。不過在Linux的進程中處理方法通常有3種:第1種是類似中斷的處理程序,對于需要處理的信號,進程可以指定處理函數,由該函數來處理;第2種是忽略某個信號,對該信號不做任何處理,就象未發生過一樣;第3種是對該信號的處理保留系統的默認值,這種缺省操作對大部分的信號而言是進程終止。進程通過系統調用signal來指定進程對某個信號的處理行為。在進程表的表項中有一個軟中斷信號域,該域的每位對應一個信號。當有信號發送給進程時,對應位置位。由此可以看出,進程對不同的信號可以同時保留,但對于同一個信號,進程在處理之前并不知道來了多少個[6?10]。
1.5 不可靠信號與可靠信號
不可靠信號:當向一個進程發送singal時,當進程還沒有處理該信號(此時被稱作pending,未決信號)或正在調用信號處理函數時,進程又收到了一個同樣的信號,kernel會把第2個信號丟棄,或者與一個信號合并,只對這個信號處理1次,而不是收到這個信號多少次就處理多少次。不可靠的信號是指1~31的信號,這些都是從Unix系統中保留下來的。
可靠信號是指不論信號是否相同,都不會丟棄或合并,信號進行排隊處理,信號不丟失,收到多少次就會處理多少次。SIGRTMIN~SIGRTMAX都是可靠信號[11]。
2 線程間消息實現方法
消息傳遞方法實現原理:使用與1個進程中的2個線程間的消息和參數傳遞,它由2部分組成,一部分是把待發送的信息內容放到指定的共享內存空間中,然后使用Linux下的signal信號向接收線程發異步信號;另一部分是接收線程收到信號后,到指定的所屬進程內存空間獲取具體的消息內容,從而實現線程間的異步通信,且可以傳遞較多的數據和參數。由于在線程間使用共享的內存空間用于存放傳遞消息的內容;所以在存放和提取消息內容時,需要使用前述的線程間同步與互斥機制,保證消息內容在傳遞過程中不被破壞和保持數據的完整性。在線程間實現消息通告方式采用Linux的signal方式,即軟件中斷方式。它是異步事件,由操作系統的內核實現。采用異步通告方式,是為了充分利用系統資源,特別是CPU資源。
2.1 信號的選擇
在上述論述中已經論述了信號分為不可靠信號和可靠信號2種。為了保證消息被安全、可靠地傳遞給對方,就必須選擇可靠信號。由于大部分可靠信號都有專門的用途,并且在這種消息實現中,所起的作用僅是異步方式通知對方有消息來了,并不指特定的消息。具體是什么消息,可通過獲取到的消息的內容獲知,選擇用于應用程序的SIGUSER1即可。傳遞具體消息的內容、格式和大小,在資源夠用的前提下,沒有任何限制,有極大的靈活性,只要保證消息封裝和消息提取保持一致即可[12]。
2.2 消息存放的位置
消息存放的位置有2種處理方式,固定位置和動態位置。
固定位置方式:發送和接收消息的線程都知道消息存放的地址,發送消息的線程就將具體封裝好的消息放到這個固定的內存空間中;接收線程收到信號后,就到這個固定的內存空間來獲取消息。
動態位置方式:由發送消息的線程在發送消息之前確定消息內容存放在共享內存空間位置,將消息內容存放在這個空間之后,向接收線程發送帶有消息位置參數信號。接收線程在收到信號時提取出信息的位置參數,然后到該位置獲取消息的內容。Linux下的signal在進程間是不能帶有參數的,但在線程間可以帶有一個整型參數。在這里所帶的整型參數是一個地址,即線程共享內存中的地址。線程都可以訪問共享內存空間,所以可以使用。
3 消息傳遞過程
在圖1所示的消息傳遞示意圖中,進程P中包含4個線程,實現線程4向線程1消息傳遞。線程4是消息的發送者,線程1是消息的接收者。首先,線程4將欲傳遞的消息內容沿著①的方向放在進程p的內存空間的地址a處;然后,線程4沿著②的方向向線程1發放signal信號,通知線程1,有消息發送給它,通知他去共享內存空間的地址a處獲取消息;最后,線程1收到信號后,到地址a處提取消息,然后進行消息處理,完成了一次消息傳遞過程。
圖1 線程間消息傳遞示意圖
這種消息傳遞方法是滿足線程1和線程4間異步并發的運行,線程4在向線程1發送消息時,不需要知道線程1的當前運行狀態,只在它需要向線程1發消息的任何時刻發送即可。使得傳遞的消息的類型和所帶參數個數以及格式等都與消息傳遞過程無關,只與系統資源的限制有關。這也對2個線程進行相互隔離,屏蔽了消息的產生和消息處理的具體過程。
4 結 語
在用戶空間的應用程序中,一個進程由多個線程來實現一個功能模塊中的不同子模塊的功能,實現各個子功能模塊的線程間即相互獨立,又相互關聯。在他們之間,都處于同一個進程內部,進行通信是不可避免的。使用消息方法既能保證線程間的異步并發和同步協作,又能充分利用系統的資源。在實際應用中,該方法能有效降低線程間的耦合性,明確各個線程的職責和范圍[13?17],降低軟件實現的復雜度,具有較強的現實作用和意義,為線程間進行消息交互提供了較好的參考方法和思路。
參考文獻
[1] 眭俊華,劉慧娜,王建鑫,等.多核多線程技術綜述[J].計算機應用,2013,33(1):239?242.
[2] 駱斌,費翔林.多線程技術的研究與應用[J].計算機研究與發展,2000,37(4):407?412.
[3] 閆偉,葉建栲.多線程技術在Android 手機開發中的應用[J].信息通信,2012(1):46?46.
[4] 施惠豐,袁道華.基于多核的多線程程序優化研究[J].計算機技術與發展,2010,20(6):70?73.
[5] TANENBAUM A S.現代操作系統[M].陳向群,馬洪兵,譯.北京:機械工業出版社,2009.
[6] 毛德操,胡希明.Linux內核源代碼情景分析[M].杭州:浙江大學出版社,2001.
[7] STEVENS W R,RAGO Stephen A.Unix環境高級編程[M].尤晉元,張亞英,戚正偉,譯.北京:人民郵電出版社,2006.
[8] 王文義,武華北.Linux中進程間信號通信機制的分析及其應用[J].計算機工程與應用,2005(3):108?109.
[9] 屈志強,喬靜,胡珊珊.Linux 信號在進程控制中的應用[J].計算機與現代化,2010(6):150?152.
[10] 鄭尚志,趙小龍,昌杰.Linux信號機制的分析與研究[J].科技資訊,2008(11):98?100.
[11] 陳毅東,李堂秋,鄭旭玲.Linux不可靠信號與可靠信號的比較實驗[J].廈門大學學報:自然科學版,2002,41(6):720?725.
[12] 冉鵬,顏紀迅.分時分區操作系統互斥信號量的設計與分析[J].計算機技術與發展,2013,23(1):43?46.
[13] 劉文峰,李程遠,李善平.嵌入式Linux操作系統的研究[J].浙江大學學報:工學版,2004,38(4):448?452.
[14] MCCONNELL Steve.代碼大全[M].金戈,湯凌,陳碩,等譯.北京:電子工業出版社,2006.
[15] 肖竟華,陳嵐.Linux 內存管理實現的分析與研究[J].計算機技術與發展,2007,17(2):187?189.
[16] 王兆文,蔣澤軍,陳進朝.一種提高Linux 內存管理實時性的設計方案[J].計算機工程,2014,40(9):291?294.
[17] 段哲.嵌入式系統通信機制的研究與應用[J].艦船電子工程,2010,30(7):81?83.