iOS体系的前史

Mac OS X交融了Mac OS Classic和NextStep的优点:Mac OC Classic的GUI以及NextStep的架构。

全新的Mac OS X在设计与完成上都和NextStep十分挨近,比方Cocoa、Mach、Interface Builder等中心组件都源于NextStep。

iOS开始被称为iPhone OS,它是OS X对应移动渠道的分支,本质上iOS便是Mac OS X。而iOS也是iPad OS,tvOS,watchOS这三者的根底。正由于本质上iOS便是Mac OS X,所以iOS具有和和Mac OS一样的操作体系层次结构以及相同的操作体系中心Dawin。

iOS体系的架构

Apple在关于OS X以及iOS体系架构的文档中,展现了十分简练的分层,某种意义上,乃至有些过于简略

  • The User ExperienceLayer(用户UI层)

包含Aqua,Dashboard,Spotlight以及一些特性。在iOS中,用户体验彻底取决于SpringBoard,一起, iOS中Spotlight也是支撑的。

  • The Application Frameworks layer(运用结构层)

包含Cocoa,Carbon以及Java。然而在iOS中,只要Cocoa(严厉来讲,Cocoa Touch,Cocoa的派生物)

  • The Core Frameworks(中心结构层)

有时也被称为图形和媒体层(Graphic and Media layer)。包含中心结构,Open GL以及Quick Time。

  • Darwin(体系中心层)

操作体系中心——kernel以及UNIX shell的环境。

在以上的这些层级中,Darwin是彻底开源的,而顶部的其他层级都是闭源的,Apple坚持专利。iOS 和 Mac OS整体上是十分像的,可是仍是有一些纤细的不同。比方iOS运用的是Spring Board而OS X运用的是Aqua,由于前者是针对触屏操作,而后者针对的是鼠标操作。假如深化的看看Darwin,能够得到如下结构:

叁:RunLoop中的消息传递机制

要明确的是Darwin的中心是XNU内核。它是一个混合内核,将宏内核和微内核两者的特点兼收并蓄: 比方为微内核中提高操作体系模块化程度,以及内存保护和音讯传递的机制;还有宏内核在高负荷下表现的高性能。XNU主要是由Mach,BSD,以及IOKit组成的。

上面这张图提出了一个问题:在什么时分会产生用户态和内核态的切换? 用户态和内核态的区别是十分明显的,可是运用会频频去运用内核服务,所以这两种态(用户态和内核态)之间的转化就需求一种高效的 且安全的办法。在XNU内核中用户态和内核态的切换有两种情况: **其一是主动切换:当运用需求内核服务的时分,它会建议对内核态的调用。经过预先设定好的硬件指令,从用户态到内核态的转化就会产生。这些服务称为*system calls。 其二是被动切换:***当某个履行反常,中断等产生时,代码的履行就会被暂停。控制权就会转移给内核态的错误预处理机制或许中断路由服务(ISR:interrupt service routine)

XNU主要的中心其实是Mach,它作为微内核,只处理操作体系最根底的一些责任,供给了进程和线程的笼统、虚拟内存的办理、使命调度、**进程间通讯(IPC)**这些根本的功用。而XNU露出给用户的是BSD层,这一层对下在一些底层的功用上运用了Mach,对上,它给运用供给了盛行的POSIX API,这也使得OSX体系对于许多其他的UNIX完成是兼容的。

Mach只具备有限的API,它并不是要成为一个五脏俱全的操作体系,它仅仅供给一些根本的功用,没有它,那么操作体系也无法作业。而一些其他的功用比方文件办理以及设备拜访,都是由它的上一层也便是BSD层来处理的,这一层供给了一些更高层级的笼统,比方The POSIX线程模型(Pthread),文件体系,网络等功用。

Mach

Mach具有一个很简略的概念:一个最小的中心支撑一个面向目标的模型,其中的子体系经过Message彼此通讯。其他的操作体系都是供给了一个完好的模型,而Mach供给了一个根本的模型,能够在此根底上完成操作体系自身,OS X的XNU是Mach之上的一个特别完成。

**在Mach中,全部都被视为目标。**进程(Mach中称为tasks),线程以及虚拟内存都是目标,每一个都有它的特点。可是这个并不是值得大书特书的地方,由于其他的操作体系也能够运用目标来完成。真实让Mach不同的是它挑选经过音讯传递(Message Passing)来完成目标之间的通讯。

所以Mach最根底的概念便是两个端点(Port)中交换的**message,**这便是Mach的IPC(进程间通讯)的中心。

Mach中的音讯,界说在<mach/message.h>文件中,简略来说,一个message便是msgh_size大小的blob, 带着一些flags,从一个端口发送到另一个端口。

typedef struct {
    mach_msg_header_t header;
    mach_msg_body_t   body;
} mach_msg_base_t;
// 音讯头是有必要的,它界说了一个音讯所需求的数据
typedef struct {
    mach_msg_bites_t   msgh_bits;
    mach_msg_size_t    msgh_size;
    mach_port_t        msgh_remote_port;
    mach_port_t        msgh_local_port;
    mach_msg_size_t    msgh_reserved;
    mach_msg_id_t      msgh_id;
} mach_msg_header_t;

