目錄
- 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()。
-
發送消息
發送消息的步驟如下所示:
- 調用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隊列中并喚醒等待的線程。
-
接收消息
接受消息的步驟如下所示:
- 調用current_space()獲取當前的IPC空間。
- 調用current_map()獲取虛擬空間
- 調用ipc_mqueue_copyin()獲取IPC隊列。
- 調用ipc_mqueue_receive()從隊列中取出消息
- 執行