[轉]QNX_IPC機制-進程線程與kernel通信機制

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

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

鋒影

email:[email protected]

 

介紹

Interprocess Communication(IPC,進程間通信)在QNX Neutrino從一個嵌入式實時系統向一個全面的POSIX系統轉變起着至關重要的作用。IPC是將在內核中提供各種服務的進程內聚在一起的粘合劑。在QNX中,消息傳遞是IPC的主要形式,也提供了其他的形式,除非有特殊的說明,否則這些形式也都是基於本地消息傳遞而實現的。QNX Neutrino提供以下形式的IPC:

 

IPC

Synchronous message passing

一個線程調用MsgSend()往目標線程發送消息時會阻塞住,直到目標線程調用MsgReceive(),進行消息處理並調用MsgReply()回覆後纔會解除阻塞。
在QNX Neutrino中,服務器線程通常是循環的,等待接收客戶端發過來的消息。可以看看客戶端線程和服務器線程在消息傳遞過程中的狀態變化:

 

  1. 客戶端線程
  • 客戶端線程調用MsgSend()後,如果服務器線程還沒調用MsgReceive(),客戶端線程狀態則爲SEND blocked,一旦服務器線程調用了MsgReceive(),客戶端線程狀態變爲REPLY blocked,當服務器線程執行MsgReply()後,客戶端線程狀態就變成了READY
  • 如果客戶端線程調用MsgSend()後,而服務器線程正阻塞在MsgReceive()上, 則客戶端線程狀態直接跳過SEND blocked,直接變成REPLY blocked
  • 當服務器線程失敗、退出、或者消失了,客戶端線程狀態變成READY,此時MsgSend()會返回一個錯誤值。
  1. 服務器線程
  • 服務器線程調用MsgReceive()時,當沒有線程給它發送消息,它的狀態爲RECEIVE blocked,當有線程發送時變爲READY
  • 服務器線程調用MsgReceive()時,當已經有其他線程給它發送過消息,MsgReceive()會立馬返回,而不會阻塞;
  • 服務器線程調用MsgReply()時,不會阻塞;

Message copying

QNX的消息服務,是直接將消息從一個線程的地址空間拷貝到另一個線程地址空間,不需要中間緩衝,因此消息傳遞的性能接近底層硬件的內存帶寬。消息內容對內核來說沒有特殊的意義,只對消息的發送和接收者纔有意義,當然,QNX也提供了定義良好的消息類型,以便能擴充或替代系統提供的服務。
消息在拷貝的時候,支持分塊傳輸,也就是不要求連續的緩衝區,發送和接收線程可以指定向量表,在這個表中去指定消息在內存中的位置。這個與DMA的scatter/gather機制類似。

multipart transfer

 

分塊傳輸也用在文件系統中,比如讀數據的時候,將文件系統緩存中的數據分塊讀到用戶提供的空間內,如下圖:

 

對於簡單的單塊消息傳遞,就不需要通過IOV(input/output vector)的形式了,直接指向緩衝區即可。對於發送和接收的接口,多塊發送和單塊發送如下:

 

 

Channels and connections

在QNX Neutrino中,消息傳遞是面向通道(channel)和連接(connection)的,而不是直接從線程到線程的。接收消息的線程需要創建一個channel,發送消息的線程需要與該channel建立connection
服務器使用MsgReceive()接收消息時需要使用channels,客戶端則需要創建connections,以連接到服務器的通道上,連接建立好之後,客戶端便可通過MsgSend()來發送消息了。如果進程中有很多線程都連接到一個通道上,爲了提高效率,這些所有的連接都會映射到同一個內核對象中。在進程中,channelsconnecttions會用一個小的整型標識符來標記。客戶端connections會直接映射到文件描述符,在架構上這是一個關鍵點,可以消除另一層轉換,不需要根據文件描述符來確定往哪裏發消息,而是直接將消息發往文件描述符即可。

 

Connections map elegantly into file descriptors

 

有幾個與channel有關聯的列表:

  • Receive,等待消息的LIFO線程隊列;
  • Send,已發送消息但還未被接收的優先級FIFO線程隊列;
  • Reply, 已發送消息,並且已經被收到,但尚未回覆的無序線程列表;
    不管在上述哪個列表中,線程都是阻塞狀態,多個線程和多個客戶端可能等待在同一個channel上。

    threads blocked while in a channel queue

Pulses

除了同步發送/接收/回覆服務外,QNX還支持固定大小的非阻塞消息,這種消息被稱爲Pulse,攜帶一個小的負載(四個字節數據,加一個字節的代碼)。Pulse通常被用在中斷處理函數中,用作通知機制;也允許服務器在不阻塞客戶端的情況下,向客戶端發送信號。

Pulses pack a small payload

 

優先級繼承與消息