Mach Message发送和接纳音讯都运用了同样的API:mach_msg()。**这个办法在用户态和内核态都有完成。**它经过option参数来决定是收音讯,仍是发音讯。

mach_msg_return_t mach_msg(mach_msg_header_t            msg,f
                           mach_msg_option_t            option,
                           mach_msg_size_t              send_size,
                           mach_msg_size_t              reveive_limit,
                           mach_port_t                  reveive_name,
                           mach_msg_timeout_t           timeout,
                           mach_port_t                  notify,
                           );

在发送音讯或许接纳音讯的时分,在用户态中Mach message运用了mach_msg() ,它会经过内核的Mach trap机制调用对应的内核办法mach_msg_trap(),。而这个mach_msg_trap()会调用到mach_msg_overwrite_trap(), 这个办法经过MACH_SEND_MSG或许是MACH_RCV_MSG的flag来决定是发送操作,仍是承受操作。

叁:RunLoop中的消息传递机制

详细关于mach_msg_trap()如何作业的,能够看Apple开源的xnu中关于mach的源码。一起本文中的大量关于体系和架构中的知识点均参考自《Mac OS X and iOS Internals To the Apples Core》。

RunLoop承受音讯

接下来咱们回到RunLoop,首要问一个问题:RunLoop中是如何完成被唤醒的呢?

从源码中可知,在RunLoop即将进入休眠状态之后,它会调用**CFRunLoopServiceMachPort()办法,而这个办法内部会调用mach_msg()办法。所以RunLoop的唤醒便是经过mach_msg()**办法来承受port或许port set的音讯,被唤醒后接着再处理相应的使命。以下是这两个办法的界说:

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port,
                                          mach_msg_header_t **buffer,
                                          size_t buffer_size,
                                          mach_port_t *livePort,
                                          mach_msg_timeout_t timeout,
                                          voucher_mach_msg_state_t *voucherState,
                                          voucher_t *voucherCopy);
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        receive_limit,
                     mach_port_t             receive_name,
                     mach_msg_timeout_t           timeout,
                     mach_port_t                   notify);

mach_msg办法上述已经说到过了,它既用于发送音讯,也用于承受音讯。而在Runloop的这个实际运用场景下,它只用于承受音讯。以下是对这个办法中的各个参数意义的解释:

msg: 是mach_msg用于发送和承受音讯的音讯缓冲区
option: Message的options是位值,按位或来结合。应该运用MACH_SEND_MSG和MACH_RCV_MSG中的一种或两种。
send_size: 当发送音讯时,指定要发送的message buffer的大小。不然便是零。
receive_limit: 当承受音讯时,指定承受的message buffer的大小。不然便是零。
receive_name:当承受音讯时,指定了端口或许端口集。音讯便是从receive_name指定的端口中承受的。不然便是MACH_PORT_NULL。
timeout:当运用MACH_SEND_TIMEOUT或许MACH_RCV_TIMEOUT选项时,指定抛弃前需求等待的时刻(单位为毫秒),不然便是MACH_MSG_TIMEOUT_NONE。
notify: 当运用MACH_SEND_CANCEL,MACH_RCV_NOTIFY和MACH_SEND_NOTIFY选项时,指定用于notification的端口。不然便是MACH_PORT_NULL

mach_msg调用用于承受和发送mach音讯,它是用相同的缓冲区去来发送和承受音讯,也便是msg参数对应的音讯缓冲区。

typedef struct {
    mach_msg_bites_t   msgh_bits;
    mach_msg_size_t    msgh_size;
    mach_port_t        msgh_remote_port;
    mach_port_t        msgh_local_port;
    mach_msg_size_t    msgh_reserved;
    mach_msg_id_t      msgh_id;
} mach_msg_header_t;

音讯接纳

当承受音讯的时分,实际上是使来着端口的音讯出音讯行列。receive_name指定了要从中承受音讯的端口或许端口集。

假如指定了端口(port),那么调用者有必要具有该端口的权限,并且该端口不能是端口集的成员。假如没有任何音讯,那么调用会被堵塞,根据MACH_RCV_TIMEOUT选项来决定抛弃等待的机遇。

假如指定了端口集(port set),那么调用者将接纳到发送到任何端口成员的音讯。端口集没有成员是允许的,并且能够在端口集接纳的过程中增加和删去端口。而接纳到的音讯头中的magh_local_port字段指定音讯来着端口会集的哪个端口。

接下来咱们再回到RunLoop中的源码调用中,来看这个办法的调用:

// ** 首要是外层
// 1、处理Source1事情的时分,调用了该办法
CFRunLoopServiceMachPort(dispatchPort,
                         &msg,
                         sizeof(msg_buffer),
                         &livePort,
                         0,
                         &voucherState,
                         NULL)
