iOS 体系架构

iOS系统和内核(XNU)架构

从上到下分为四层:

  • 用户体会层
  • 运用结构层,是App开发会用到的
  • 中心结构层。
  • Darwin 层,是iOS体系的中心,归于内核态。

Darwin 的内核是 XNU,XNU 是在 UNIX 的基础上做了许多改善以及立异。

XNU 内核架构

iOS系统和内核(XNU)架构

  • XNU 由 MachBSDIOKit(驱动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完好流程

  1. fork 新进程;

  2. 为 Mach-O 分配内存;

  3. 解析 Mach-O;

  4. 读取 Mach-O 头信息;

  5. 遍历 load command 信息,将 Mach-O 映射到内存;

  6. 发动用户态进程dyld, 后续流程就和内核态xnu没有关系, dyld 流程能够参考 iOS发动优化-dyld4流程介绍。