九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
QNX system architecture 2
userphoto

2022.06.29 上海

關(guān)注
microkernel實(shí)現(xiàn)了嵌入式實(shí)時系統(tǒng)使用的POSIX核心功能,以及QNX的消息傳遞服務(wù)。
有些POSIX功能(如file, device I/O)不是在procnto microkernel中實(shí)現(xiàn)的,這些功能是通過可選進(jìn)程和共享庫實(shí)現(xiàn)的。
想查看你使用系統(tǒng)的kernel版本號,可以使用uname -a命令。更多信息,可參考Utilities Reference
后續(xù)的QNX microkernel已經(jīng)減少了實(shí)現(xiàn)系統(tǒng)調(diào)用的代碼。kernel code的底層對象定義也變得更加特定,這樣使得代碼復(fù)用程度更高(比如抽象POSIX型號,實(shí)時信號和QNX pulses到一個通用的數(shù)據(jù)結(jié)構(gòu)和結(jié)構(gòu)的通用處理函數(shù))。
在最底層,microkernel包含了一些基本對象以及高度抽象的處理函數(shù)。OS就構(gòu)建在這個基礎(chǔ)之上
Figure 5: The microkernel
有些開發(fā)者認(rèn)為我們的微內(nèi)核是用匯編代碼實(shí)現(xiàn)的,以減小尺寸,提高性能。實(shí)際上,QNX主要是用C實(shí)現(xiàn)的;通過持續(xù)優(yōu)化算法和數(shù)據(jù)結(jié)構(gòu)進(jìn)行性能是內(nèi)核尺寸優(yōu)化,而不是通過匯編級代碼做優(yōu)化。
The implementation of the QNX Neutrino RTOS
QNX發(fā)展史,QNX軟件操作刺痛的應(yīng)用壓力已經(jīng)從內(nèi)存有限的嵌入式系統(tǒng),擴(kuò)展到高端的SMP機(jī)器。
相應(yīng)的,QNX的設(shè)計目標(biāo)就是適應(yīng)這些系統(tǒng)。為了達(dá)到這些目標(biāo),就需要QNX實(shí)現(xiàn)其他操作系統(tǒng)未達(dá)到的設(shè)計目標(biāo)。
Posix realtime and thread extensions
因?yàn)镼NX RTOS在微內(nèi)核中實(shí)現(xiàn)了實(shí)時和線程服務(wù),這些服務(wù)無需加載其他OS模塊即可使用。
此外,某些POSIX profiles建議這些服務(wù)無需以進(jìn)程模型存在。為了適應(yīng)POSIX建議,QNX直接支持線程,但是依賴QNX進(jìn)程管理服務(wù)來擴(kuò)展包含多線程的進(jìn)程。
注意,一些實(shí)時執(zhí)行單位和內(nèi)核僅提供無內(nèi)存保護(hù)的線程模型,沒有包含進(jìn)程模型和內(nèi)存保護(hù)模型。沒有進(jìn)程模型,就不是完全兼容POSIX。
System Services
微內(nèi)核支持如下內(nèi)核調(diào)用
Threads
message passing
signals
clocks
timers
interrupt handlers
semaphores
mutual exclusion locks
condition variables(condvars)
barriers
整個內(nèi)核都是構(gòu)建在這些調(diào)用之上。OS是完全可剝奪的,甚至在消息傳遞過程中都可剝奪;當(dāng)再次調(diào)度回來,系統(tǒng)會繼續(xù)傳送剩余的消息。
內(nèi)核越簡單,則進(jìn)程剝奪所要等待的內(nèi)核最大代碼路徑越小,當(dāng)然代碼尺寸小使得復(fù)雜多進(jìn)程環(huán)境下定位問題困難。服務(wù)執(zhí)行路徑短,是選擇服務(wù)包含進(jìn)微內(nèi)核中的基本原則。如果操作需要執(zhí)行很多工作(比如進(jìn)程負(fù)載),那么交給外部進(jìn)程和線程執(zhí)行,此時切換線程代價和進(jìn)程處理請求相比微不足道。
嚴(yán)格的使用上面的規(guī)則劃分內(nèi)核和外部進(jìn)程功能,打破了微內(nèi)核負(fù)載高于大內(nèi)核的神話。對于給定的上下文切換工作,簡單內(nèi)核使得上下文切換非常快,上下文切換消耗的時間淹沒在請求花費(fèi)的時間海洋中。
下圖演示了non-SMP內(nèi)核(x86)的preemption實(shí)現(xiàn)細(xì)節(jié)
中斷禁用或者剝奪暫停的時間非常短,通常僅為幾百納秒。
Threads and processes
當(dāng)構(gòu)造一個應(yīng)用(實(shí)時的,嵌入式的,圖形化或者其他的)開發(fā)者可能需要考慮應(yīng)用中幾個算法并行執(zhí)行。并行執(zhí)行通過POSIX線程模型達(dá)到,也就是一個進(jìn)程中包含了一個或者多個執(zhí)行線程。
一個線程可以認(rèn)為是一個最小執(zhí)行單位,是微內(nèi)核中調(diào)度和執(zhí)行單位。一個進(jìn)程,可以看做是線程的容器,定義了線程執(zhí)行的地址空間。一個進(jìn)程包含至少一個線程。
依賴于應(yīng)用的性質(zhì):線程可以獨(dú)立的執(zhí)行,或者線程之間需要緊密的合作,進(jìn)行數(shù)據(jù)通信以及線程同步。為了達(dá)到線程通信和同步,QNX提供了豐富的IPC和同步服務(wù)。
下列pthread_*(POSIX Threads)庫函數(shù)沒有響應(yīng)的微內(nèi)核實(shí)現(xiàn)
pthread_attr_destroy()
pthread_attr_getdetachstate()
pthread_attr_getinheritsched()
pthread_attr_getschedparam()
pthread_attr_getschedpolicy()
pthread_attr_getscope()
pthread_attr_getstatckaddr()
pthread_attr_getstacksize()
pthread_attr_init()
pthread_attr_setdetachstate()
pthread_attr_setinheritsched()
pthread_attr_setchedparam()
pthread_attr_setschedpolicy()
pthread_attr_setscope()
pthread_attr_setstackaddr()
pthread_attr_setstacksize()
pthread_cleadup_pop()
pthread_cleanup_push()
pthread_equal()
pthread_getspecific()
pthread_setspecific()
pthread_key_create()
pthread_key_delete()
pthread_self()
下表列出了POSIX線程調(diào)用相應(yīng)的微內(nèi)核線程調(diào)用,
POSIX callMicrokernel callDescription
pthread_create()ThreadCreate() Create a new thread of execution
pthread_exit()ThreadDestroy() Destroy a thread
pthread_detach()ThreadDetach() Detach a thread so it doesn't need to be joined
pthread_join()ThreadJoin() Join a thread waiting for its exit status
pthread_cancel()ThreadCancel() Cancel a thread at the next cancellation point
N/AThreadCtl() Change a thread's QNX Neutrino-specific thread characteristics
pthread_mutex_init()SyncTypeCreate() Create a mutex
pthread_mutex_destroy()SyncDestroy() Destroy a mutex
pthread_mutex_lock()SyncMutexLock() Lock a mutex
pthread_mutex_trylock()SyncMutexLock() Conditionally lock a mutex
pthread_mutex_unlock()SyncMutexUnlock() Unlock a mutex
pthread_cond_init()SyncTypeCreate() Create a condition variable
pthread_cond_destroy()SyncDestroy() Destroy a condition variable
pthread_cond_wait()SyncCondvarWait() Wait on a condition variable
pthread_cond_signal()SyncCondvarSignal() Signal a condition variable
pthread_cond_broadcast()SyncCondvarSignal() Broadcast a condition variable
pthread_getschedparam()SchedGet() Get the scheduling parameters and policy of a thread
pthread_setschedparam(),pthread_setschedprio()SchedSet() Set the scheduling parameters and policy of a thread
pthread_sigmask()SignalProcmask() Examine or set a thread's signal mask
pthread_kill()SignalKill() Send a signal to a specific thread
配置OS,提供線程和進(jìn)程支持。每一個進(jìn)程通過MMU保護(hù)地址空間,防止其他進(jìn)程訪問。一個進(jìn)程則可以包含多個線程共享同一個地址空間。
因此用戶的選擇不僅僅影響到應(yīng)用程序的并發(fā),而且也決定了IPC和同步服務(wù)的使用。
關(guān)于進(jìn)程和線程的編程角度信息,可參考<<Get Programming with the QNX Neutrino RTOS>>中的Processes and Threads, 以及<<QNX Neutrino Programmer's Guide>>中的The Programming OVerview and Precesses
Thread attributes
盡管進(jìn)程中的線程分享進(jìn)程所有地址空間,每一個線程還是有一些私有數(shù)據(jù)。有些私有數(shù)據(jù)(比如tid或thread ID)被kernel保護(hù)起來,其他線程無法訪問,而有些私有數(shù)據(jù)則駐留在未保護(hù)的進(jìn)程地址空間內(nèi)(線程??臻g),以下是比較重要的線程私有數(shù)據(jù):
tid
每一個線程都有一個整形值表示的線程ID,從1開始,線程tid在所屬的進(jìn)程中是唯一的。
Priority
線程優(yōu)先級用來幫助決定線程何時執(zhí)行,線程從他的父親繼承初始優(yōu)先級,優(yōu)先級可以改變,比如顯示的修改線程優(yōu)先級,發(fā)送消息線程也會改變線程優(yōu)先級。
在QNX Neutrino TROS中,進(jìn)程沒有優(yōu)先級屬性,只有線程有。
Name
從QNX Neutrino Core OS 6.3.2開始,線程有了名字屬性;QNX Neutrino C 庫參考中的pthread_getname_np和pthread_setname_np可以用來設(shè)置和獲取線程名字。工具dumper和pidin支持線程名,線程名是QNX Neutrino擴(kuò)展。
Register set
每個線程有自己的指令指針I(yè)P,棧指針SP,以及其他處理器特定的寄存器上下文。
Stack
每個線程都有專有的??臻g,存放在所屬進(jìn)程的地址空間中。
Signal mask
每個線程有專有的signal mask
Thread local storage
線程有一個系統(tǒng)定義的數(shù)據(jù)區(qū),稱為線程本地存儲TLS,TLS用來存放per-thread信息,比如tid, pid, stack base, errno,以及線程特定的鍵值對。一個線程可以把用戶定義數(shù)據(jù)關(guān)聯(lián)到線程特定的data key
Cancellation handlers
線程結(jié)束時,執(zhí)行的回調(diào)函數(shù)。
pthread庫實(shí)現(xiàn)了線程特定數(shù)據(jù),存儲在TLS中。關(guān)聯(lián)一個進(jìn)程的全局key和線程特定數(shù)據(jù)。為了使用線程特定數(shù)據(jù),首先創(chuàng)建一個key然后綁定一個數(shù)據(jù)值到這個key。數(shù)據(jù)值可以是一個整數(shù)或者指向動態(tài)分配數(shù)據(jù)結(jié)構(gòu)的指針。通過key就可以訪問這個綁定的數(shù)據(jù)。
thread特定數(shù)據(jù)的一個典型應(yīng)用場景:一個線程安全函數(shù)需要為每一個調(diào)用線程維護(hù)一個上下文。
使用如下函數(shù)創(chuàng)建和維護(hù)線程特定數(shù)據(jù)
FunctionDescription
pthread_key_create() Create a data key with destructor function
pthread_key_delete() Destroy a data key
pthread_setspecific() Bind a data value to a data key
pthread_getspecific() Return the data value bound to a data key
Thread life cycle
一個進(jìn)程內(nèi)的線程數(shù)目可能隨時變化,因?yàn)榫€程是動態(tài)創(chuàng)建和銷毀的。
線程創(chuàng)建(pthread_create())涉及到進(jìn)程地址空間內(nèi)的資源分配和資源初始化,然后啟動線程的執(zhí)行。
線程銷毀(pthread_exit(), pthread_cancel())涉及到線程停止,以及線程資源回收。當(dāng)一個線程執(zhí)行時,它的狀態(tài)通??梢悦枋鰹閞eady或者blocked狀態(tài)。確切的說,它可以是以下狀態(tài)的一個。
Figure 8: 可能的線程狀態(tài)。注意,除了以上列出的變遷,線程可以從任何狀態(tài)轉(zhuǎn)換為READY狀態(tài)。
CONDVAR
線程阻塞在一個條件變量(比如調(diào)用了pthread_cond_wait())
DEAD
線程中止并且等待其他線程join
INTERRUPT
線程正在等待一個中斷
JOIN
線程阻塞等待join另外一個線程。
MUTEX
線程阻塞在一個互斥鎖上,比如調(diào)用了pthread_mutex_lock()
NANOSLEEP
線程正在睡眠一個短時間間隔,比如調(diào)用了nanosleep()
NET_REPLY
線程正在等待reply發(fā)送到網(wǎng)絡(luò)上,比如調(diào)用了MsgReply*()
NET_SEND
線程等待pulse或者signal的發(fā)送,比如調(diào)用了MsgSendPulse(), MsgDeliverEvent(), 或者SignalKill()
READY
線程已經(jīng)準(zhǔn)備就緒,處理器正在執(zhí)行其他高優(yōu)先級線程。
RECEIVE
線程阻塞在消息接收上,比如調(diào)用了MsgReceive
REPLY
線程阻塞在消息reply上,比如調(diào)用了MsgSend()
RUNNING
線程正在被處理器執(zhí)行。內(nèi)核使用一個隊(duì)列(每處理器對應(yīng)一個)跟蹤正在運(yùn)行的線程
SEM
線程正在等待一個信號量被釋放,比如調(diào)用了SyncSemWait()
SEND
線程被阻塞在了消息發(fā)送,比如調(diào)用了MsgSend(),但是服務(wù)器還沒有收到這個消息
SIGSUSPEND
線程阻塞等待一個信號,比如調(diào)用了sigsuspend
SIGWAITINFO
線程阻塞等待一個信號,比如調(diào)用了sigwaitinfo()
STACK
線程等待分配分配線程堆棧地址空間,通常是父進(jìn)程調(diào)用了ThreadCreate()
STOPPED
線程阻塞等待SIGCONT信號
WAITCTX
線程正在等待一個非整數(shù)上下文變得可用
WAITPAGE
線程正在等待為一個虛擬地址分配物理內(nèi)存
WAITTHREAD
線程正在等待一個子線程創(chuàng)建完成,比如調(diào)用了ThreadCreate()
Thread scheduling
kernel的部分工作就是決定哪個進(jìn)程運(yùn)行,以及何時運(yùn)行。
首先,讓我們看下kernel何時進(jìn)行調(diào)度決定。
當(dāng)微內(nèi)核發(fā)生系統(tǒng)調(diào)用,異常或者硬件中斷,正在執(zhí)行的線程臨時掛起,此時會進(jìn)行調(diào)度決策。而不需考慮線程運(yùn)行在哪個處理器上。線程調(diào)度是全局的,發(fā)生所有處理器上。
正常情況下,掛起線程會被resume,但是在一下情況下,線程調(diào)度器會執(zhí)行上下文切換,從一個線程切換到另外一個線程。
線程被blocked
線程被preempted
yields
When is a thread blocked
當(dāng)運(yùn)行線程等待某些事件的發(fā)生時(IPC請求的響應(yīng),等待一個mutex等等)會被阻塞。阻塞的線程被從運(yùn)行隊(duì)列移除,并選中等待隊(duì)列中優(yōu)先級最高的線程執(zhí)行。當(dāng)被阻塞線程解除阻塞后,線程被放到該優(yōu)先級等待隊(duì)列的最后。
When is a thread preempted
當(dāng)一個高優(yōu)先級的線程被放到ready隊(duì)列中,運(yùn)行線程被剝奪執(zhí)行,被剝奪的線程放在對應(yīng)優(yōu)先級等待隊(duì)列的隊(duì)首,然后高優(yōu)先級線程獲得執(zhí)行。
When is a thread yielded?
正在運(yùn)行的進(jìn)程自愿放棄處理器(調(diào)用sched_yield()),進(jìn)程被放到等待隊(duì)列的末尾。然后調(diào)用最高優(yōu)先級的進(jìn)程執(zhí)行(注意,有可能仍然是該進(jìn)程被調(diào)度到)。
Scheduling priority
每一個線程都對應(yīng)一個優(yōu)先級。線程調(diào)度器通過查找所有READY線程的優(yōu)先級,選擇優(yōu)先級最高的線程運(yùn)行。
下圖顯示了五個線程(B-F)的等待隊(duì)列。線程A是當(dāng)前運(yùn)行進(jìn)程。所有其他進(jìn)程(G-Z)為BLOCKED狀態(tài)。線程A,B以及C在最高優(yōu)先級。
Figure 9: 等待隊(duì)列
OS支持256個調(diào)度優(yōu)先級。一個非特權(quán)線程可以設(shè)置它的優(yōu)先級為1~63(63為最高優(yōu)先級)。root線程和哪些具有PROCMGR_AID_PRIORITY能力的線程允許設(shè)置優(yōu)先級大于63。特殊進(jìn)程idle優(yōu)先級為0,隨時準(zhǔn)備運(yùn)行。線程缺省情況下繼承父線程的優(yōu)先級。
你可以使用如下命令改變非特權(quán)進(jìn)程允許的優(yōu)先級范圍
procnto -P priority
QNX Neutrino 6.6及后續(xù)版本,可以使用s和S選項(xiàng),對于超范圍的優(yōu)先級請求,選擇使用最大允許優(yōu)先級而不是返回一個錯誤。
注意為了防止優(yōu)先級反轉(zhuǎn),kernel可以臨時提升一個線程的優(yōu)先級。更多信息,參考本章和Interprocess Communication(IPC)章中的"Priority inheritance and mutexes"小節(jié)。內(nèi)核線程的初始優(yōu)先級是255,不過在他們阻塞在MsgReceive()后,這些內(nèi)核線程優(yōu)先級變成了發(fā)送消息線程的優(yōu)先級。
Ready隊(duì)列上的線程按照優(yōu)先級排序。Ready隊(duì)列實(shí)現(xiàn)了256個隊(duì)列,每個隊(duì)列對應(yīng)一個優(yōu)先級,相稱調(diào)度時,選擇最高優(yōu)先級隊(duì)列中的第一個線程執(zhí)行。
大部分情況下,線程是FIFO方式加入到相應(yīng)優(yōu)先級隊(duì)列中,存在如下特殊情況:
一個server線程收到了來自client的消息,離開RECEIVE-blocked狀態(tài),被插到所在優(yōu)先級隊(duì)列的頭部,此時可以認(rèn)為是LIFO。
如果一個線程發(fā)送了一個nc(non-cancellation point)變種消息,那么么當(dāng)server回復(fù)后,線程被放到等待隊(duì)列頭部,而不是尾部。如果調(diào)度策略是round-robin,線程的時間片沒有重添;例如,如果線程在發(fā)送之前已經(jīng)使用了一半的時間片,那么在可以優(yōu)雅的剝奪該線程之前,它還有一半的時間片。
Scheduling Policies
為了復(fù)合各種應(yīng)用需求,QNX Neutrino RTOS提供了如下算法
FIFO調(diào)度
round-robin 調(diào)度
sporadic調(diào)度
系統(tǒng)中的線程理論上可以用上述任意調(diào)度方法運(yùn)行。調(diào)度方法是每線程,而不是一個全局線程和進(jìn)程調(diào)度方法。
記住FIFO和round-robin調(diào)度策略僅當(dāng)兩個或以上線程共享相同優(yōu)先級。而sporadic調(diào)度策略,使用budget來控制線程執(zhí)行。在以上所有調(diào)度策略中,如果一個高優(yōu)先級線程變得READY,那么會立刻剝奪所有的低優(yōu)先級進(jìn)程。
下圖,有三個相同優(yōu)先級線程狀態(tài)為READY。如果線程A blocks,線程B會執(zhí)行。
FIFO 10: Thread A blocks; Thread B runs.
盡管一個線程從父進(jìn)程哪里繼承了調(diào)度策略,線程可以請求內(nèi)核改變調(diào)度算法。
FIFO scheduling
在FIFO調(diào)度策略,線程被選擇繼續(xù)運(yùn)行,直到
自愿放棄控制
被高優(yōu)先級進(jìn)程剝奪
Round-robin scheduling
對于round-robin調(diào)度,線程被選擇繼續(xù)運(yùn)行,直到
自愿放棄控制
被高優(yōu)先級進(jìn)程剝奪
時間片結(jié)束
如下圖所示,進(jìn)程A持續(xù)運(yùn)行,直到消耗完時間片,下一個READY thread變成運(yùn)行進(jìn)程
Figure 12: Round-robin scheduling
時間片是系統(tǒng)賦給每個線程的時間單位。一旦消耗完時間片,線程被剝奪,相同優(yōu)先級READY隊(duì)列中的下一個線程被選取執(zhí)行。一個時間片是4x時鐘周期。
Sporadic scheduling
sporadic調(diào)度策略,通常用來對進(jìn)程在某個給定時間段內(nèi),提供一個執(zhí)行時間上限。
該策略對于系統(tǒng)中運(yùn)行的周期性或者非周期性RMA非常有用。該算法可以保證執(zhí)行非周期性服務(wù)線程不會影響到系統(tǒng)內(nèi)線程和進(jìn)程的實(shí)時響應(yīng)上限。
當(dāng)使用sporadic調(diào)度時,線程優(yōu)先級動態(tài)的在前臺正常優(yōu)先級和后臺低優(yōu)先級間調(diào)整震蕩。使用下面的參數(shù),你可以控制sporadic調(diào)度的條件。
Initial budget
線程在從正常優(yōu)先級變成低優(yōu)先級前,允許執(zhí)行的時間總數(shù)。
Low priority
線程要降到的優(yōu)先級。線程作為后臺進(jìn)程時,在這個低優(yōu)先級運(yùn)行。
Replenishment period
允許線程消耗執(zhí)行budget的時間段。對于replenishment操作,POSIX實(shí)現(xiàn)使用這個值做為進(jìn)程變?yōu)镽EADY狀態(tài)的時間段。
Max number of pending replenishment
這個值限制replenishment操作的上限,因而也就決定了sporadic調(diào)度策略的系統(tǒng)負(fù)載上限。
如下圖所示,sporadic調(diào)度策略建立了線程的初始執(zhí)行budget,線程執(zhí)行時會消耗這個budget,但是這個值會周期性重新充滿。當(dāng)一個線程被阻塞后,那么在某個特定時間后,執(zhí)行budget會被重新充滿。
Figure 13: A thread's budget is replenished periodically
在正常優(yōu)先級N, 線程可執(zhí)行時間定義為初始執(zhí)行時間C,一旦這個時間被消耗完,線程優(yōu)先級被調(diào)整低優(yōu)先級L,直到replenishment操作發(fā)生。
下圖顯示了另外一種情況,線程從來沒有發(fā)生阻塞或者被剝奪。
Figure14: A thread drops in priority until its budget is replenished.
在這里,線程掉到了低優(yōu)先級,在低優(yōu)先級線程可能會執(zhí)行也可能不會執(zhí)行。一旦replenishment發(fā)生,線程優(yōu)先級恢復(fù)到原始級別。這樣每周期T,線程都會可以在高優(yōu)先級N執(zhí)行最大C時間。這就確保系統(tǒng)線程在N優(yōu)先級,僅能占用C/T系統(tǒng)資源。
實(shí)際上,一個線程可能會被阻塞多次,因此在優(yōu)先級N線程并不會真正執(zhí)行C時間,C僅僅是上限。
Manipulating priority and scheduling policies
一個線程優(yōu)先級可以在執(zhí)行時發(fā)生變化,線程本身直接修改,或者當(dāng)線程從高優(yōu)先級線程接收消息時kernel調(diào)整線程優(yōu)先級。
除了優(yōu)先級,用戶可以選擇線程的調(diào)度策略。盡管QNX庫提供了許多不同的方法獲取和設(shè)置調(diào)度參數(shù),最好使用pthread_getschedparam(), pthread_setschedparam()和pthread_setschedprio()。更詳細(xì)的信息,參見<<Neutrino Programmer's Guide>>中Programming Overview 一章。
IPC issues
因?yàn)檫M(jìn)程中的所有線程都可以不受阻礙的訪問共享數(shù)據(jù)空間,看起來這個執(zhí)行模型可以解決所有的IPC問題?是否我們可以通過共享數(shù)據(jù)機(jī)制通信而拋棄掉其他的IPC通信機(jī)制?
如果事情真像這么簡單就好了!
第一個問題是線程訪問共享數(shù)據(jù)需要同步操作。一個線程讀取到不一致數(shù)據(jù)因?yàn)榱硗庖粋€線程正在修改這部分?jǐn)?shù)據(jù),這回導(dǎo)致災(zāi)難性后果。對于critical section,必須使用某種同步機(jī)制,保證對critical section的串行訪問。
Muxtexes, semaphores, 以及condvars是解決該問題的方法。
盡管同步服務(wù)可以用來協(xié)調(diào)線程對共享內(nèi)存的訪問,共享內(nèi)存仍然無法解決某些IPC通信問題。比如,線程加同步只能做為單進(jìn)程內(nèi)IPC通信機(jī)制,如果我們的應(yīng)用需要對一個數(shù)據(jù)庫server通信,我們需要傳遞請求細(xì)節(jié)給database server,但是要通信的線程存在于database server進(jìn)程內(nèi),它的地址空間是無法尋址的。
network-distributed IPC機(jī)制通過在本地和遠(yuǎn)程網(wǎng)絡(luò)間傳送消息,因此可以用來訪問所有的OS服務(wù)。因?yàn)橄⑹怯谐叽绲模⑶蚁⒁话銇碚f都比較小,遠(yuǎn)小于共享內(nèi)存?zhèn)鬏數(shù)臄?shù)據(jù)。
Thread complexity issues
盡管線程非常適合某些系統(tǒng)設(shè)計,但是一定要注意使用線程導(dǎo)致的潘多拉魔盒。 在某種意義上,MMU保護(hù)的多任務(wù)已經(jīng)變得很普通了,計算機(jī)領(lǐng)域已經(jīng)普遍采用在未保護(hù)地址空間實(shí)現(xiàn)多線程。這不僅使得調(diào)試變得困難,而且也讓創(chuàng)建穩(wěn)定代碼更困難。
線程最初在UNIX操作系統(tǒng)中引入,作為一個輕量級并發(fā)機(jī)制解決重量級的進(jìn)程上下文切換。盡管這是一個非常有意義的嘗試,但是我們不僅要問:為什么最初進(jìn)程上下文切換如此耗時?
事實(shí)上,QNX的線程和進(jìn)程上下文切換性能幾乎相同。QNX Neutrino RTOS進(jìn)程切換時間遠(yuǎn)快于UNIX線程切換時間。因此,QNX線程無需作為解決IPCL性能問題的手段;而是應(yīng)用和server進(jìn)程中獲取更大并發(fā)性能的方法。
無需求助于線程,QNX系統(tǒng)快速的進(jìn)程間上下文切換,使得用一組共享顯示分配共享內(nèi)存的合作進(jìn)程,來構(gòu)建應(yīng)用變得非常合理。應(yīng)用程序因此僅僅會受到共享內(nèi)存區(qū)域引入的bug。而它的私有內(nèi)存空間則不會受到其他進(jìn)程破壞。在純線程模式中,所有線程的私有數(shù)據(jù)(包括??臻g)都可被其他線程訪問,很容易被野指針影響。
盡管如此,線程仍然提供了純進(jìn)程模型所不具備的并發(fā)優(yōu)點(diǎn)。比如,一個文件系統(tǒng)服務(wù)進(jìn)程執(zhí)行來自客戶端的請求,那么顯然會受益于多線程執(zhí)行。如果一個client進(jìn)程需要一個磁盤塊,而其他的client則請求一個已經(jīng)在cache中的磁盤塊,文件系統(tǒng)進(jìn)程可以利用一個線程池并發(fā)的服務(wù)客戶端請求,而不是傻等第一個請求完成。
隨著請求的到達(dá),每一個線程能夠直接使用buffer cache響應(yīng)請求或者等待disk I/O完成,等待disk I/O過程并不會增加其他client進(jìn)程的響應(yīng)延遲。文件系統(tǒng)server可以預(yù)先創(chuàng)建一組線程,準(zhǔn)備響應(yīng)到來的客戶端請求。盡管這種實(shí)現(xiàn)方式使得filesystem manager的實(shí)現(xiàn)方式更復(fù)雜,但是獲得的并發(fā)性是客觀的。
Synchronization services
QNX Neutrino RTOS提供了POSIX標(biāo)準(zhǔn)的線程級別同步原語,這些同步方法甚至可以用在不同進(jìn)程的線程之間。
同步服務(wù)至少包括如下機(jī)制
Synchronization serviceSupported between processesSupported across aQNX Neutrino LAN
Mutexes Yes No
Condvars Yes No
Barriers No No
Sleepon locks No No
Reader/writer locks Yes No
Semaphores Yes Yes (named only)
FIFO scheduling Yes No
Send/Receive/Reply Yes Yes
Atomic operations Yes No
以上同步原語大部分是實(shí)現(xiàn)在kernel中,除了:
barriers, sleepon locks, 以及reader/writer locks
atomic operations,可以實(shí)現(xiàn)由處理器實(shí)現(xiàn),或者由kernel模擬
Mutexes: mutual exclusion locks
互斥鎖,或者說mutexes是最簡單的同步服務(wù)。mutex被用來互斥訪問線程間共享數(shù)據(jù)。
mutex獲取pthread_mutex_lock()或者pthread_mutex_timedlock(),釋放pthread_mutex_unlock(),在訪問共享數(shù)據(jù)附近,通常是臨界區(qū)。
在某個時間點(diǎn),僅僅一個線程可以獲得mutex鎖。線程如果企圖lock已經(jīng)上鎖的互斥鎖,將會阻塞線程直到mutex被解鎖。當(dāng)線程unlocks互斥鎖,互斥鎖等待隊(duì)列中最高優(yōu)先級的線程被喚醒并獲得mutex。以這種方式,mutex等待線程按照優(yōu)先級高低順序訪問臨界區(qū)。
在大部分處理器上,互斥鎖獲取并不需要內(nèi)核項(xiàng)來實(shí)現(xiàn)空閑mutex。在x86機(jī)器上使用compare-and-swap操作碼;在大部分RISC處理器上可以使用load/store條件操作碼。
僅當(dāng)請求已被其他進(jìn)程獲取的mutex時,才會創(chuàng)建內(nèi)核項(xiàng),以便把當(dāng)前進(jìn)程加到blocked list上;當(dāng)釋放mutex時,內(nèi)核項(xiàng)被銷毀。這使得申請和釋放無競爭的臨界區(qū)和臨界資源變得非??欤瑑H當(dāng)需要解決競爭問題時,才會引入額外的OS工作。
非阻塞函數(shù)pthread_mutex_trylock()可以用來測試mutex是否可用。為了獲得更好的性能,臨界區(qū)的執(zhí)行時間應(yīng)該很小。需要使用condvar來實(shí)現(xiàn)線程在臨界區(qū)的阻塞。
Priority inheritance and mutexes
缺省情況下,如果申請mutext線程優(yōu)先級高于當(dāng)前mutex owner的進(jìn)程優(yōu)先級,那么當(dāng)前mutex owner的有效優(yōu)先級增加到等待mutex進(jìn)程的優(yōu)先級。當(dāng)mutex owner釋放mutex后,有效優(yōu)先級調(diào)整回原優(yōu)先級;mutext owner的有效優(yōu)先級應(yīng)該是它所阻塞線程的最高優(yōu)先級,無論是直接阻塞還是間接阻塞。
這個模式不僅確保阻塞在mutex的高優(yōu)先級線程等待時間盡可能短,而且也解決了經(jīng)典的優(yōu)先級反轉(zhuǎn)問題。
調(diào)用ptread_mutexattr_init()函數(shù)時通過設(shè)置PTHREAD_PRIO_INHERIT,支持優(yōu)先級繼承;還可以調(diào)用pthread_mutexattr_setprotocol()覆蓋這個初始設(shè)置。pthread_mutex_trylock()函數(shù)不會改變線程優(yōu)先級,因?yàn)楦暮瘮?shù)并不會阻塞。
可以使用pthread_mutexattr_settype()修改mutex屬性,允許mutex被同一個線程遞歸locked。這樣該線程可以調(diào)用會申請mutex的進(jìn)程,而這個線程已經(jīng)獲得了這個locked。
Condvar: condition variables
條件變量condvar,用來在臨界區(qū)阻塞一個線程直到某些條件滿足。條件可以是任意復(fù)雜的不依賴于條件變量本身。條件變量應(yīng)該和mutex一起使用以便實(shí)現(xiàn)monitor。
條件變量支持三種操作:
wait, pthread_cond_wait()
signal, pthread_cond_signal()
broadcast, pthread_cond_broadcase()
下面代碼是使用條件變量的例子
[html] view plain  copy
pthread_mutex_lock( &m );
. . .
while (!arbitrary_condition) {
pthread_cond_wait( &cv, &m );
}
. . .
pthread_mutex_unlock( &m );
在這個代碼例子中,在測試condition之前獲取mutex。確保只有這個線程訪問arbitrary condition。當(dāng)條件為幀,示例代碼阻塞等待直到其他進(jìn)程通過信號和廣播設(shè)置condvar。
while循環(huán)存在有兩個原因。第一,POSIX不能保證錯誤喚醒發(fā)生。第二,當(dāng)另外一個線程修改了condition,我們需要測試修改是否滿足我們的標(biāo)準(zhǔn)。mutex m被pthread_cond_wait自動unlocked,這樣允許其他線程進(jìn)入臨界區(qū)。
一個線程執(zhí)行信號unlock condvar等待隊(duì)列上最高優(yōu)先級線程,而broadcase則unblock等待隊(duì)列上的所有線程。線程必須在訪問臨界區(qū)后unlock mutex。
pthread_cond_timedwait允許condvar指定一個timeout, 等待線程在timeout超時后會unblockd
Barriers
barrier是一種同步機(jī)制,相當(dāng)于把一組合作線程阻塞,直到指定數(shù)目的線程都到達(dá)該阻塞點(diǎn)后,才會unblock這些線程。
和pthread_join不同,pthread_join是等待線程結(jié)束;而barrier則類似獸欄一樣,把線程圈在一個地方,達(dá)到一定數(shù)目后,才把這些牲畜放走。
使用pthread_barrier_init()創(chuàng)建一個barrier
[html] view plain  copy
#include <pthread.h>
int
pthread_barrier_init (pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr,
unsigned int count);
@barrier是傳入的barrier對象
@count 是pthread_barrier_wait()要阻塞的線程數(shù)目。
一旦創(chuàng)建了barrier,每個線程通過調(diào)用pthread_wait_wait()指示線程已經(jīng)完成,等待barrier放行。
[html] view plain  copy
#include <pthread.h>
int pthread_barrier_wait (pthread_barrier_t *barrier);
當(dāng)一個線程調(diào)用了pthread_barrier_wait(),該線程阻塞在改函數(shù),直到pthread_barrier_init()函數(shù)中指定數(shù)目的線程調(diào)用了pthread_barrier_wait(),姿勢所有的線程都會解除阻塞,掛到READY隊(duì)列上。
如下,是barrier使用的例子
[html] view plain  copy
<pre class="pre codeblock">/*
*  barrier1.c
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <sys/neutrino.h>
pthread_barrier_t   barrier; // barrier synchronization object
void *
thread1 (void *not_used)
{
time_t  now;
time (&now);
printf ("thread1 starting at %s", ctime (&now));
// do the computation
// let's just do a sleep here...
sleep (20);
pthread_barrier_wait (&barrier);
// after this point, all three threads have completed.
time (&now);
printf ("barrier in thread1() done at %s", ctime (&now));
}
void *
thread2 (void *not_used)
{
time_t  now;
time (&now);
printf ("thread2 starting at %s", ctime (&now));
// do the computation
// let's just do a sleep here...
sleep (40);
pthread_barrier_wait (&barrier);
// after this point, all three threads have completed.
time (&now);
printf ("barrier in thread2() done at %s", ctime (&now));
}
int main () // ignore arguments
{
time_t  now;
// create a barrier object with a count of 3
pthread_barrier_init (&barrier, NULL, 3);
// start up two threads, thread1 and thread2
pthread_create (NULL, NULL, thread1, NULL);
pthread_create (NULL, NULL, thread2, NULL);
// at this point, thread1 and thread2 are running
// now wait for completion
time (&now);
printf ("main() waiting for barrier at %s", ctime (&now));
pthread_barrier_wait (&barrier);
// after this point, all three threads have completed.
time (&now);
printf ("barrier in main() done at %s", ctime (&now));
pthread_exit( NULL );
return (EXIT_SUCCESS);
}
主線程創(chuàng)建了barrier對象,并且初始化count為3,在該barrier對象上調(diào)用pthread_barrier_wait的線程,會阻塞到這個調(diào)用上,當(dāng)阻塞的線程數(shù)達(dá)到3時,線程繼續(xù)執(zhí)行。
在本次release中,包含如下barrier函數(shù)
FunctionDescription
pthread_barrierattr_getpshared() Get the value of a barrier's process-shared attribute
pthread_barrierattr_destroy() Destroy a barrier's attributes object
pthread_barrierattr_init() Initialize a barrier's attributes object
pthread_barrierattr_setpshared() Set the value of a barrier's process-shared attribute
pthread_barrier_destroy() Destroy a barrier
pthread_barrier_init() Initialize a barrier
pthread_barrier_wait() Synchronize participating threads at the barrier
Sleepon locks
Sleepon locks和condvars非常類似,除了幾個微小的不同。
和condvars類似,sleepon locks用來阻塞當(dāng)前線程,直到某個條件變?yōu)檎?。但是不像condvars必須為每個檢查的condition必須分配;sleepon locks復(fù)用一個mutex,而不管被檢查的條件數(shù)目。
Reader/write locks
更正式的說法是 - 多個讀者,單個寫者鎖。這些鎖被用來實(shí)現(xiàn)多個線程讀數(shù)據(jù)結(jié)構(gòu),單個線程寫數(shù)據(jù)結(jié)構(gòu)。讀寫鎖的代價高于mutexes,但是在某些訪問模式下是有用的。
讀寫鎖允許多個線程同時申請讀請求鎖pthread_rwlock_rdlock(),但是如果一個線程調(diào)用了寫請求鎖pthread_rwlock_wrlock(),這個讀鎖請求會被拒絕,直到當(dāng)前所有的讀進(jìn)程釋放了讀鎖pthread_rwlock_unlock()
多個寫進(jìn)程可以排隊(duì)等待寫操作,所有被阻塞的寫線程運(yùn)行結(jié)束前,讀進(jìn)程不會再允許訪問。讀線程的優(yōu)先級不會被考慮的。
pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock()允許線程嘗試獲取請求的鎖,而不會阻塞當(dāng)前線程。這些調(diào)用或者成功獲取鎖,或者返回一個狀態(tài)指明鎖無法立刻獲得。
讀寫鎖不是在kernel中實(shí)現(xiàn)的,而是通過kernel提供的mutex和condvar構(gòu)建的。
semaphores
信號量是另外一種同步方式,允許線程通過post和wait操作控制線程喚醒和休眠。
sem_pose操作增加信號量值,sem_wait()則減少信號量值。
如果sem_wait在一個正值的信號量上,線程不會被阻塞。sem_wait在負(fù)值信號量上,會導(dǎo)致當(dāng)前進(jìn)程阻塞,直到某個進(jìn)程執(zhí)行了sem_post。如果在sem_post之前執(zhí)行了多次sem_post,那么多個線程執(zhí)行sem_wait操作就不會阻塞。
信號量和其他同步原語的主要區(qū)別是信號量是異步安全的,可以被signal handlers控制,如果需要的效果是通過信號喚醒線程,那么信號量是正確選擇。
信號量另外一個有用的屬性是支持進(jìn)程間同步。盡管mutex也可以在進(jìn)程間工作,POSIX線程標(biāo)準(zhǔn)認(rèn)為這是可選功能,該功能不是可移植的。如果在單進(jìn)程內(nèi)線程間同步,Mutexes比信號量更有效。
信號量一個變種,命名為信號量的服務(wù)。使用它可以實(shí)現(xiàn)網(wǎng)絡(luò)上不同主機(jī)上進(jìn)程間同步。
Synchronization via scheduling policy
通過選擇POSIX FIFO調(diào)度策略,我們可以保證在non-SMP系統(tǒng)上,兩個具有相同優(yōu)先級的進(jìn)程不會訪問臨界區(qū)。
FIFO調(diào)度策略控保證具有相同優(yōu)先級的線程持續(xù)運(yùn)行,直到他們自愿放棄處理器給其他線程。
這個放棄包括線程向其他進(jìn)程請求服務(wù)引起的阻塞,或者一個信號發(fā)生。臨界區(qū)內(nèi)必須仔細(xì)編碼和注釋,確保后面的維護(hù)者不破壞這個原則。
此外高優(yōu)先級線程仍然有可能剝奪這些FIFO調(diào)度線程。所以臨界區(qū)碰撞只能是具有相同優(yōu)先級的FIFO調(diào)度線程。通過施加這個條件,線程可以放心的訪問這個共享內(nèi)存而無需考慮顯示的同步操作。
Synchronization via message passing
Send/Receive/Reply IPC消息天然就是一種同步機(jī)制。在很多場景下,使用IPC消息后就無需在使用其他同步機(jī)制。
Synchronization via atomic operations
在某些情況下,你可能想執(zhí)行一個端操作,并且保證短操作是原子的,換句話說就是不允許其他線程或者ISR打斷操作。
QNX提供了如下原子操作
Add a value
subtracting a value
clearing bits
setting bits
toggling bits
盡管可以在任何地方使用原子操作,但是有兩種情況非常適合使用原子操作:
ISR和線程之間
兩個線程之間
ISR可以在任意時間點(diǎn)剝奪一個線程,線程保護(hù)自己不被ISR打擾的唯一方法就是禁用中斷。在實(shí)時系統(tǒng)中不建議禁用中斷,推薦使用QNX提供的原子操作。
在一個SMP系統(tǒng)中,多個線程可以同時執(zhí)行。此外,上面提到的ISR仍然存在,使用QNX的原子操作解決以上問題。
Synchronization services implementation
下表列出了各種微內(nèi)核調(diào)用,以及構(gòu)造在這些微內(nèi)核調(diào)用之上的POSIX調(diào)用。
Microkernel callPOSIX callDescription
SyncTypeCreate()pthread_mutex_init(),pthread_cond_init(),sem_init() Create object for mutex, condvars, and semaphore
SyncDestroy()pthread_mutex_destroy(),pthread_cond_destroy(),sem_destroy() Destroy synchronization object
SyncCondvarWait()pthread_cond_wait(),pthread_cond_timedwait() Block on a condvar
SyncCondvarSignal()pthread_cond_broadcast(),pthread_cond_signal() Wake up condvar-blocked threads
SyncMutexLock()pthread_mutex_lock(),pthread_mutex_trylock() Lock a mutex
SyncMutexUnlock()pthread_mutex_unlock() Unlock a mutex
SyncSemPost()sem_post() Post a semaphore
SyncSemWait()sem_wait(),sem_trywait() Wait on a semaphore
Clock and timer services
時鐘服務(wù)用來維護(hù)時間,相應(yīng)的被kernel定時器調(diào)用用來實(shí)現(xiàn)間隔定時器。
ClockTime()內(nèi)核調(diào)用CLOCK_REALTIME用來獲取系統(tǒng)時鐘,也就是系統(tǒng)時間。一旦設(shè)置,系統(tǒng)時間基于時鐘精度增加一定的納秒。時間精度可以通過系統(tǒng)調(diào)用ClockPeriod()查詢和設(shè)置。
在系統(tǒng)內(nèi)存中的64bit數(shù)據(jù)結(jié)構(gòu)用來保存從系統(tǒng)啟動開始的nanoseconds。數(shù)據(jù)結(jié)構(gòu)的nsec成員總是單調(diào)增加,不會受到ClockTime()和ClockAdjust()設(shè)置當(dāng)前時間的影響。
ClockCycles()函數(shù)返回64bit循環(huán)計數(shù)器的當(dāng)前值。這是處理器實(shí)現(xiàn)短時間間隔的高性能機(jī)制。例如在x86處理器上,可以通過機(jī)器碼獲取時間戳計數(shù)器。在Prntium處理器上,這個計數(shù)器每個時鐘周期加1。一個100MHz的Pentium的時鐘周期為1/100,000,000秒(10納秒)。其他的CPU架構(gòu)有類似的指令。
對于沒有實(shí)現(xiàn)該指令的處理器架構(gòu),內(nèi)核通過模擬方式,提供一個低精度實(shí)現(xiàn)。
在所有的情況下,SYSPAGE_ENTRY(qtime)->cycles_per_sec成員給出了每秒ClockCycles()增量數(shù)。
ClockPeriod()函數(shù)允許線程設(shè)置系統(tǒng)timer為納秒的倍數(shù)。OS內(nèi)核根據(jù)硬件盡量滿足請求的精度。
選擇的間隔最后會換算為潛在硬件的精度的整數(shù)值。當(dāng)然,設(shè)置成一個非常低的值,會導(dǎo)致CPU性能消耗在時鐘中斷上。
Microkernel callPOSIX callDescription
ClockTime()clock_gettime(),clock_settime() Get or set the time of day (using a 64-bit value in nanoseconds ranging from 1970 to 2554).
ClockAdjust() N/A Apply small time adjustments to synchronize clocks.
ClockCycles() N/A Read a 64-bit free-running high-precision counter.
ClockPeriod()clock_getres() Get or set the period of the clock.
ClockId()clock_getcpuclockid(),pthread_getcpuclockid() Return an integer that's passed to ClockTime()as a clockid_t.
內(nèi)核可以運(yùn)行在無滴答模式以便減少電量消耗,但這有點(diǎn)誤導(dǎo),實(shí)際上系統(tǒng)仍然存在時鐘滴答。僅僅當(dāng)系統(tǒng)完全idle后,kernel才關(guān)閉時鐘滴答。使能無滴答操作,可以在執(zhí)行startup-*時增加-Z選項(xiàng)。
Time correction
為了應(yīng)用時間矯正并且系統(tǒng)無需經(jīng)歷時間跳躍。ClockAdjust調(diào)用提供了一個選項(xiàng),設(shè)置時間矯正的時間間隔。這回導(dǎo)致時間加速或者倒退直到系統(tǒng)同步到指定的當(dāng)前時間。
Timers
QNX提供了POSIX timer功能全集。因?yàn)檫@些timer的創(chuàng)建和維護(hù)是快速的,所以timer是內(nèi)核中不昂貴資源。
POSIX時鐘模型是非常豐富的,提供了如下timer類型:
絕對日期
相對日期
周期性的
周期性模式是最重要的,因?yàn)閠imer最常用的是作為一個周期性的源,啟動某個進(jìn)程處理些工作,然后繼續(xù)休眠直到協(xié)議個事件。如果線程在每個事件中重新設(shè)置timer,那么就有可能錯過時間除非使用絕對時間設(shè)置。但是,更壞的情況是,如果t高優(yōu)先級線程導(dǎo)致timer事件上的線程無法及時運(yùn)行,就會造成寫入的絕對時間已經(jīng)變成過去時。
周期模式繞開了這些問題,只設(shè)置一次,然后簡單的響應(yīng)周期性事件即可。
因?yàn)閠imer是OS中的另外一個事件源,所有timer可以用做時間分發(fā)系統(tǒng)。應(yīng)用請求可以在timer超時后,系統(tǒng)發(fā)送QNX支持的任意事件。
OS提供的timeout服務(wù)可以用來指定應(yīng)用等待kernel調(diào)用和請求完成的最大等待時間。使用常用OS timer服務(wù)的問題:是在可剝奪的實(shí)時操作系統(tǒng)下,在標(biāo)識timout值到請求服務(wù)的這段時間間隔內(nèi),可能會有一個高優(yōu)先級進(jìn)程被調(diào)度運(yùn)行,并且剝奪的時間足夠長,這就導(dǎo)致設(shè)定的 timout在請求服務(wù)時已經(jīng)過期了。這樣應(yīng)使用一個過期的timout請求服務(wù)(不會再超時),這可能導(dǎo)致掛起的進(jìn)程,協(xié)議傳輸時莫名其妙的延遲等問題。
QNX提供了TimerTimeout()內(nèi)核方法允許應(yīng)用設(shè)定一系列的阻塞狀態(tài)的timeout。之后,當(dāng)應(yīng)用向內(nèi)核發(fā)送一個請求時,內(nèi)核自動使能之前配置的timeout作為應(yīng)用阻塞在給定狀態(tài)的超時時間。
Microkernel callPOSIX callDescription
TimerAlarm()alarm() Set a process alarm
TimerCreate()timer_create() Create an interval timer
TimerDestroy()timer_delete() Destroy an interval timer
TimerInfo()timer_gettime() Get the time remaining on an interval timer
TimerInfo()timer_getoverrun() Get the number of overruns on an interval timer
TimerSettime()timer_settime() Start an interval timer
TimerTimeout()sleep(),nanosleep()sigtimedwait(),pthread_cond_timedwait()pthread_mutex_trylock() Arm a kernel timeout for any blocking state
Interrupt handling
不論我們多么期望,計算你并不能無限快。在一個實(shí)時系統(tǒng)中,CPU時鐘不被浪費(fèi)是絕對重要的,最小化外部事件到相應(yīng)線程代碼執(zhí)行這個時間間隔也是關(guān)鍵的,這個時間間隔稱為延遲。
我們最關(guān)心的兩種延遲,是中斷延遲和調(diào)度延遲。
interrupt latency
中斷延遲硬件中斷發(fā)生,到執(zhí)行驅(qū)動處理函數(shù)第一條指令之間的時間間隔。
OS在大部分情況下都會維持中斷使能,所以中斷延遲不那么重要,但是某些臨界區(qū)代碼確實(shí)需要臨時禁用中斷。最大禁中斷時間通常決定了最壞中斷延遲,在QNX系統(tǒng)中這個時間是非常小的。
下圖演示了硬件中斷的中斷處理函數(shù)流程。中斷處理函數(shù)可以簡單的返回,或者分發(fā)一個事件再返回。
Figure 16: Interrupt handler simply terminates
Scheduling latency
在某些情況下,低級硬件中斷處理函數(shù)必須調(diào)度一個高級線程運(yùn)行。在這個場景下,中斷處理函數(shù)分發(fā)一個事件并返回。這就引出了第二種形式的延遲- 調(diào)度延遲。
調(diào)度延遲是從中斷處理函數(shù)的最后一條指令開始,到驅(qū)動線程的第一條指令開始執(zhí)行的時間間隔。這通常包括保存當(dāng)前執(zhí)行上下文,加載驅(qū)動線程上下文。盡管這個時間大于中斷延遲,但是QNX中調(diào)度延遲仍然非常小。
Figure 17: Interrupt handler terminates, returning an event.
需要注意的是,大部分(或者部分)中斷不需要發(fā)送一個事件。在大部分情況下,中斷處理函數(shù)可以處理硬件相關(guān)的問題。僅當(dāng)中斷需要很多額外處理時,才會喚醒高級別的驅(qū)動線程。例如,串行設(shè)備驅(qū)動的中斷處理函數(shù)每次收到傳輸中斷時會向硬件填充一個數(shù)據(jù),僅當(dāng)輸出buffer幾乎為空時才會觸發(fā)串行設(shè)備驅(qū)動的線程。
Nested interrupts
QNX完全支持nested中斷。
前面的場景描述的只有一個中斷發(fā)生,這是最簡單的,最常見的情況??紤]最壞的時序下,當(dāng)前處理中斷的時間需要考慮未屏蔽的中斷,因?yàn)楦邇?yōu)先級未屏蔽中斷將剝奪一個正在處理的中斷。
在下圖中,進(jìn)程A正在運(yùn)行,中斷IRQx觸發(fā)Intx運(yùn)行,在處理過程中被IRQy的Inty剝奪,Inty返回一個事件導(dǎo)致線程B隱形。Intx返回一個時間導(dǎo)致線程C運(yùn)行。
Interrupt calls
中斷處理API包括如下內(nèi)核調(diào)用
FunctionDescription
InterruptAttach() Attach a local function (an Interrupt Service Routine or ISR) to an interrupt vector.
InterruptAttachEvent() Generate an event on an interrupt, which will ready a thread. No user interrupt handler runs. This is the preferred call.
InterruptDetach() Detach from an interrupt using the ID returned by InterruptAttach() orInterruptAttachEvent().
InterruptWait() Wait for an interrupt.
InterruptEnable() Enable hardware interrupts.
InterruptDisable() Disable hardware interrupts.
InterruptMask() Mask a hardware interrupt.
InterruptUnmask() Unmask a hardware interrupt.
InterruptLock() Guard a critical section of code between an interrupt handler and a thread. A spinlock is used to make this code SMP-safe. This function is a superset of InterruptDisable()and should be used in its place.
InterruptUnlock() Remove an SMP-safe lock on a critical section of code.
使用API,具有相應(yīng)特權(quán)的用戶線程可以調(diào)用InterruptAttach()或者InterruptAttachEvent(),綁定中斷號和某個線程內(nèi)陸址空間內(nèi)的函數(shù)地址。QNX允許多個ISRs綁定到一個硬件中斷號上,在運(yùn)行中斷處理函數(shù)時,未屏蔽中斷仍然接收服務(wù)。
在系統(tǒng)初始化階段,啟動代碼確保所有的中斷源是屏蔽的。當(dāng)首次調(diào)用InterruptAttach()和InterruptAttachEvent()時,內(nèi)核會取消該中斷的屏蔽。類似的,調(diào)用最后一個中斷向量InterruptDetach(),內(nèi)核則屏蔽該中斷。
在中斷處理函數(shù)中使用浮點(diǎn)操作是不安全的。
下面代碼演示了如何綁定ISR到PC的硬件時鐘中斷上。因?yàn)閮?nèi)核時鐘ISR已經(jīng)負(fù)責(zé)清理中斷源,所以這個ISR只是簡單的增加線程數(shù)據(jù)空間一個計數(shù)變量,然后返回到kernel。
[html] view plain  copy
#include <stdio.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
struct sigevent event;
volatile unsigned counter;
const struct sigevent *handler( void *area, int id ) {
// Wake up the thread every 100th interrupt
if ( ++counter == 100 ) {
counter = 0;
return( &event );
}
else
return( NULL );
}
int main() {
int i;
int id;
// Request I/O privileges
ThreadCtl( _NTO_TCTL_IO, 0 );
// Initialize event structure
event.sigev_notify = SIGEV_INTR;
// Attach ISR vector
id=InterruptAttach( SYSPAGE_ENTRY(qtime)->intr, &handler,
NULL, 0, 0 );
for( i = 0; i < 10; ++i ) {
// Wait for ISR to wake us up
InterruptWait( 0, NULL );
printf( "100 events\n" );
}
// Disconnect the ISR handler
InterruptDetach(id);
return 0;
}
使用這個方法,特權(quán)用戶線程可以動態(tài)的添加中斷處理函數(shù)到硬件中斷。這些線程可以使用源碼級調(diào)試工具;當(dāng)使用interuptAttachEvent()調(diào)用時,ISR本身也是可以在源代碼級別調(diào)試。
當(dāng)硬件中斷發(fā)生時,處理器將進(jìn)入內(nèi)核的中斷重定向。這段代碼保存當(dāng)前運(yùn)行進(jìn)程上下文到線程表項(xiàng),并且設(shè)置處理器上下文為ISR代碼和數(shù)據(jù)所在的線程。這允許ISR使用用戶態(tài)線程buffer和代碼處理中斷,如果需要線程執(zhí)行更高級別的工作。把ISR所在線程加入事件隊(duì)列,稍后該線程即可使用ISR已經(jīng)放入到線程buffers中的數(shù)據(jù)進(jìn)一步處理。
因?yàn)镮SR可以訪問所在線程的內(nèi)存映射空間,所以ISR可以直接操作映射到線程地址空間的設(shè)備,或者直接執(zhí)行I/O指令。因此,控制硬件的設(shè)備驅(qū)動不去要連接到內(nèi)核中。
內(nèi)核中的中斷重定向代碼將調(diào)用綁定到硬件中斷上的每一個ISR。如果返回值指示有事件需要處理,那么kernel把事件加入隊(duì)列。當(dāng)這個中斷向量的最后一個ISR已經(jīng)調(diào)用完成,內(nèi)核中斷處理函數(shù)完成了硬件中斷的控制,并從中斷返回。
中斷返回并不意味著進(jìn)入被中斷線程的上下文,如果入隊(duì)的事件使得一個高優(yōu)先級線程變成READY,微內(nèi)核將返回到這個高優(yōu)先級線程。
這個方法使得中斷發(fā)生到第一條ISR指令執(zhí)行間隔,以及最后一條ISR指令到ISR觸發(fā)線程第一條指令執(zhí)行間隔范圍良好。
最壞情況下的中斷延遲是良有界的,因?yàn)镺S僅僅在代碼很少的臨界區(qū)禁用中斷。禁用中斷的時間間隔是固定的,因?yàn)闆]有數(shù)據(jù)依賴。
微內(nèi)核中斷重定向在調(diào)用ISR前僅執(zhí)行很少的指令。因此硬件或者內(nèi)核調(diào)用的進(jìn)程剝奪是非常塊的,并且代碼路徑相同。
當(dāng)ISR正在執(zhí)行時,它對硬件有完全的訪問權(quán)限,但是不能調(diào)用其他內(nèi)核調(diào)用。ISR主要目的是在盡可能短時間內(nèi)響應(yīng)硬件中斷,做盡可能少的工作來滿足中斷,如果必要,則觸發(fā)一個線程調(diào)度做進(jìn)一步的工作。
最壞的中斷延遲計算方法:內(nèi)核導(dǎo)致的中斷延遲,加上所有大于當(dāng)前中斷的最大ISR運(yùn)行時間。因?yàn)橹袛鄡?yōu)先級是可以重新賦值的,所有系統(tǒng)內(nèi)最重要的中斷使用最高的優(yōu)先級。
注意對于InterruptAttachEvent()調(diào)用,沒有ISR運(yùn)行。而是為每個中斷生成一個用戶特定事件。當(dāng)時間生成后中斷被自動屏蔽,在設(shè)備驅(qū)動處理線程中需要顯示的重新使能中斷。
因?yàn)橛布袛嗌傻墓ぷ鲀?yōu)先級可以在OS調(diào)度優(yōu)先級執(zhí)行,而不是硬件定義的優(yōu)先級。因此中斷源不會在被處理前不會重入中斷,
除了硬件中斷,各種微內(nèi)核事件也可以被綁定到用戶線程和進(jìn)程。當(dāng)這種事件發(fā)生時,內(nèi)核可以調(diào)用到用戶線程中的代碼,執(zhí)行這個事件的特定處理。例如,當(dāng)系統(tǒng)idle線程被調(diào)用時,一個用戶線程可以被內(nèi)核回調(diào)到線程內(nèi),這樣硬件特定的低功耗模式可以很容易實(shí)現(xiàn)。
Microkernel callDescription
InterruptHookIdle2() When the kernel has no active thread to schedule, it runs the idle thread, which can call a user handler. This handler can perform hardware-specific power-management operations.
InterruptHookTrace() This function attaches a pseudo interrupt handler that can receive trace events from the instrumented kernel.
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux下C語言的多線程編程學(xué)習(xí)
超硬核,要是當(dāng)初這么學(xué)進(jìn)程和線程就好了!
多線程編程基礎(chǔ)
Linux多線程編程(不限Linux)
【轉(zhuǎn)】pthread多線程編程整理 - 與時間賽跑的使者
一個 Linux 上分析死鎖的簡單方法
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服