[轉]QNX_Neutrino微內核

如果你認爲本系列文章對你有所幫助,請大家有錢的捧個錢場,點擊此處贊助,贊助額0.1元起步,多少隨意

聲明:本文只用於個人學習交流,若不慎造成侵權,請及時聯繫我,立即予以改正

鋒影

email:[email protected]

 

介紹

QNX Neutrino微內核procnto實現了嵌入式實時系統中常用的核心POSIX功能,並提供基本的消息傳遞服務。而未實現的POSIX功能(比如文件、設備IO)則可以通過可選的進程和共享庫來提供。
微內核包含了一些基本對象以及操作這些對象的例程,這些對象定義得很具體,而且高度可重用,整個操作系統在此之上構建的。

QNX微內核

 

系統服務

QNX Neutrino微內核提供了一系列系統調用來支持以下服務:

  • 線程
  • 消息傳遞
  • 信號
  • 時鐘
  • 定時器
  • 中斷處理
  • 信號量
  • 互斥鎖
  • 條件變量
  • 屏障
    整個系統都是基於這些系統調用來構建的,QNX完全可搶佔,甚至在消息傳遞的過程時也能被搶佔,並在搶佔完成後恢復之前的消息傳遞狀態。
    微內核實現越簡單,越有利於減少不可搶佔區間的長度,同時,代碼量少,讓解決複雜的多處理器問題也變得簡單。將系統服務包含進內核的前提是,系統服務只有一個短的執行路徑長度。需要執行很多工作的操作,可以交給外部的進程或線程去做。
    嚴格按照上邊的規則來劃分內核和外部進程功能的話,微內核的運行時負載不見得就高於單內核。簡單內核的上下文切換的時間非常快,相比於在進程間通過消息傳遞來服務請求的時間,上下文的切換開銷微不足道。
    下圖演示了在非對稱多處理器內核(X86實現)搶佔的細節,其中,中斷禁用或禁止搶佔的時間非常短,通常爲幾百納秒。

     

    QNX Neutrino搶佔

線程和進程

在開發應用程序時(實時、嵌入式、圖形等),通常會用到POSIX線程模型來實現多個算法同時執行。線程是微內核中最小的執行和調度單元,進程可以認爲是線程的“容器”,定義了線程將在其中執行的“地址空間”,進程會包含一個或多個線程。
應用程序中的線程有可能相互獨立,也可能緊密的聯繫,QNX Neutrino提供了豐富的IPC和同步服務。
其中不涉及微內核線程調用的POSIX接口有如下:

 

下表中的POSIX接口,微內核中有對應的接口實現一樣的功能,允許自己來選擇:

 

線程屬性

儘管進程中的線程共享進程地址空間中的所有內容,但每個線程仍然有一些“私有”數據,在某些情況下,這些私有數據在內核中受到保護,比如線程ID/進程ID;而其他的可能不受保護,比如線程的堆棧。
值得注意的私有數據有:

  • tid,線程ID,每個線程在進程中都有唯一的ID,從1開始;
  • priority,每個線程都有一個調度的優先級,線程的初始優先級是繼承而來,並且可以根據調度策略進行改變,進程沒有優先級;
  • name,線程名字,可以通過pthread_getname_np()pthread_setname_np()來獲取和設置;
  • Register set,每個線程都有IP、SP以及處理器相關的寄存器上下文;
  • Stack,線程在自己的堆棧上執行,存儲在其進程的地址空間中;
  • Signal mask,信號掩碼;
  • Thread local storage,線程本地存儲TLS,用於存儲線程私有的數據,用戶不需要直接訪問TLS,線程可以通過線程特定的key與用戶自定義數據進行綁定,用到的接口有pthread_key_create(), pthread_key_delete(), pthread_setspecific(), pthread_getspecfic().其中線程對應的key和線程ID是通過稀疏矩陣來映射的。
  • Cancellation handlers,線程終止時執行的回調函數;

線程生命週期

線程是動態創建的,創建時涉及到資源分配和初始化,銷燬時涉及到資源回收,當線程執行時,它的狀態通常描述爲“就緒”或“阻塞”,具體來說有以下狀態:

 

