實時Linux進程間的通信

實時Linux進程間的通信

介紹完實時系統中的進程通信之後,將會介紹兩種用於進程通信的機制,分別是FIFOs和共享內存。另外對兩種機制進行比較,特別是在檢測新的寫入和數據一致性這一塊。然後會說明一下兩種機制在實時系統應用中的可能性。

進程通信已經成為實時系統要處理的一個重要問題,在分時系統中,進程通信是通過開銷很大的形式化結構來實現的,這對於應用來說會造成定時上的問題。

實時Linux中的應用被分為了兩個部分:實時和非實時。(這句話說的是實時系統中的應用被分為實時和非實時,而不是系統。)實時這部分,應該是體積小、簡單並且快速,它只包含與時間直接相關的代碼,並且被分配在內核地址空間中,還可以被高等級的特權執行。非實時這部分,被允許被分配在用戶空間,還能實現數據處理、存檔以及用戶接口這些功能。

這兩部分也需要一些有效的方法來實現相互的通信。接下來會介紹兩種有關進程通信的機制,分別是FIFOs和共享內存。

實時的FIFOs

實際上,實時的FIFOs是一個點對點的隊列,是用來傳輸數據的,它類似於UNIX FIFOs標準。它們的通信方式是單工通信,也就是說,它們只能沿著一個固定的方向來傳輸數據。所以,要建立兩個實時FIFOs才能實現全雙工通信模式,一個FIFO負責一個方向上的數據傳輸。但是這種點對點的通信管道,不能支持一個寫者,多個讀者的運行模式。

實時FIFOs被分配在內核空間中,實時任務的接口包括創建、註銷、讀寫,讀和寫是原子操作,也是不可阻塞的。在實時Linux中所有的實時任務都被分配在內核空間中的,所以我們使用了整數來查找其對應的實時FIFO,而不是文件描述符。(數字標記和文件標識符都是索引的作用,為什麼不能用文件標識符?因為在內核?)這些數字的聲明和賦值是在實時任務的初始化例程中完成的。實時任務有一個重要的特徵,當它讀取一個空的FIFO時,將會返回0個字節。也就是說,實時任務不會從沒有數據的FIFO中執行讀取操作。兩個實時任務可以通過FIFO來相互通信,也可以用來相互觸發。

對於非實時來說,實時FIFOs一般被當做字符設備,這個接口遵循了UNIX API中字符設備的標準:一個文件描述符標記著一個FIFO,然後打開這個FIFO,更多的操作是通過調用讀/寫函數來實現。當從用戶這邊讀取數據時,一般都是選擇系統調用去等待數據準備好,但是輪詢的方法更好一點。

在實時這邊,一個處理程序是和實時FIFO是相關的。當數據從用戶空間的進程寫入到實時FIFO時,處理程序將被執行。這個特徵適合控制實時任務的執行。當用戶進程向FIFO寫入命令信息,fifo處理程序立即被調用,它會處理信息並且相應的操作。實時和非實時這兩部分中關於實時FIFOs的接口布局,如圖1所示。

實時Linux進程間的通信

共享內存

在兩個進程之間需要交換大量的數據時,共享內存是一種最有效的進程通信機制。當內置的同步不被要求時,進程之間共享內存的模式就像線程之間共享變量一樣,但是它們仍有各自保護的地址空間。實時Linux中的實時應用和非實時應用被分配在不同的地址空間,這部分,我們的關注點在內核模式和用戶空間進程是如何共享內存的。

在實時Linux中,共享內存主要體現在物理存儲預留了一部分用於非實時和實時任務之間的共享。有兩種方法分配這些內存,第一種是分配在系統引導部分,第二種是分配給特殊的設備。如果使用第一種解決方法,也就是告訴系統需要預留一個存儲池用於段的存儲,所以在進程中不需要使用它,這種解決方法的主要問題是因為內存碎片的原因導致共享內存的大小是有限的。

第二種解決方法是使用存儲緩衝區(mbuff)模式和/dev/mbuff設備去匹配共享內存。例如存儲區在系統起始階段是不能更新的,更不能被內存碎片限制大小,它被分配在內核地址空間,並且存儲緩衝區提供了在用戶空間的存儲匹配功能。另外,存儲區是按照邏輯連續分配的,而不是按物理地址連續分配的。因為它不能交換出去,所以適合實時任務和用戶空間之間的通信,或者是一些用於內核和用戶之間交換數據的高速帶寬。

共享內存的主要特點是異步接收,它降低了死鎖的風險,因為一個進程不能阻塞另一個正在訪問公共區域的進程。另一個特有的改變是不需要把大數據結構再一次的寫入整個塊。因為沒有內置同步,所以要注意的是數據的一致性。

實時FIFOs和共享內存的比較

選擇實時FIFOs還是共享內存,要取決於應用自身的通信特點。在表1中展示了實時FIFOs和共享內存的主要特點。

實時Linux進程間的通信

