如果你認為本系列文章對你有所幫助,請大家有錢的捧個錢場,點選此處贊助,贊助額0.1元起步,多少随意
聲明:本文隻用于個人學習交流,若不慎造成侵權,請及時聯系我,立即予以改正
鋒影
email:[email protected]
1.Introduction
在QNX Neutrino中,微核心與程序管理器一起組成
procnto
子產品,所有運作時系統都需要這個子產品。
程序管理器可用于建立多個POSIX程序(每個程序可能包含多個POSIX線程),它的主要職責包括:
- 程序管理,管理程序的建立、銷毀、屬性處理(使用者ID群組ID)等;
- 記憶體管理,管理一系列的記憶體保護功能、共享庫、程序間POSIX共享記憶體等;
- 路徑名管理,管理資料總管可能附加到的路徑名空間;
使用者程序可以通過核心調用通路微核心函數,也可以通過向
procnto
發送消息來通路程序管理器函數。
在
procnto
中執行線程去調用微核心的方式與其他程序中的線程完全相同,程序管理器代碼和微核心共享相同的位址空間并不意味着有一套特殊的或私有的接口,系統中的所有線程共享相同的核心接口,并且在調用核心時執行特權切換。
2. Process management
procnto
的首要任務就是動态建立新程序,建立的程序也會依賴
procnto
提供的記憶體管理和路徑名管理相關功能。
程序管理包括程序建立、銷毀、屬性(程序ID、使用者ID、組ID)管理。包含以下接口:
-
,POSIX接口,通過直接指定要加載的可執行檔案來建立子程序。熟悉UNIX系統的人可能知道,這個函數相當于在posix_spawn()
之後調用fork()
,但是更高效,因為不需要在像exec*()
函數中那樣需要複制位址空間,而是在fork()
調用時直接銷毀和替代;exec*()
-
,QNX Neutrino接口,功能類似于spawn()
,使用這個接口可以控制程序的屬性資訊,比如檔案描述符、程序組ID、信号、排程政策、排程優先級、堆棧、運作掩碼(SMP系統);posix_spawn()
-
,POSIX接口,建立子程序,子程序與父程序共享相同的代碼,并複制父程序的資料。大多數的程序資源都是繼承的,不能繼承的資源包括:程序ID、父程序ID、檔案鎖、pending信号和alarms,定時器。fork()
函數可以在兩種情況下使用:fork()
- 建立目前執行環境的新執行個體,可用
替代;pthread_create()
- 建立一個運作不同程式的新程序,可用
來替代;posix_spawn()
-
,UNIX BSD擴充接口,vfork()
隻能在單線程程序中調用。vfork()
函數與vfork()
函數不同之處在于,它與父程序共享資料段,在調用fork()
或exec*()
函數之前資料都是共享的,調用exit()
或exec*()
函數之後父程序才能運作;exit()
-
,POSIX接口,exec*()
系列函數,會用可執行檔案加載的新程序,替換目前程序,由于調用程序被替換,是以不會有成功傳回。這些函數通常用在exec*()
或fork()
之後,用于加載子程序。更好的方式是使用vfork()
接口。posix_spawn()
3. Memory management
在某些實時核心中,會在開發環境中提供記憶體保護支援,卻很少為運作時配置提供記憶體保護,原因是記憶體和性能的損失。随着記憶體保護在很多嵌入式處理器中越來越普遍,記憶體保護的好處遠遠超過了它帶來的性能損失,最關鍵的一點就是提高了軟體的魯棒性。
記憶體保護對位址空間進行了隔離,避免了一個程序中的錯誤影響其他程序或核心。啟用MMU後,作業系統可以在發生記憶體通路沖突時中止程序,并立刻回報給程式員,而不是在運作一段時間後突然崩潰。
3.1 MMU
典型的MMU操作方式是将實體記憶體劃分為4KB頁面,在記憶體中會存儲一組頁表,頁表裡存放着虛拟位址到實體位址的映射關系,CPU根據頁表内容來通路實體記憶體。
![](/image///upload-images.jianshu.io/upload_images/4899042-d105d26a5dd3c74b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/838/format/webp)
Virtual address mapping(on an X86)
為了提高性能,通常會使用TLB來提高頁表條目的查找效率。頁表條目中會對頁面進行讀寫等權限控制。當CPU進行上下文切換的時候,如果是不同程序之間,則需要通過MMU來切換不同的位址空間,如果是在一個程序内部,則不需要這步操作。
3.2 Memory protection at run time
在許多嵌入式系統中,會使用硬體看門狗來檢測是否有軟體或硬體異常,在出現異常時則進行重新開機。
在記憶體保護系統中,有一種更好的方式,可以稱為軟體看門狗。當軟體出現間歇性錯誤時,作業系統可以捕獲事件,并将控制權交給使用者線程,而不是直接進行記憶體轉儲。使用者線程則可以有選擇的做決定,而不是像硬體看門狗那樣直接重新開機。軟體看門狗可以做:
- 中止由于記憶體通路沖突而失敗的程序,并在不關閉系統其餘部分的情況下重新開機該程序;
- 中止失敗程序以及任何相關程序,将硬體初始化為安全狀态,再重新開機相關程序;
- 如果故障很嚴重,則關閉整個系統,并發出警報;
很顯然,軟體看門狗能更好的進行控制,還可以收集有關軟體故障的資訊,有利于事後的診斷。
3.3 Quality control
通過将嵌入式系統劃分成一組協作的、受記憶體保護的程序,我們可以很容易重用這些元件。加上有明确的接口定義,這些程序可以放心的內建到應用程式中,確定它們不會破壞系統的整體可靠性。當然,應用程式不可能做到完全沒有bug,系統應該設計成能夠容忍并從故障中恢複的架構,而利用MMU提供記憶體保護正是朝着這個方向邁出了良好的一步。
3.4 Full-protection model
在全保護模型中,QNX Neutrino首先會将image中的所有代碼重定位到一個新的虛拟空間中,使能MMU,設定好初始頁表。這就允許
procnto
在支援MMU的環境中啟動,随後,程序管理器便會接管該環境,再根據啟動的程序來修改頁表。
Full protection VM
3.5 Locking memory
QNX Neutrino支援記憶體鎖定,程序可以通過鎖定記憶體來避免擷取記憶體頁的延遲。
記憶體鎖定分為以下幾級:
- Unlocked,未鎖定的記憶體可以換入換出,記憶體在映射時完成配置設定,但是不會建立頁表條目。當第一次通路記憶體時會失敗,記憶體管理器會進行記憶體的初始化并建立頁表條目,此時線程的狀态為
;WAITPAGE
- Locked,被鎖定的記憶體不能被換入換出,會在通路時發生頁面錯誤;
- Superlocked,QNX Neutrino的擴充實作,不允許任何錯誤,所有記憶體都必須初始化和私有化,并且在記憶體映射時設定權限,覆寫整個線程的位址空間;
3.6 Defragmenting physical memory
就像磁盤碎片一樣,程式的運作也有可能帶來記憶體碎片問題。
碎片整理的任務包括更改現有的記憶體配置設定和映射,以便使用不同的底層實體頁面。通過交換底層的實體記憶體單元,作業系統可以将碎片化空間合并成連續的區域,但是在移動某些類型的記憶體時需要小心,因為這類記憶體的映射表不能被安全的修改。
- 核心配置設定的一對一映射的記憶體區域不能移動,因為作業系統不能在不更改虛拟位址的情況下更改實體位址;
- 被應用程式鎖定(
)的記憶體不能移動;mlock()/mlockall()
- 具有IO特權的應用程式預設會鎖定所有頁面,因為裝置驅動通常需要實體位址;
- 目前沒有移動具有互斥鎖對象的記憶體頁,互斥鎖對象通過實體位址向核心注冊,如果移動帶有互斥鎖對象的頁面,則需要重新編寫這些對象。
4. Pathname management
procnto
允許資料總管通過提供标準的API接口,管理路徑名空間子集作為自己的“授權域”。當一個程序打開一個檔案時,相容POSIX的open庫函數會向
procnto
發送路徑名消息,
procnto
會根據路徑的字首來判斷由哪一個資料總管來處理。當一個字首被重疊注冊時,會使用與最長的字首關聯的資料總管來處理。
啟動時,
procnto
會建立以下路徑名:
4.1 Resolving pathnames
可以舉個例子來說明一下最長路徑名比對,假設有以下路徑名進行了注冊,并有對應的子產品:
下表展示了路徑名解析的最長比對規則:
4.2 Single-device mountpoints
假設有三個伺服器:
- 伺服器A,QNX 4檔案系統,挂載點是
,包含兩個檔案/
和bin/true
;bin/false
- 伺服器B,Flash檔案系統,挂載點是
,包含檔案/bin/
和ls
;echo
- 伺服器C,産生數字的裝置,挂載點是
/dev/random
;
在程序管理器内部,挂載點清單如下:
[轉]QNX程式管理器-程式排程政策1.Introduction2. Process management3. Memory management4. Pathname management
假設一個用戶端想往伺服器C發送消息,用戶端的代碼如下:
int fd;
fd = open("/dev/random", ...);
read(fd, ...);
close(fd);
在這種情況下,C庫代碼會請求程序管理器提供處理路徑
/dev/random
的伺服器,程序管理器将傳回伺服器清單:
- 伺服器C(可能性最大,最長路徑比對)
-
伺服器A(可能性最小,最短路徑比對)
根據這些資訊,C庫将以此與每個伺服器進行聯系,并發送
的消息和路徑元件,而伺服器會對路徑元件進行驗證:open
- 伺服器C收到空路徑,因為請求與挂載在同一個路徑下;
- 伺服器A收到路徑
,因為它的挂載點是dev/random
/
;
一旦一個伺服器确認了請求,C庫就不會聯系其他伺服器,這意味着隻有伺服器C拒絕請求時,才會去聯系伺服器A。
4.3 Unioned filesystem mountpoints
假設有兩個伺服器:
- 伺服器A, QNX 4檔案系統,挂載點是
,包含兩個檔案/
和bin/true
;bin/false
- 伺服器B, Flash檔案系統,挂載點是
,包含兩個檔案/bin
和ls
echo
;
兩個伺服器都有
路徑,但是包含不同的内容,當兩個伺服器都挂載後,可以看到聯合挂載點如下:/bin
-
,伺服器A;/
-
,伺服器A和B;/bin
-
,伺服器B;/bin/echo
-
,伺服器A;/bin/false
-
,伺服器B;/bin/ls
-
/bin/true
,伺服器A;
當執行以下代碼,路徑解析跟之前一樣,但是不是将傳回限制成一個連接配接ID,而是去聯系所有的伺服器并詢問它們對路徑的處理。
DIR *dirp;
dirp = opendir("/bin", ...);
closedir(dirp);
結果為:
- 伺服器B将收到一個空路徑,因為請求與挂載在同一個路徑下;
- 伺服器A将收到
,因為挂載點是bin
/bin
;
結論是,對于處理路徑
的伺服器(本例中A和B),我們有一組檔案描述符,當調用/bin
時,可以依次讀取實際的目錄名條目。如果目錄中的任何名稱都是通過正常的readdir()
通路,那就會執行正常的解析過程,并且隻能通路一個伺服器。open
4.4 Symbolic prefixes
類似于Linux系統中的連結,在QNX中,可以通過
ln -s
來建立連結,比如:
ln -Ps /net/neutron/bin /bin
此時,
/bin/ls
會被替換成
/net/neutron/bin/ls
,在進行路徑名比對時,會比對到
/net
上,而
/net
指向的是
lsm-qnet
,
lsm-qnet
資料總管會去解析
neutron
元件,并将進一步的解析請求發送到叫
neutron
的網絡節點上,進而在
neutron
節點上完成
/bin/ls
的解析。符号連結允許我們像通路本地檔案系統一樣通路遠端檔案系統。
執行重定向不需要運作本地檔案系統,無磁盤工作站的路徑名組織可能如下圖,本地裝置(如
;dev/ser1
或
/dev/console
)将被路由到本地字元裝置管理器,而對其他路徑的請求将會被路由到遠端檔案系統。
4.5 File descriptor namesapce
檔案描述符空間屬于程序内部資源,資料總管通過使用
SCOID
(Server Connection ID)和
FD
(File Descriptor)來辨別
OCB
(open control block),其中在IO管理器中,使用稀疏矩陣完成三者之間的映射:
Sparse array
OCB
結構包含了打開資源的活動資訊,下圖中表示一個程序打開檔案兩次,另一個程序打開同一檔案一次:
Two processes open the same file
也可以多個不同程序之間的描述符指向同一個
OCB
結構,通常是在
dup()/dup2()/fcntl()
或者
vfork()/fork()/posix_spawn()/spawn()
接口調用時産生的,此時一個程序對
OCB
操作可能會影響與之關聯的程序,如下圖: