QNX system architecture 6 - Process manager

進程管理器能夠創建多個POSIX進程,每個進程可以包含多個POSIX線程。

在QNX Neutrino RTOS,procnto系統進程包含microkernel, 進程管理模塊,內存管理模塊和路徑管理模塊。因此進程管理模塊並不是微內核的一部分。

  • 進程管理 - 管理進程創建,銷燬和進程屬性比如uid和gid
  • 內存管理 - 管理一定範圍的內存保護能力,共享庫,以及進程間的POSIX共享內存原語。
  • 路徑管理 - 管理路徑空間

用戶進程通過系統調用直接訪問微內核,通過發送消息給procnto訪問進程管理。注意用戶進程通過MsgSend*()內核調用發送一個message。

注意,在procnto中調用微內核和在其他進程中是一樣的。進程管理代碼和微內核共享同一個地址空間,並不意味着它有特定的或者私有的接口。系統中所有的線程都使用一致的內核接口並且都會在調用內核接口時發生特權切換.

Process management

procnto的第一個職責是動態創建新進程。這些進程將會依賴procnto的其他功能:內存管理和路徑管理。

進程管理包含了進程創建和進程銷燬,以及進程屬性的管理:比如進程ID,進程組ID和用戶ID。

Process primitives


Process loading

使用exec*() posix_spawn或者spawn從文件系統加載進程。

如果文件系統存放在塊設備上,代碼和數據被加載到主存中。缺省情況下,包含二進制代碼的內存頁是按需加載的,但是你可以使用procnto -m選項改變它;更多的信息,參見本章中Locking memory小結

如果文件系統是內存映射的,比如ROM/flash image,那麼代碼不需要被裝載到RAM中,而是在存儲介質中直接執行。這種方法是在RAM中爲數據和棧分配內存,代碼則還是在ROM或者flash上。

不管代碼以何種形式存放,如果相同的進程被加載多次,代碼是共享的。

Memory management

某些實時內核在開發環境中提供了內存保護支持。隨着內存保護在嵌入式處理器上變得越來越普及,引入內存管理所帶來的性能損失越來越微不足道。

在嵌入式應用中特別是關鍵任務系統中增加內存保護的最大好處:是改善了系統健壯性。

通過內存保護,如果在多任務系統中一個進程試圖非法訪問內存,MMU硬件可以通知OS,然後系統會abort線程。

這防止進程間內存地址空間的濫用,防止一個進程錯誤的代碼破壞另外一個進程甚至OS的內存。這個保護對於和集成實時系統是非常重要的,因爲這使得事後分析稱爲可能。

在開發階段,通常代碼錯誤(比如野指針和數組越界)可能會導致一個進程或者線程破壞另外一個進程的數據空間。如果覆蓋的內存在短時間內沒有被使用,錯誤會變得更難跟蹤,可能會花費數小時複雜的調試手段,比如使用電路模擬器或者邏輯分析儀,來發現犯罪方。

通過使能MMU,OS可以忽略掉進程非法內存訪問的企圖,並且立刻向程序員提供反饋,避免系統在隨後某個時間神祕的崩潰。OS可以提供非法訪問的指令位置,甚至非法指令的調試符號。

Memory Management Units(MMUs)

一個典型MMU操作,劃分物理內存爲4-KB的page。處理器硬件使用保存在系統內存中的頁表定義虛擬地址到CPU物理地址的映射。

當線程執行時,OS管理的頁表決定線程內的邏輯地址如何映射到處理器的物理內存。


Figure 32: Virtual address mapping(on an x86)

如果系統內線程和進程很多,地址空間很大,需要描述這個映射的頁表項數目變得非常可觀,已經無法保存在處理器中。爲了保證系統性能,處理器會caches經常使用的頁表項到TLB中。

TLB cache可能會導致cache misses,OS要儘量避免由此帶來的性能損失。

頁表項中定義了內存中page的屬性。Pages可以是隻讀,讀寫等等。典型的,可執行進程通常把代碼頁標記爲只讀,data和statck爲可讀寫。

當OS執行上下文切換時(比如一個進程執行了掛起操作,恢復了另外一個進程執行)。這將操作MMU爲新進程使用一組不同的頁表。如果OS是在一個進程內的兩個線程之間切換,那麼MMU刷新是不必要的,因爲同一進程的兩個線程共享相同的地址空間。

當新進程恢復執行,新進程的任何地址轉換都是通過新的頁表生成。如果線程試圖訪問一個未映射的地址,或者試圖訪問一個地址但又不遵守頁面訪問權限,那麼CPU會收到一個fault錯誤(類似一個除0錯),OS實現一個特殊類型的中斷。

通過檢查壓入中斷棧中的指令地址,OS可以判斷出引發fault的指令地址,並作進一步處理。

Memory protection at run time

在開發階段內存保護是有用的,它可以提供嵌入式系統的可靠性。許多嵌入式系統已經採用了硬件watchdog 來檢測軟件或者硬件是否已經失控,但是這個辦法和MMU相比,缺少精確性。

硬件watchdog通常實現爲一個可重新觸發的定時器,如果系統軟件沒有定期的復位定時器,定時器超時將會引發處理器reset。典型的,系統軟件部件將檢查系統完整性,並且更新時鐘指示系統工作良好。

儘管這個方法可以把系統從軟件或者硬件的錯誤中恢復過來,但是由於整個系統重啓,因此導致重啓時系統有一段時間不可用。

Software watchdog