兩種機制提供了一些優點,實時FIFOs提供了數據隊列,所以沒有協議可以預防寫入覆蓋,它們也同樣支持同步的阻塞,所以進程不需要對到達的數據進行輪詢。另外,共享內存需要握手協議來預防數據被覆蓋重寫,但是它沒有點對點的限制,也能夠被任意數量的用戶空間和實時進程共享。另外,它也提供快速的更新數據,甚至我們在一個大的數據結構中進入個別的項目時,也可以快速更新數據。在這部分中,我們也會考慮在應用設計中存在的共同問題:檢測新的寫入,數據的一致性,以及它們可能的解決方案。關於新的寫入的檢測,因為讀和寫要遵守UNIX標準協議,所以FIFOs要比共享內存好。在第二部分中,我們已經介紹了有關使用實時FIFOs來交換數據的協議,而進程也會使用read()和write()函數來完成這些操作。但是,如果使用共享內存的方法,則不需要這些函數,讀和寫的操作可以通過直接讀寫指針完成。所以,操作系統不需要檢測共享內存是否被更新,唯一的解決方法是建立明確的握手協議。

這樣做的方法之一是使得每一個新消息的消息標識符遞增。接收者對標識符進行調查,並且和前一個進行比較,如果它們不一樣,則接收到新的消息。當接收者讀取信息時,它必須在狀態結構中顯示消息標識符,從而使得發送者知道該消息已經被讀取。另一方面,只有前一個消息標識符在狀態結構中顯示後,發送方才能發送其他消息。這種解決方法假定了時間週期輪詢模型在用戶空間和內核中都成立。如果這不是應用設計模型,但是共享內存是,那麼這可能不是一個好的選擇。關於這個問題的解決方案將會在第5部分介紹。

如果我們只使用實時FIFOs,那麼在數據一致性上將不會存在問題,因為FIFOs能夠排隊,但是我們必須確保FIFOs的空間是足夠的大。當使用共享內存時,實時Linux設計會施加約束條件,因為Linux用戶進程在共享內存執行讀取或者是寫入的過程時,一些實時(內核)任務可能會打斷進程的執行。如果一個Linux進程在讀的過程中被打斷,這個進程在開始讓數據變得陳舊,在最後刷新數據。如果它在讀的過程中被打斷,這個進程在開始刷新數據,在最後使得數據變陳舊。這兩個問題一般都是致命的。

因為實時進程能夠打斷用戶空間進程,所以這個問題很容易發生。在用戶進程使用共享內存時,我們可以使用指定的共享標誌來實現進程之間的互斥。當實時任務進入共享內存時,它會時刻檢測標誌,當標誌位被置1時,進程將會延遲讀和寫的操作,直到該標誌位被置0。另外,如果下面的條件成立,可能會造成實時任務無限期的推遲:

-實時任務在用戶空間進程中的同一個週期運行(或在該週期的整數倍);

-因為訪問臨界區時是同步的,所以實時進程總是打斷Linux進程;

第一個條件意味著是實時代碼運行比較慢或者慢於用戶進程,第二個意味著Linux代碼運行確定性的實時編碼。在同一時間,兩者都不是真的,並且都是很少的。如果它發生了,連續的延期可以通過實時代碼檢測,並且可以通過觸發動作來到達保持控制系統的目的。

實時FIFOs和共享內存的應用

下面會通過兩個例子來介紹實時FIFOs和共享內存的應用。第一個例子實在工業控制中的應用,另外一個例子是介紹如何通過實時FIFOs來創建事件驅動實時任務。工業控制應用程序的組織如圖2所示。

實時Linux進程間的通信

共享內存被用於實時任務到用戶空間應用的大數據傳送。實時任務和內核線程之間的通信通過RS-485串行端口來實現,它們是週期運行的,週期時間為100ms。系統參數歸檔,事件歸檔(寫入磁盤),以及人類系統交互(我麼可以容忍一定的延遲,但是不要太長,避免打破濃度運算符)這三部分屬於用戶空間應用程序。

實時FIFOs的主要用途是發送命令到實時任務,當命令通過實時FIFOs發送給實時任務時,FIFO處理程序將會被立即調用,並且處理命令。這就是實時任務啟動和結束的過程,這種機制也可以用於改變一些內部的實時任務的參數,也可以使用不復雜的共享內存來存儲公共變量。實時FIFOs的另一種用途是表示實時任務中在共享存儲器的數據的是否準備好,從而觸發用戶空間線程來讀取共享內存。如果使用阻塞來實現同步,一個用戶空間線程可以阻塞,通過實時FIFO來等待來自實時任務的消息。當消息到來時,它將不阻塞,讀取共享內存中的數據,進程接收數據後再次阻塞,然後等待FIFO中可用的新數據。

前面實例中的實時任務都是週期性的,在有些情況下,只有當某些事件發生時,系統才適合去執行任務,這些實時任務是事件驅動,如果這是一箇中斷事件,它們被叫做中斷驅動實時任務。中斷驅動實時任務被中斷服務程序直接出發啟動,這裡不需要實時FIFOs幫助我們做任何事情。當發生一些事件的同時,如果我們想觸發一個任務,實時FIFOs將變的非常有用。一個用戶進程觸發一個事件驅動實時任務的數據流說明如圖3a所示。

實時Linux進程間的通信

用戶進程通過實時FIFO發送命令,命令是FIFO的處理程序,所以觸發實時任務去喚醒並做一些工作。如果我們有另一個實時任務而不是用戶進程,這種情況幾乎是相同的。在案例中,一個實時任務會觸發另一個。這適合在系統利用高來優先級處理罕見的事件,因為實施保證了它們能夠被迅速的處理。


分享到:


相關文章: