QNX相關歷史文章:
介紹
Interprocess Communication(IPC狮惜,進程間通信)在QNX Neutrino從一個嵌入式實時系統(tǒng)向一個全面的POSIX系統(tǒng)轉變起著至關重要的作用。IPC是將在內核中提供各種服務的進程內聚在一起的粘合劑。在QNX中衔沼,消息傳遞是IPC的主要形式新蟆,也提供了其他的形式,除非有特殊的說明媚狰,否則這些形式也都是基于本地消息傳遞而實現的溪胶。QNX Neutrino提供以下形式的IPC:
Synchronous message passing
一個線程調用MsgSend()
往目標線程發(fā)送消息時會阻塞住躯嫉,直到目標線程調用MsgReceive()
纱烘,進行消息處理并調用MsgReply()
回復后才會解除阻塞。
在QNX Neutrino中祈餐,服務器線程通常是循環(huán)的擂啥,等待接收客戶端發(fā)過來的消息》簦可以看看客戶端線程和服務器線程在消息傳遞過程中的狀態(tài)變化:
- 客戶端線程
- 客戶端線程調用
MsgSend()
后哺壶,如果服務器線程還沒調用MsgReceive()
,客戶端線程狀態(tài)則為SEND blocked
蜒谤,一旦服務器線程調用了MsgReceive()
山宾,客戶端線程狀態(tài)變?yōu)?code>REPLY blocked,當服務器線程執(zhí)行MsgReply()
后鳍徽,客戶端線程狀態(tài)就變成了READY
资锰; - 如果客戶端線程調用
MsgSend()
后,而服務器線程正阻塞在MsgReceive()
上阶祭, 則客戶端線程狀態(tài)直接跳過SEND blocked
绷杜,直接變成REPLY blocked
; - 當服務器線程失敗濒募、退出鞭盟、或者消失了,客戶端線程狀態(tài)變成
READY
瑰剃,此時MsgSend()
會返回一個錯誤值齿诉。
- 服務器線程
- 服務器線程調用
MsgReceive()
時,當沒有線程給它發(fā)送消息培他,它的狀態(tài)為RECEIVE blocked
鹃两,當有線程發(fā)送時變?yōu)?code>READY; - 服務器線程調用
MsgReceive()
時舀凛,當已經有其他線程給它發(fā)送過消息俊扳,MsgReceive()
會立馬返回,而不會阻塞猛遍; - 服務器線程調用
MsgReply()
時馋记,不會阻塞号坡;
Message copying
QNX的消息服務,是直接將消息從一個線程的地址空間拷貝到另一個線程地址空間梯醒,不需要中間緩沖宽堆,因此消息傳遞的性能接近底層硬件的內存帶寬。消息內容對內核來說沒有特殊的意義茸习,只對消息的發(fā)送和接收者才有意義畜隶,當然,QNX也提供了定義良好的消息類型号胚,以便能擴充或替代系統(tǒng)提供的服務籽慢。
消息在拷貝的時候,支持分塊傳輸猫胁,也就是不要求連續(xù)的緩沖區(qū)箱亿,發(fā)送和接收線程可以指定向量表,在這個表中去指定消息在內存中的位置弃秆。這個與DMA的scatter/gather
機制類似届惋。
分塊傳輸也用在文件系統(tǒng)中,比如讀數據的時候菠赚,將文件系統(tǒng)緩存中的數據分塊讀到用戶提供的空間內脑豹,如下圖:
對于簡單的單塊消息傳遞,就不需要通過IOV
(input/output vector)的形式了衡查,直接指向緩沖區(qū)即可晨缴。對于發(fā)送和接收的接口,多塊發(fā)送和單塊發(fā)送如下:
Channels and connections
在QNX Neutrino中峡捡,消息傳遞是面向通道(channel)和連接(connection)的击碗,而不是直接從線程到線程的。接收消息的線程需要創(chuàng)建一個channel
们拙,發(fā)送消息的線程需要與該channel
建立connection
稍途。
服務器使用MsgReceive()
接收消息時需要使用channels
,客戶端則需要創(chuàng)建connections
砚婆,以連接到服務器的通道上械拍,連接建立好之后,客戶端便可通過MsgSend()
來發(fā)送消息了装盯。如果進程中有很多線程都連接到一個通道上坷虑,為了提高效率,這些所有的連接都會映射到同一個內核對象中埂奈。在進程中迄损,channels
和connecttions
會用一個小的整型標識符來標記≌嘶牵客戶端connections
會直接映射到文件描述符芹敌,在架構上這是一個關鍵點痊远,可以消除另一層轉換,不需要根據文件描述符來確定往哪里發(fā)消息氏捞,而是直接將消息發(fā)往文件描述符即可碧聪。
有幾個與channel
有關聯(lián)的列表:
- Receive,等待消息的LIFO線程隊列液茎;
- Send逞姿,已發(fā)送消息但還未被接收的優(yōu)先級FIFO線程隊列;
- Reply捆等, 已發(fā)送消息哼凯,并且已經被收到,但尚未回復的無序線程列表楚里;
不管在上述哪個列表中,線程都是阻塞狀態(tài)猎贴,多個線程和多個客戶端可能等待在同一個channel
上班缎。
threads blocked while in a channel queue
Pulses
除了同步發(fā)送/接收/回復服務外,QNX還支持固定大小的非阻塞消息她渴,這種消息被稱為Pulse
达址,攜帶一個小的負載(四個字節(jié)數據,加一個字節(jié)的代碼)趁耗。Pulse
通常被用在中斷處理函數中沉唠,用作通知機制;也允許服務器在不阻塞客戶端的情況下苛败,向客戶端發(fā)送信號满葛。
優(yōu)先級繼承與消息
服務器進程按照優(yōu)先級順序來接收消息和脈沖,當服務器中的線程接收請求時罢屈,它們將繼承發(fā)送線程的優(yōu)先級嘀韧。請求服務器工作的線程的優(yōu)先級被保留,服務器工作將以適當的優(yōu)先級執(zhí)行缠捌,這種消息驅動的優(yōu)先級繼承避免了優(yōu)先級反轉的問題锄贷。
Message-passing API
Robust implementations with Send/Receive/Reply
異步系統(tǒng)的一個重要問題是事件通知需要運行信號處理程序。異步IPC難以徹底對系統(tǒng)進行測試曼月,此外也難以確保信號處理程序按預期的運行谊却。基于Send/Receive/Reply
構建的同步哑芹、非隊列系統(tǒng)結構炎辨,可以讓應用程序的架構更健壯。
在使用各種IPC機制時聪姿,避免死鎖是一個難題蹦魔,在QNX中只需要遵循兩個原則激率,就可以構建無死鎖系統(tǒng):
- 永遠不要兩個線程相互發(fā)送消息;
- 將線程組織為層級結構勿决,并只向上發(fā)送消息乒躺;
Threads should always send up to higher-level threads
上層的線程可以通過MsgSendPulse()
或MsgDeliverEvent()
來傳遞非阻塞消息或事件:
A higher-level thread can "send" a pulse event
Events
QNX Neutrino提供異步事件通知機制,事件源可能有三種:
- 調用
MsgDeliverEvent()
接口發(fā)送事件 - 中斷處理函數
- 定時器到期
事件本身可以有多種類型:Pulse
低缩、中斷嘉冒、各種形式的信號、強制解除阻塞的事件等咆繁。
考慮到事件本身的多樣性讳推,服務器實現所有的異步通知顯然不太合適,更好的方式是客戶端提供一個數據結構或者cookie
玩般,服務器調用MsgDeliverEvent()
時將事件類型寫進cookie
中银觅。
The client sends a sigevent to the server
ionotify()
函數是客戶端線程請求異步事件通知的一種方式,許多POSIX異步服務都基于這個之上來構建的坏为,比如mq_notify
和select
等究驴。
Signals
信號類似于軟中斷,QNX支持的信號如下:
QNX Neutrino擴展了信號傳遞機制匀伏,允許信號針對特定的線程洒忧,而不是簡單的針對包含線程的進程。由于信號是異步事件够颠,它們通過事件傳遞機制實現熙侍。接口如下:
當一個服務器線程想通知一個客戶端線程時,有兩種合理的事件選擇:
Pulse
或信號
-
Pulse
履磨,需要客戶端創(chuàng)建一個channel
蛉抓,并且調用MsgReceive()
接收; - 信號剃诅,只需要調用
sigwaitinfo()
芝雪,不需要創(chuàng)建channel
;
POSIX message queues
POSIX通過message queues
定義一組非阻塞的消息傳遞機制综苔。消息隊列為命名對象惩系,針對這些對象可以進行讀取和寫入,作為離散消息的優(yōu)先級隊列如筛,消息隊列具有比管道更多的結構堡牡,為應用程序提供了更多的通信控制。QNX Neutrino內核不包含message queues
杨刨,它的實現在內核之外晤柄。
QNX Neutrino提供了兩種message queues
的實現:
- mqueue,使用mqueue資源管理的傳統(tǒng)實現
- mq妖胀,使用mq服務和非同步消息的替代實現
QNX消息機制與POSIX的Message queues
有一個根本的區(qū)別:芥颈,QNX的消息機制通過內存拷貝來實現消息的傳遞惠勒;而POSIX的消息隊列通過將消息進行存取來實現消息的傳遞。QNX的消息機制比POSIX的消息隊列效率更高爬坑,但有時為了POSIX的靈活纠屋,需要適當的犧牲一點效率。
消息隊列與文件類似盾计,操作的接口相近售担。
Shared memory
共享內存提供了最高帶寬的IPC機制,一旦創(chuàng)建了共享內存對象署辉,訪問對象的進程可以使用指針直接對其進行讀寫操作族铆。共享內存本身是不同步的,需要結合同步原語一起使用哭尝,信號量和互斥鎖都適合與共享內存一塊使用哥攘,信號量一般用于進程之間的同步,而互斥鎖通常用于線程之間的同步材鹦,通通常來說互斥鎖的效率會比信號量要高逝淹。
共享內存與消息傳遞結合起來的IPC機制,可以提供以下特點:
- 非常高的性能(共享內存)
- 同步(消息傳遞)
- 跨網絡傳遞(消息傳遞)
QNX中消息傳遞通過拷貝完成侠姑,當消息較大時,可以通過共享內存來完成箩做,發(fā)送消息時不需要發(fā)送整個消息內容莽红,只需將消息保存到共享內存中,并將地址傳遞過去即可邦邦。
通常會使用mmap來將共享內存區(qū)域映射到進程地址空間中來安吁,如下圖所示:
Typed memory
類型化內存是POSIX規(guī)范中定義的功能,它是高級實時擴展的一部分燃辖。
POSIX類型化內存鬼店,提供了一個接口來打開內存對象(以操作系統(tǒng)特定的方式定義),并對它們執(zhí)行映射操作黔龟。這個對提供BSP/板級特定的地址布局與設備驅動或用戶代碼之間的抽象時非常有用妇智。
Pipes and FIFOs
管道是一種非命名IO通道,用于在多個進程之間的通信氏身,一個進程往管道寫巍棱,其他進程從管道讀取。管道一般用于平行的兩個進程單向的傳遞數據蛋欣,如果要雙向通信的話航徙,就應該使用消息傳遞了。
FIFOs與管道本質是一樣的陷虎,不同點在于FIFOs會在文件系統(tǒng)中保存為一個永久的命名文件到踏。