阅读 87

iOS启动过程

背景

app是怎样运行起来的?
点击app图标到看到首页都发生了什么?
app如何快速启动?
......

启动app快速展示首页,会给用户带来极好的体验,头条、抖音系列的产品做得都很好,作者也是其深度用户,这得益于他们有一套系统的方法论并且在不断的演化中,本文会阐述整个启动过程以及快启优化的核心思想

装载过程

为何需要装载?
CPU只能处理被装载到内存中的指令和数据

我们编写好的app程序被编译为可执行二进制文件,其内部包含了操作指令和数据集合,是确确实实存储在手机上的物理文件,应用启动之初,操作系统会为其创建一个进程,iOS一个应用只有一个进程,早期的装载过程十分暴力,直接把可执行二进制从磁盘拷贝到内存中,随着硬件的发展,多用户、虚拟存储的操作系统被发明出来,装载过程得到很大的优化,现代操作系统页映射被广泛应用为动态装载方案,内存也得到了进一步的解放

页映射是虚拟存储机制的一部分,将内存和所有磁盘中的数据和指令按照为单位划分多个页,所有装载和操作的单位就是页,常见的处理器一般都是用4096个字节大小的页,核心思想是用时载入内存页,不用时候写入物理页,内存紧张时候释放长时间没有操作的内存页

程序能得以执行可以被简单的归纳为如下三步:执行者都是操作系统

  • 创建一个进程,开辟一个独立的虚拟地址空间
  • 读取可执行二进制的文件头(iOS编译过程这篇有说明文件头的结构与作用),建立虚拟地址与可执行二进制的映射关系
  • 将CPU的指令寄存器设置成可执行二进制文件的入口地址并把控制权交给进程

同时堆栈从内核态切换为用户态,CPU运行权限由操作系统切换为进程,以上是装载过程,自此程序开始运行,首先加载动态链接器,然后动态链接器负责链接所有动态库与可执行文件

动态链接过程

为何需要动态链接?
如上的装载过程已经把可执行二进制加载到内存中,我们知道在编译的最后阶段,静态链接器会将静态库和可执行文件机器码一起链接为一个Mach-O格式的文件,可以说如果没有动态库的存在,就不需要动态链接器,也就没有动态链接过程了,那么静态库有哪些不足之处一定要设计动态库呢?

  • 空间浪费,静态链接时,静态库中被引用的部分会被拷贝到可执行文件中,被多个可执行文件引用就有多份冗余拷贝
  • 更新部署发布费事,动态库可以单独更新发布,而静态库需要与可执行程序一起链接为一个文件才能发布

当然在iOS中除了系统库如UIKit,是不存在真正意义上的动态库的,每个程序包都需要有一份自研动态库的拷贝,系统动态库配合共享缓存可以实现多进程共享一份动态库,苹果研发的动态链接器命名为Dyld(全称Dynamic link editor),被集成在操作系统中

动态链接就是运行时在内存中把动态库与可执行文件链接起来的过程

Dyld的源码已经开源,有兴趣的可以下载下来看看,主要流程如下:

Rebase:
装载过程已经将可执行二进制加载到内存,然后Dyld负责初始化可执行二进制,递归加载所有动态库,然后进行rebase,操作系统在进程在创建之初会为其随机开辟一个独立的虚拟地址空间 ,这样函数和变量的地址就需要通过虚拟地址的起始位置加上偏移量才能得到其真实地址,这个过程就是rebase

Bind:
变量及函数被编译器称为符号其值就是对应的地址,定义在动态库A中的符号在动态库B中是无法得到真实地址的,编译时符号的值用0填充,待到动态链接的时候由链接器在内存中进行修正,这个过程叫做重定位,递归修正所有符号值的过程就是bind过程,此过程严格依赖符号表

Objc setup:
进行Objc的初始化,包括注册Objc类、检测selector的唯一性、插入分类方法等运行时初始化工作

Initializers:
上一步注册的所有类执行+load方法、调用 C++构造器(用 attribute((constructor)) 修饰的函数)、创建非基本类型的C++静态、全局变量等(基础类型的静态、全局变量编译时在可执行二进制中就分配了存储,装载的时候直接分配了内存)

最后:
Dyld会调用main函数,main会调用UIApplicationMain,UIApplicationMain调用didFinishLaunchingWithOptions函数,接着是业务流程

启动优化

动态链接阶段

动态库数量、ObjC类数量,分类数量,C++的constructor数量,C++静态、全局对象数量,它们越多启动越耗时,因此能减少的减少,不能减少的合并就是优化的方向,main函数之后呢,一句话:减少didFinishLaunchingWithOptions函数中同步执行的逻辑

以上是动态链接阶段的优化,如果你已经做了上面的所有事情,那么可以试试优化装载阶段,对于大型app来讲会有不小的收益

装载阶段

我们知道app和动态库的二进制的装载过程会严格按照各自的静态链接顺序执行,装载过程会通过缺页中断(Page Fault)的方式申请物理内存,而中断次数越多就越耗时,因此我们可以通过合并缺页中断的方式来优化启动时间,具体方式是通过clang插桩的方式获取启动时的所有符号,然后进行二进制重排,将启动时需要调用的函数放到一起,具体可参考抖音团队这位道友的文章

文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