iOS 体系架构
从上到下分为四层:
- 用户体会层
- 运用结构层,是
App开发
会用到的 - 中心结构层。
- Darwin 层,是iOS体系的
中心
,归于内核态。
Darwin 的内核是 XNU,XNU 是在 UNIX
的基础上做了许多改善以及立异。
XNU 内核架构
- XNU 由
Mach
、BSD
、IOKit(驱动API)
组成。 - Mach 和 BSD 各自担任如图所示的体系不同的作业
Mach
Mach 是微内核
,微内核能够进步体系的模块化
程度,供给内存保护
的音讯传递机制。
BSD
BSD是对 Mach再次封装
的宏内核
, 供给了更现代、更易用的内核接口
宏内核
也叫单内核,性能更高, 在高负荷状况时依然保持高效
运作。
BSD符合POSIX标准
IEEE 为了保证软件在各个 UNIX 体系上运转而制定了POSIX
标准,iOS 经过BSD
对 POSIX 的兼容而成为了类 UNIX 体系
。
比方BSD 会构建UNIX进程模型,创建POSIX兼容的线程模型pthread
。
进程
Mach
进程在Mach中表示为Task,Mach Task
是线程履行的环境和容器。
用户态经过 mach_msg_trap() 函数触发圈套,切换至 Mach 内核态,由 Mach 里的 mach_msg() 函数完结进程间通讯
。
Mach 运用 mach_msg_trap() 函数触发圈套来处理反常音讯
,
BSD
进程在BSD中表示为Process,BSD Process
扩展了 Mach Task
,增加
了进程 ID
、信号
信息等,。
BSD 在Mach反常音讯
机制的基础上建立了信号处理
机制,用户态发生的信号会先被 Mach 转换成反常,BSD 将反常再转换成信号
。
线程
Mach Thread 表示一个线程,是 Mach 里的最小履行单位。Mach Thread 有自己的状况,包括机器状况、线程栈、调度优先级、调度战略、内核 Port、反常 Port。
Mach Thread 也能够扩展为 Uthread
,经过 `BSD Process“ 处理。
IOKit
IOKit 是硬件驱动程序
的运转环境,包括电源、内存、CPU 等信息。
IOKit 底层 libkern 运用 C++ 子集 Embedded C++ 编写了驱动程序基类,比方 OSObject、OSArray、OSString 等,新驱动能够继承这些基类来写。
XNU 加载 App
iOS 的可履行文件和动态库都是 Mach-O 格式,所以加载 APP 实际上便是加载 Mach-O 文件。
苹果公司现已将 xnu 开源,放到Github了, 地址是 xnu
整个 fork 进程,加载解析 Mach-O 文件的过程能够在 XNU 的源代码中检查,代码途径是 xnu/bsd/kern/kern_exec.c
,代码如下:
int __mac_execve(proc_t p, struct __mac_execve_args *uap, int32_t *retval)
{
// 字段设置
int is_64 = IS_64BIT_PROCESS(p);
struct vfs_context context;
struct uthread *uthread; // 线程
task_t new_task = NULL; // Mach Task(进程)
context.vc_thread = current_thread();
context.vc_ucred = kauth_cred_proc_ref(p);
// 分配大块堆内存,不用栈是因为 Mach-O 结构很大。
char *bufp = kheap_alloc(KHEAP_TEMP,
sizeof(*imgp) + sizeof(*vap) + sizeof(*origvap), Z_WAITOK | Z_ZERO);
image_params *imgp = (struct image_params *) bufp; // Mach-O参数
// 初始化 imgp 结构里的公共数据
imgp->ip_user_fname = uap->fname; // 可履行程序的文件名
imgp->ip_user_argv = uap->argp; // 参数列表
imgp->ip_user_envv = uap->envp; // 环境列表
uthread = get_bsdthread_info(current_thread()); // 初始化线程
if (uthread->uu_flag & UT_VFORK) {
imgp->ip_flags |= IMGPF_VFORK_EXEC;
in_vfexec = TRUE;
} else {
// 程序如果是发动态,就 fork 新进程
imgp->ip_flags |= IMGPF_EXEC;
// fork 新进程和线程
imgp->ip_new_thread = fork_create_child(current_task(),
NULL, p, FALSE, p->p_flag & P_LP64, TRUE);
new_task = get_threadtask(imgp->ip_new_thread);
context.vc_thread = imgp->ip_new_thread;
}
// 加载解析 Mach-O
error = exec_activate_image(imgp);
if (!error && !in_vfexec) {
p = proc_exec_switch_task(p, current_task(), new_task, imgp->ip_new_thread);
should_release_proc_ref = TRUE;
}
kauth_cred_unref(&context.vc_ucred);
if (!error) {
task_bank_init(get_threadtask(imgp->ip_new_thread));
proc_transend(p, 0);
thread_affinity_exec(current_thread());
// 继承进程处理
if (!in_vfexec) {
proc_inherit_task_role(get_threadtask(imgp->ip_new_thread), current_task());
}
// 设置进程的主线程
thread_t main_thread = imgp->ip_new_thread;
task_set_main_thread_qos(new_task, main_thread);
}
}
由于 Mach-O 文件很大, __mac_execve 函数会先为 Mach-O 分配一大块堆内存
imgp,接下来初始化 imgp 里的公共数据。内存处理完,经过 fork_create_child() 函数 fork 出一个新的进程和线程。新进程 fork 后,会经过 exec_activate_image() 函数解析加载 Mach-O 文件到内存 imgp 里。最后,运用 task_set_main_thread_qos() 函数设置新 fork 出进程的主线程
。
exec_mach_imgact()
经过 load_machfile() 函数加载 Mach-O 文件,依据解析 Mach-O 后得到的 load command 信息,经过映射方法加载到内存中。还会运用 activate_exec_state() 函数处理解析加载 Mach-O 后的结构信息,设置履行 App 的进口点
。
设置完进口点后会经过 load_dylinker() 函数来解析加载 dyld,然后将进口点地址改成 dyld 的进口地址。这一步完后,内核
部分就完结了 Mach-O 文件的加载。剩余的便是用户态
层 dyld
加载 App 了。
XNU加载App完好流程
-
fork 新进程;
-
为 Mach-O 分配内存;
-
解析 Mach-O;
-
读取 Mach-O 头信息;
-
遍历 load command 信息,将 Mach-O 映射到内存;
-
发动
用户态
进程dyld
, 后续流程就和内核态xnu
没有关系, dyld 流程能够参考 iOS发动优化-dyld4流程介绍。