在內存保護系統中,當一個間歇性軟件錯誤發生後,OS可以捕捉到這個時間,並且把控制權轉給一個用戶線程而不是內存dump機制。這個線程可以進一步判斷如何從失敗中恢復過來,而不是簡單粗暴的reset系統。軟件watchdog可以:

  • 中止觸發內存訪問失敗的進程並重啓這個進程,而不是關閉系統的其餘部分。
  • 中止失敗的進程和相關的進程,初始化硬件到一個安全的狀態,然後重新啓動失敗進程和相關進程。
  • 如果失敗非常關鍵,那麼關閉整個系統,併發出聲音警告。

這裏最重要的區別是我們保留智能的,可編程的控制,即便控制軟件的個別進程和線程由於某些原因失敗。硬件watchdog仍然可以用來恢復系統。

當我們在執行某種恢復策略時,系統可以收集軟件失敗的各種信息。比如,如果嵌入式系統包含或者訪問了mass storage,軟件watchdog可以生成按事件排序的dump files。我們可以使用這個dump文件來做時候診斷。

嵌入式系統通常使用這種部分重啓方法來處理間歇性軟件失效,而不會讓用戶體驗到系統宕機,甚至注意到這些快速恢復軟件失效。因爲dump files是可用的,軟件開發者可以檢測和發現軟件問題,而不需要隨時緊急的到達現場。如果我們比較這個方法和硬件watchdog方法,明顯我們傾向與前者。事後dump file分析對於任務緊急的嵌入式系統是非常重要的。不論何時緊急系統失敗,需要盡力發現失敗的根源以便可以修復它並且應用到其他系統上。

dump files包含了程序員修復系統所需要的信息,沒有dump files,程序員並不比碰到系統crashed的用戶直到更多。

Quality control

Full-protection model

我們的全保護模式重定位鏡像中的所有代碼到新的虛擬空間,使能MMU硬件並重置頁表映射。這允許procnto啓動一個正確的MMU-enabled環境。進程管理器然後接收這個環境,改變進程需要的頁表映射。

Private virtual memory

在全保護模式下,每個進程都給定了自己的虛擬地址空間,一般來說2 ~ 3.5GB,進程切換和消息傳遞的性能代價受到兩個私有地址空間進行地址空間切換複雜性的影響。


Figure 33: Full protection VM(on an x86)

每個進程花費在page table上的內存可能增加4KB~8KB。注意這個內存模型支持POSIX fork()調用。

Variable page size

虛擬內存管理器可以使用可變page尺寸,前提是處理器支持這個特性。

使用可變page size可以改善性能:

  • 可以增加page size的尺寸,大於4KB。由此係統可以使用更少的TLB項
  • TLB錯失會變少

如果你想關閉可變page size功能,可以在procnto的buildfile中編輯-m~v選項,-mv選項則是使能可變page尺寸。

Locking memory

QNX支持POSIX內存鎖定,所以一個進程可以避免取頁延遲,通過鎖定memory對應的page,所謂鎖定,就是分配,並且不準交換到交換分區中。

鎖定分爲如下幾種級別

  • Unlocked

不鎖定的內存可以換入換出。內存雖然被分配了,但是page table項並沒有創建。第一次對內存的訪問可能會失敗,線程在WAITPAGE狀態等待直到內存管理器初始化內存並創建頁表項。

  • locked

鎖定的內存不可以換入或者換出。儘管在訪問或者引用時仍然會發生page faults,來維護使用和修改統計。用戶認爲的PROT_WRITE頁面可能仍是PROT_READ。這樣,在第一次對MAP_PRIVATE頁面執行寫操作時,內核會收到警告:MAP_PRIVATE頁面現在必須要私有化。

lock或者unlock一個線程的內存區域,可以調用mlock()和munlock();lock或者unlock線程所有內存區,可以使用mlockall()和munlockall()。內存保持鎖定直到進程unlocks,進程退出,或者調用exec*()函數。如果進程調用fork(),posix_spawn*()或者spawn*()函數,在子進程中內存鎖會被釋放。

多個進程可以lock相同的內存區,內存會保持lock直到所有的進程都unlock它。內存鎖不支持stack,如果一個進程lock同一段區域多次,那麼unlock一次即可以取消該進程之前所有的lock操作。

lock所有應用的所有內存,procnto使用-ml選項。因此所有的pages至少被初始化。

  • superlocked

不允許有faulting發生,所有的內存在映射時都被初始化,privatized以及設置權限。supoerlocking會覆蓋線程的整個地址空間。

supoerlock所有應用的所有內存,procnto標識-mL選項。

對於MAP_LAZY映射,上面類型的內存在第一次引用之前並不會被分配。一旦被引用後,纔會遵守以上規則。如果在critical代碼區(比如禁止中斷或者在ISR中),使用了從未引用的MAP_LAZY 區域,那麼編程者要對此負責。

Defragmenting physical memory

大部分計算機用戶都很熟悉disk隨便整理的概念,隨着時間的推移,磁盤空閒空間被分割爲許多小塊,散佈在已使用塊之間。物理內存分配和釋放存在類似的問題,隨着時間的推移,系統物理內存也會漸漸碎片化。最終,儘管空閒物理內存的總量非常大,但是由於碎片化,導致請求分配一塊連續物理內存失敗。

通常需要使用DMA的設備驅動會需要連續物理內存。一種解決辦法是確保所有驅動都儘可能早的初始化(碎片發生前),並且獲取所需的內存。這是個很不好的限制,特別是對於嵌入式系統可能只是根據用戶動作啓動相應驅動;同時啓動所有可能的驅動,看起來相當的不靈活,而且浪費資源。

QNX使用一套分配和回收算法來顯著的減少內存碎片的發生。然後,無論算法多麼精妙,特定的行爲仍然會導致內存碎片化。














發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章