線程狀態

  • CONDVAR,阻塞在條件變量上,比如調用pthread_cond_wait()
  • DEAD,線程終止了,等待被其他線程join;
  • INTERRUPT,阻塞在等待中斷上,比如調用InterruptWait()
  • JOIN,線程阻塞在join另一個線程,比如調用pthread_join()
  • MUTEX,線程阻塞在互斥鎖上,比如調用pthread_mutex_lock()
  • NANOSLEEP,線程休眠很短的時間,比如調用nanosleep()
  • NET_REPLY,線程正在等待通過網絡傳遞迴復,比如調用MsgReply*()
  • NET_SEND,線程正在等待通過網絡發送脈衝或信號,比如調用MsgSendPulse()MsgDeliverEvent(),SignalKill()`等;
  • READY,線程等待執行,此時處理器可能正在執行同級或更高優先級的線程;
  • RECEIVE,線程阻塞在接收消息上,比如調用MsgReceive()
  • REPLY,線程阻塞在消息回覆上,比如調用MsgSend()
  • RUNNING,線程正在執行,內核會使用一個數組(每個CPU上有一個入口)來跟蹤記錄所有運行的線程;
  • SEM,線程正在等待信號量的釋放,比如調用SyncSemWait()
  • SEND,線程阻塞在信息發送上,比如調用MsgSend(),但服務器還沒收到消息;
  • SIGSUSPEND,線程阻塞在等待一個信號上,比如調用sigsuspend()
  • SIGWAITINFO,線程阻塞在等待一個信號之上,比如調用sigwaitinfo()
  • STACK,線程正在等待虛擬堆棧地址空間分配,父進程調用ThreadCreate()
  • STOPPED,線程阻塞在等待SIGCONT信號;
  • WAITCTX,線程在等待非整數上下文變得可用,比如浮點運算;
  • WAITPAGE,線程等待爲虛擬地址分配物理地址;
  • WAITTHREAD,線程等待子線程完成自我創建,比如調用ThreadCreate()

線程調度

當執行內核調用、異常、硬件中斷時,當前的執行線程會被掛起,每當任何線程的執行狀態發生改變時,都會做出調度決策。通常被掛起的線程將會被恢復,這時線程調度器將進行一次上下文切換。
有三種情況會發生上下文切換:

  • 阻塞,當線程需要等待某些事件的發生時(比如響應IPC請求、等待互斥鎖等),就會阻塞等待。線程被阻塞時,會從運行隊列中移除,解阻塞時會移動到同優先級就緒隊列的尾部中。
  • 搶佔,高優先級線程會搶佔低優先線程;
  • 主動讓出CPU,比如調用sched_yield()等;

調度優先級

每個線程都會分配一個優先級,QNX Neutrino支持256級優先級,non-root線程可以將優先級設置爲1-63,與調度策略無關,root線程(有效uid爲0),能將優先級設置爲63之上。通常會採用優先級繼承來應對優先級反轉的問題。
下圖中描述了一個就緒隊列中,B-F是就緒,G-Z是阻塞,A正在運行:

 

就緒隊列

調度策略

QNX Neutrino支持三種調度策略,這個也跟Nuttx系統一樣:

  1. FIFO調度
    在FIFO調度下,線程會在兩種情況下放棄執行:1)主動放棄CPU;2)高優先級線程搶佔;

     

    FIFO調度

  2. Round-Robin調度
    在Round-Robin調度下,線程會在三種情況下放棄執行:1)主動放棄CPU;2)高優先級線程搶佔;3)時間片消耗完畢;

     

    Round-Robin調度

     

    時間片爲4倍時鐘週期。與FIFO調度不同的是多了一個時間片的控制。

  3. Sporadic調度
    Sporadic調度策略通常用於在給定時間段內提供線程執行時間的上限,Sporadic調度會爲線程執行提供“預算”。與FIFO調度一樣,在阻塞或被搶佔的情況下會放棄執行。Sporadic調度會自動降低線程的優先級,可以更精確的控制線程的行爲。
    Sporadic調度時,線程優先級會在前臺正常優先級N和後臺低優先級L之間動態調整,通過使用下列參數控制調度條件:

  • Initial budget(C),線程從正常優先級調整到低優先級前,允許的執行時間;
  • Low priority(L),線程降到的優先級,線程在後臺以L優先級運行,在前臺以N優先級運行;
  • Replenishment period(T),允許線程消耗執行預算的時間段,對於Replenishment操作,POSIX實現時採用這個值作爲線程變爲Ready狀態的時間段。
  • Max number of pending replenishment,Replenishment最大次數,決定了Sporadic調度策略的最大系統負載上限。
    下圖所示,Sporadic調度策略建立了線程的初始化執行budget(預算),線程執行時會消耗這個budget,但這個值會週期性重複填滿。

     

    Sporadic調度

在正常優先級N時,線程會執行budget時間C,當時間耗盡後,線程的優先級會調整至L。當Replenishment發生後又將恢復到原來的優先級,在一個T的時間週期內,線程將會有機會最大去執行C的運行時間,也能保證一個線程在N優先級的情況下只消耗C/T比例的系統資源。假設在一個系統中,線程不會被阻塞或搶佔,運行情況如下圖所示:

 

image.png

關於優先級和調度策略的設置,有以下接口來實現:

  • sched_getparam()/SchedGet()
  • sched_setparam()/SchedSet()
  • sched_getscheduler()/SchedGet()
  • sched_setscheduler()/SchedSet()

同步服務

QNX Neutrino提供POSIX標準線程級別的同步原語:

 

 

上述同步機制中,大部分都是由內核直接實現,除了以下幾種:

  • 屏障、睡眠鎖、讀寫鎖,這些是基於條件變量和互斥鎖實現的;
  • 原子操作,由處理器提供,或者在內核中模擬實現;
  1. Mutex
    互斥鎖是最簡單的同步服務,用於對臨界區的互斥訪問,通常會用phtread_mutext_lock()/pthread_mutex_timedlock()來獲取鎖,使用phread_mutext_unlock()來釋放鎖,當獲取不到鎖的時候線程會阻塞等待,也可以使用非阻塞函數pthread_mutex_trylock()來測試Mutex是否已經被鎖。

  2. Condvars
    條件變量用於在臨界區來阻塞線程,直到滿足某些條件,這些條件可以是任意複雜的,並且與Condvar無關。Condvar必須始終與Mutex一起使用。
    條件變量支持三種操作:

  • 等待,pthread_cond_wait()
  • 發出信號,pthread_cond_signal()
  • 廣播,pthread_cond_broadcast()

3.Barriers
屏障是一種同步機制,可以用於將多個協作線程阻塞在某個點等待,直到所有線程都完成後纔可以繼續。pthread_join()函數是用於等待線程終止,而屏障是用於等待多個線程在某個點“集合”,當線程都達到後,就可以取消阻塞所有線程並繼續運行了。有以下接口:

 

4.Sleepon locks
Sleepon locksCondvar很像,也都是等待條件的滿足,不同的是Condvars必須爲每個檢查的condition分配Mutex,而Sleepon locks可以複用一個Mutex

  1. Reader/writer locks
    讀寫鎖通常用於“多個讀取者,單個寫入者”場景的同步,它的開銷遠大於Mutex,但是在這種數據訪問模式下很有用。通常使用pthread_rwlock_rdlock()/pthread_rwlock_wrlock()/pthread_rwlock_unlock()接口。

  2. Semaphores
    信號量是常用的同步形式,允許線程在一個信號量上postwait來控制線程何時喚醒和休眠。信號量與其他同步原語的一個顯著區別是信號量是異步安全的,可以由信號處理程序操作。如果想讓一個信號處理程序喚醒一個線程,信號量是正確的選擇。
    對於單個進程中的線程之間同步,互斥鎖比信號量更有效。

  3. 通過調度策略來同步
    可以使用FIFO調度策略來保證同一優先級的線程不會在非SMP系統中併發運行。

  4. 通過消息傳遞來同步
    Send/Receive/ReplyIPC消息傳遞天然就是一種同步機制,在很多情況下讓其他同步機制變得不必要。它們也是唯一可以跨網絡使用的同步和IPC原語。

  5. 通過原子操作來同步
    QNX Neutrino提供以下原子操作:

  • adding a value
  • subtracting a value
  • clearing bits
  • setting bits
  • toggling (complementing) bits
    可以在任何地方使用原子操作,但原子操作在以下兩種情況下非常適用:
  • ISR和線程之間,ISR可以在任何時間點搶佔線程,線程保護自己不被ISR打擾的唯一方法就是禁用中斷,在實時系統中不建議禁用中斷,推薦使用QNX提供的原子操作;
  • 兩個線程之間,在SMP系統中,線程能做到真正併發,使用原子操作來進行保護;

時鐘和定時器服務

時鐘服務用於維護系統時間,同時內核也會使用時間服務來完成定時器操作。
時鐘相關的接口如下:

 

QNX提供了POSIX定時器所有功能函數集,定時器模型很豐富,有以下幾種timer類型:

  • 絕對日期
  • 相對日期
  • 週期性的
    週期模式非常重要,因爲定時器常用來作爲事件週期性來源,觸發某些線程進行處理,處理完後睡眠,直到下一個事件觸發。定時器是OS中的另外一個事件源,所有定時器都能用作時間分發系統。應用請求在定時器超時後,系統發送QNX支持的任意事件。
    定時器有以下接口:

     

中斷處理

在實時系統中,減少不必要的CPU cycles是至關重要的,需要關注兩個latency:中斷latency,調度latency。

中斷latency

中斷latency指的是從硬件中斷觸發到執行驅動程序中中斷處理函數的第一條指令之間的時間間隔。在QNX中,一直維持中斷使能,但在某些特殊的代碼中需要關閉中斷,可能會造成大的延遲,在QNX中這個時間很短。

 

中斷latency

調度latency

調度latency指的是從中斷處理函數中返回後到驅動線程第一條指令執行的時間間隔,通常包括保存當前執行上下文,加載驅動線程上下文。儘管這個時間大於中斷latency,但是QNX中調度延遲仍然很小。

 

調度latency

中斷嵌套

QNX支持中斷嵌套,中斷嵌套時序比較複雜,考慮以下這種情況:進程A在運行,中斷IRQx觸發Intx運行,在處理時又被IRQy搶佔觸發Inty運行,Inty返回一個事件導致線程B運行,Intx返回一個事件導致線程C運行,如下圖:

 

中斷嵌套

中斷接口

中斷API



作者:Loyen
鏈接:https://www.jianshu.com/p/f507fdd2d7e1
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

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