目录
- 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()从队列中取出消息
- 执行
网友评论