iOS系统和内核(XNU)架构

iOS 系统架构

image.png

从上到下分为四层:

  • 用户体验层
  • 应用框架层,是App开发会用到的
  • 核心框架层。
  • Darwin 层,是iOS系统的核心,属于内核态。

Darwin 的内核是 XNU,XNU 是在 UNIX 的基础上做了很多改进以及创新。

XNU 内核架构

image.png

  • 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 文件。

苹果公司已经将 darwin_xnu 开源,放到Github了, 地址是 darwin-xnu

整个 fork 进程,加载解析 Mach-O 文件的过程可以在 XNU 的源代码中查看,代码路径是 darwin-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, 后续流程就和内核态darwin_xnu 没有关系, dyld 流程可以参考 iOS启动优化-dyld4流程介绍

猜你喜欢

转载自juejin.im/post/7255902957035438138
今日推荐