服務器進程按照優先級順序來接收消息和脈衝,當服務器中的線程接收請求時,它們將繼承發送線程的優先級。請求服務器工作的線程的優先級被保留,服務器工作將以適當的優先級執行,這種消息驅動的優先級繼承避免了優先級反轉的問題。

Message-passing API

Robust implementations with Send/Receive/Reply

異步系統的一個重要問題是事件通知需要運行信號處理程序。異步IPC難以徹底對系統進行測試,此外也難以確保信號處理程序按預期的運行。基於Send/Receive/Reply構建的同步、非隊列系統結構,可以讓應用程序的架構更健壯。

在使用各種IPC機制時,避免死鎖是一個難題,在QNX中只需要遵循兩個原則,就可以構建無死鎖系統:

  • 永遠不要兩個線程相互發送消息;
  • 將線程組織爲層級結構,並只向上發送消息;

    Threads should always send up to higher-level threads


    上層的線程可以通過MsgSendPulse()MsgDeliverEvent()來傳遞非阻塞消息或事件:

    A higher-level thread can "send" a pulse event

Events

QNX Neutrino提供異步事件通知機制,事件源可能有三種:

  • 調用MsgDeliverEvent()接口發送事件
  • 中斷處理函數
  • 定時器到期
    事件本身可以有多種類型:Pulse、中斷、各種形式的信號、強制解除阻塞的事件等。
    考慮到事件本身的多樣性,服務器實現所有的異步通知顯然不太合適,更好的方式是客戶端提供一個數據結構或者cookie,服務器調用MsgDeliverEvent()時將事件類型寫進cookie中。

    The client sends a sigevent to the server

ionotify()函數是客戶端線程請求異步事件通知的一種方式,許多POSIX異步服務都基於這個之上來構建的,比如mq_notifyselect等。

Signals

信號類似於軟中斷,QNX支持的信號如下:

 

QNX Neutrino擴展了信號傳遞機制,允許信號針對特定的線程,而不是簡單的針對包含線程的進程。由於信號是異步事件,它們通過事件傳遞機制實現。接口如下:

 

 

Signal delivery


當一個服務器線程想通知一個客戶端線程時,有兩種合理的事件選擇:Pulse或信號

 

  • Pulse,需要客戶端創建一個channel,並且調用MsgReceive()接收;
  • 信號,只需要調用sigwaitinfo(),不需要創建channel

POSIX message queues

POSIX通過message queues定義一組非阻塞的消息傳遞機制。消息隊列爲命名對象,針對這些對象可以進行讀取和寫入,作爲離散消息的優先級隊列,消息隊列具有比管道更多的結構,爲應用程序提供了更多的通信控制。QNX Neutrino內核不包含message queues,它的實現在內核之外。
QNX Neutrino提供了兩種message queues的實現:

  • mqueue,使用mqueue資源管理的傳統實現
  • mq,使用mq服務和非同步消息的替代實現

QNX消息機制與POSIX的Message queues有一個根本的區別:,QNX的消息機制通過內存拷貝來實現消息的傳遞;而POSIX的消息隊列通過將消息進行存取來實現消息的傳遞。QNX的消息機制比POSIX的消息隊列效率更高,但有時爲了POSIX的靈活,需要適當的犧牲一點效率。

消息隊列與文件類似,操作的接口相近。

 

Shared memory

共享內存提供了最高帶寬的IPC機制,一旦創建了共享內存對象,訪問對象的進程可以使用指針直接對其進行讀寫操作。共享內存本身是不同步的,需要結合同步原語一起使用,信號量和互斥鎖都適合與共享內存一塊使用,信號量一般用於進程之間的同步,而互斥鎖通常用於線程之間的同步,通通常來說互斥鎖的效率會比信號量要高。

共享內存與消息傳遞結合起來的IPC機制,可以提供以下特點:

  • 非常高的性能(共享內存)
  • 同步(消息傳遞)
  • 跨網絡傳遞(消息傳遞)

QNX中消息傳遞通過拷貝完成,當消息較大時,可以通過共享內存來完成,發送消息時不需要發送整個消息內容,只需將消息保存到共享內存中,並將地址傳遞過去即可。

 

通常會使用mmap來將共享內存區域映射到進程地址空間中來,如下圖所示:

 

Arguments to mmap

Typed memory

類型化內存是POSIX規範中定義的功能,它是高級實時擴展的一部分。
POSIX類型化內存,提供了一個接口來打開內存對象(以操作系統特定的方式定義),並對它們執行映射操作。這個對提供BSP/板級特定的地址佈局與設備驅動或用戶代碼之間的抽象時非常有用。

Pipes and FIFOs

管道是一種非命名IO通道,用於在多個進程之間的通信,一個進程往管道寫,其他進程從管道讀取。管道一般用於平行的兩個進程單向的傳遞數據,如果要雙向通信的話,就應該使用消息傳遞了。

FIFOs與管道本質是一樣的,不同點在於FIFOs會在文件系統中保存爲一個永久的命名文件。


 

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