Mach消息發送機制

目錄

  • Mach基礎
  • Mach作用
  • Mach消息
    • 簡單消息
    • 復雜消息
    • 端口
  • 消息傳遞實現

Mach基礎

Mach是iOS的XNU內核中最為核心的部分,稱為核心中的核心。

Mach中,所有東西都是通過自己的對象實現的。進程、線程和虛擬內存都是對象,所有的對象都有自己的屬性。Mach采用消息傳遞的方式實現對象與對象之間的通信。Mach對象不能直接調用另一個對象,只能傳遞消息,源對象發送一條消息,這條消息加入到目標對象的隊列中等待處理。如果產生應答,則通過另一條消息傳遞回去,消息遵從FIFO方式。

Mach作用

Mach作為核心中的核心,設計之初是為了把一些不重要的功能移到用戶態,因此Mach中留下的功能都是內核最重要的功能。

  • 線程管理
  • 線程資源分配
  • 虛擬內存分配及管理
  • 底層物理資源的分配

Mach消息

上文說到Mach中對象之間通信的方式是消息傳遞。消息在Mach處于很重要的地位,是MachIPC的核心構建塊。消息定義在<mach/message.h>文件中,在xcode中就能夠看到。

簡單消息

一個簡單消息由三個部分組成:

  • 一個強制要有的消息頭(mach_msg_header_t)
  • 一個可選的body(mach_msg_body_t)
  • 一個可選的tailer(mach_msg_trailer_t),這個tailer只與接收端有關系。

結構如下所示

bits是標志位,表示消息的性質。size則表示消息的大小。id標識了該消息的唯一性。

復雜消息

復雜消息是帶有一些額外字段和結構的消息。結構如下圖:

可以看出,相比于簡單消息,它的結構要比簡單消息多一段“附件”,也就是body。

typedef struct
{
    mach_msg_size_t msgh_descriptor_count;
} mach_msg_body_t;

復雜消息靠msgh_bit(標志位)來標識,當msgh_bit設置為MACH_MSGH_BITS_COMPLEX時,表示該消息為復雜消息。復雜消息與簡單消息結構差異處在于header后面接著一個描述符計數字段(msgh_descrptor_count),然后是一個接一個的串行化的描述符,最后才是data。之所以前面說復雜消息帶有"附件",附件就是mach_msg_type_descriptor_t,在message.h可以看到它的定義

typedef struct
{
  natural_t         pad1;
  mach_msg_size_t       pad2;
  unsigned int          pad3 : 24;
  mach_msg_descriptor_type_t    type : 8;
} mach_msg_type_descriptor_t;

第一個參數相當于"附件",第二個參數是附件的大小,第四個參數代表了附件的類型。定義如下:

typedef unsigned int mach_msg_descriptor_type_t;

#define MACH_MSG_PORT_DESCRIPTOR        0
#define MACH_MSG_OOL_DESCRIPTOR         1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR       2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR    3
type 用途
MACH_MSG_PORT_DESCRIPTOR 傳遞一個端口權限
MACH_MSG_OOL_DESCRIPTOR 傳遞out-of-line數據
MACH_MSG_OOL_PORTS_DESCRIPTOR 傳遞out-of-line端口
MACH_MSG_OOL_VOLATILE_DESCRIPTOR 傳遞有可能變化的out-of-line數據

這里要說明的是mach_msg_type_descriptor_t只是相當于一個基類,Mach還提供了其他更加具體的結構體供我們使用。例如:

typedef struct
{
  uint64_t          address;//指向數據的指針
  boolean_t             deallocate: 8;//發送后是否接觸分配
  mach_msg_copy_options_t       copy: 8;//復制指令
  unsigned int          pad1: 8;//預留
  mach_msg_descriptor_type_t    type: 8;
  mach_msg_size_t           size;//在address處數據的大小
} mach_msg_ool_descriptor64_t;

這是64位out-of-line數據可以使用的"附件包",message.h中還有其他結構體,這里就不再贅述了。

前面說了那么多out-of-line,還沒說out-of-line是什么。out-of-line是Mach消息的一項重要特性,允許添加指向各種數據的分散指針,就像附件一樣。簡單來說,OOL描述符描述了要附加的數據的地址的大小以及如何處理數據的指令,還有復制選項。常用于傳遞大塊數據,并且能夠避免進行昂貴的復制操作。

端口

端口是一個32位的整型標識符,消息在端口之間傳遞。消息從某一個端口發送到另一個端口,每一個端口都可以接收來自任意發送者的消息,但是每一個消息只能有一個接收者。向一個端口發送消息實際上是將消息放在隊列中,直到消息被處理。

所有的Mach原生對象都是通過端口訪問的,換句話說,我們要查找一個對象的句柄(標識應用程序中的不同對象和同類中的不同的實例的值),實際上查找的是這個對象端口的句柄。

消息傳遞實現

用戶態的Mach消息傳遞使用的是Mach_msg()函數,這個函數通過內核的Mach陷阱把自己從用戶態陷入內核態。函數原型如下:

mach_msg_return_t   mach_msg(
                    mach_msg_header_t *msg,
                    mach_msg_option_t option,
                    mach_msg_size_t send_size,
                    mach_msg_size_t rcv_size,
                    mach_port_name_t rcv_name,
                    mach_msg_timeout_t timeout,
                    mach_port_name_t notify);

無論對于發送還是接收,使用的都是Mach_msg()。

  1. 發送消息

    發送消息的步驟如下所示:

    • 調用current_space()獲取當前的IPC空間。
    • 調用current_map()獲取虛擬空間
    • 消息大小正確性檢查
    • 計算要分配的消息大小
    • 通過ipc_kmsg_alloc分配消息
    • 復制消息
    • 復制消息關聯的端口權限,然后通過ipc_kmsg_copyin將所有的out-of-line數據的內存復制到當前虛擬空間。(如果不復制權限可能導致無法訪問數據)
    • 調用ipc_kmsg_send()發送消息
      • 獲得msgh_remote_port引用并鎖定端口
      • 調用ipc_mqueue_send(),將消息直接復制到端口的ipc_messages隊列中并喚醒等待的線程。
  2. 接收消息

    接受消息的步驟如下所示:

    • 調用current_space()獲取當前的IPC空間。
    • 調用current_map()獲取虛擬空間
    • 調用ipc_mqueue_copyin()獲取IPC隊列。
    • 調用ipc_mqueue_receive()從隊列中取出消息
    • 執行
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容