// 2、进入休眠状态的时分,调用了该办法
CFRunLoopServiceMachPort(waitSet,
                         &msg,
                         sizeof(msg_buffer),
                         &livePort,
                         poll ? 0 : TIMEOUT_INFINITY,
                         &voucherState,
                         &voucherCopy);
// ** 然后是里层
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port,
                                          mach_msg_header_t **buffer,
                                          size_t buffer_size,
                                          mach_port_t *livePort,
                                          mach_msg_timeout_t timeout,
                                          voucher_mach_msg_state_t *voucherState,
                                          voucher_t *voucherCopy) {
    for(;;) {
        
        ret = mach_msg(msg,
               MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)| MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
               0,
               msg->msgh_size,
               port,
               timeout,
               MACH_PORT_NULL);
        
    }
}

端口接纳:dispatchPort

也便是说在处理source1事情的时分,需求承受的音讯是从dispatchPort端口的音讯行列中承受的,而这个端口:dispatchPort = _dispatch_get_main_queue_port_4CF(),所以这儿只处理GCD的主行列的事情,一起这儿CFRunLoopServiceMachPorttimeout参数为0,这意味着,假如没有收到音讯,那它就直接抛弃而不会继续等待了,这也符合RunLoop的运行逻辑。

端口集接纳:waitSet

而在进入休眠状态时,CFRunLoopServiceMachPortport参数是waitSet,这个参数会传递到内部的mach_msg()函数的receive_name参数,这表明它是从这个端口会集承受音讯的。那么waitSet包含哪些端口呢?

在__CFRunLoopRun函数中有:
...
dispatchPort = _dispatch_get_main_queue_port_4CF();
__CFPortSet waitSet = rlm -> _portSet;
CFPortSetInsert(dispatchPort, waitSet);
...
那么rlm中的_portSet呢?在__CFRunLoopFindMode函数中

mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
__CFPortSetInsert(queuePort, rlm->_portSet);
__CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
__CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);

在CFRunLoopAddSource办法中:
CFPortSetInsert(src_port, rlm->_portSet);// source1

至此,咱们能够确定Apple关于RunLoop文档中,将RunLoop唤醒的几种事情了:

1、根据Port的source事情

2、timer到时刻了

3、runloop要超时了

4、runloop被显式唤醒了

那么RunLoop又是如何判别是由那个Port承受到的音讯呢?在CFRunLoopServiceMachPort函数中,当成功承受到音讯后,会将livePort赋值为**msg->msgh_local_portmsgh_local_port**便是端口会集承受音讯的那个端口,而后RunLoop判别livePort的端口,然后决定处理不同的唤醒事情。

__CFRunLoopRun() {
    
    if (MACH_PORT_NULL == livePort) {
        
    } else if (livePort == rl->_wakeUpPort) {
        
    } else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
        
    } else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort){
        
    } else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort){
        
    } else if (livePort == dispatchPort) {
        
    } else {
    }
    
}

端口接纳的是什么?

上述的描绘比较明确的是一个端口接纳到到音讯是会放在了端口的音讯行列中,那么这个音讯行列是如何完成的呢?从安卓中的looper中能够看到它们运用了链表来办理这种音讯行列的,其实在iOS的xnu(x is not Unix)内核底层也是经过双向链表的办法来联系的音讯的,在**mach_msg_overwrite_trap** 办法中接纳音讯的时分,最终都会将音讯存储到**ipc_msg**中,而这个ipc_msg 便是一个双向链表的节点, 源码如下:

struct ipc_kmsg {
	mach_msg_size_t            ikm_size;
	struct ipc_kmsg            *ikm_next;        /* next message on port/discard queue */
	struct ipc_kmsg            *ikm_prev;        /* prev message on port/discard queue */
	mach_msg_header_t          *ikm_header;
	ipc_port_t                 ikm_prealloc;     /* port we were preallocated from */
	ipc_port_t                 ikm_voucher;      /* voucher port carried */
	mach_msg_priority_t        ikm_qos;          /* qos of this kmsg */
	mach_msg_priority_t        ikm_qos_override; /* qos override on this kmsg */
	struct ipc_importance_elem *ikm_importance;  /* inherited from */
	queue_chain_t              ikm_inheritance;  /* inherited from link */
	sync_qos_count_t sync_qos[THREAD_QOS_LAST];  /* sync qos counters for ikm_prealloc port */
	sync_qos_count_t special_port_qos;           /* special port qos for ikm_prealloc port */
#if MACH_FLIPC
	struct mach_node           *ikm_node;        /* Originating node - needed for ack */
#endif
};

参考

1、mach_msg

2、Mach Message Call

3、深化理解RunLoop

4、Apple文档《Threading Programming Guide》

5、《Mac OS X and iOS Internals To the Apples Core》一书

6、opensource.apple.com开源代码

  • 我正在参与技能社区创作者签约方案招募活动,点击链接报名投稿。