美文网首页
iOS启动原理

iOS启动原理

作者: 梦翔_d674 | 来源:发表于2018-04-08 09:30 被阅读0次

背景

iOS的启动过程一直比较神秘,这方面的资料也不是太多,大多数的资料都来自2016年WWDC的一篇视频,本文的大部分内容来自于视频,算是视频的一个归纳总结再加上自己的一点点感悟吧。

启动的过程

dyld是App的启动器,启动的大部分事情都由dyld完成,iOS的启动大致分为几个部分:

内核将App的执行文件加载到随机地址空间(加载到随机地址主要是因为ASLR技术)

内核将dyld的执行文件加载到随机地址空间

内核执行dyld文件

dyld启动App

dyld加载所有App所依赖的dylibs(动态库)

执行rebasing/binding修复地址

Objc Setup

initialize

dyld调用App中的main(),将主动权交还给App

手机内核只负责将App的执行文件和dyld加载到内存中,然后所有的启动工作都交给了dyld。

dyld加载App依赖的dylibs

dyld拿到App的执行文件后,首先从文件的header中解析出App依赖的dylib列表,找到每一个依赖的dylib。打开并读取dylib文件的起始位置,验证签名,确保dylib没有被篡改。验证签名后,对dylib中的每个segment调用mmap()

segment

一般每个Mach-O文件都会有三个segment:

__TEXT: 一般处于文件的头部位置,包含Mach header,被执行的代码,和只读常量,只读可执行(r-x)。由于不会被更改,所以读到内存中后可复用

__DATA: 包含各种变量,可读写(rw-),由于可以被更改,所以不可复用

__LINKEDIT: 包含函数名称和对应的地址,只读(r--)

mmap()

文件读入内存并不用一次性读入整个文件,它可以使用分页映射(mmap())的方式进行读取。也就是用到哪个segment,再将哪个segment读入内存,实现文件读入的懒加载。

同时同一个Mach-O文件中的segment也可以映射到多个进程,实现进程之间的内存共享。__TEXT和__LINKEDIT段都是只读的,不会有进程对它进行修改,它们是可以让所有进程共享的,大家都使用同一份内容。然而__DATA段却不是这样,__DATA是可读写的,当某一个进程需要对它进行修改时,需要先copy一份出来,映射到新的 RAM 页上。让这个进程拥有自己独立的内存拷贝,进行修改。这就是Copy-On-Write技术,简称COW。

由于__TEXT和__LINKEDIT段可以进程间共享,只需要在第一次使用的时候进行IO操作,后续即可直接使用,所以App在第一次启动时会比较费时,因为所有的segment读取都需要进行IO操作。后续启动,会快很多,很多segment已经映射到内存中,会被缓存起来,二次启动直接使用,不需要进行IO操作,这就有了iOS中冷启动和热启动的概念:

冷启动:新安装App或者手机重启后,第一次启动。手机需要加载所有的segment

热启动:启动过App后,再次启动。内存中缓存的segment可以直接复用。

执行rebasing/binding修复地址

由于App和每个dylib加载到的都是随机地址空间,代码中原来的函数地址跟真实的函数地址会有差异。修复这个差异的过程就是rebasing和binding。其中rebasing主要做的是image内部的修复,binding主要做的是image间的修复。

Rebasing

对于Image内部的函数,假设它的原地址是A,对应当前地址空间下的新地址是B。那么它所有的函数指针都需要加上地址差(B-A)。所有的Rebasing过程就是从__LINKEDIT取出函数指针,修改函数指针,存入__DATA中,供函数调用。(原始的函数指针存在__LINKEDIT中,修改后的数据存在__DATA中)

之前说到,加载文件使用的是mmap技术,__LINKEDIT和DATA段是在第一次使用时才会执行IO操作,加载到内存中。所以Rebasing阶段,耗时主要是在IO操作上。

Binding

image间的函数指针,实际是被符号名称绑定的,为了找到对应的函数实现,dyld需要去符号表中根据符号名称查找,找到后将地址存到__DATA中对应函数指针中。由于IO操作在rebasing阶段已经在做了,所以binding阶段主要耗时在符号表查找的这个过程,这个过程的主要瓶颈在CPU计算上。

Objc Setup

Objc是一门动态语言,为了维持它的动态性,在启动时,需要将类的名称和类的方法都注册起来。Objc Setup阶段,主要是做Class的注册,Method的注册和Category的注册。

一个好的设计模式,一般都推崇写很多类,每个类尽量简单,写很多Category,每个Category都只包含独立模块的方法。但是从启动速度的角度来说,尽量减少类,Category和方法,才会让Objc Setup阶段耗时更少。

initialize

当所有的Class和method都注册过后,系统需要做一些初始化的工作,对于Objective-C而言,主要是需要调用各个类的+load方法,所以项目中应该尽量避免使用+load方法,正常的初始化工作,可以在initialize中实现。StackOverflow上有详细的关于+load和initialize的对比

End

当上面所有阶段执行完成之后,dyld会调用main()函数,将主动权交还给App。之后才会调用到didFinishLaunch中的代码。

上面介绍的启动时间主要是main()函数之前的启动时间,正常这个时间控制在400ms以内就可以算一个启动速度优异的App了。正常我们关注更多的可能是main()函数后didFinishLaunch中代码的执行时间。但是对用户而言,main()函数之前的时间也是启动的一部分。往往这部分时间也不短,所以不能掉以轻心哦~

作者:小笨狼

链接:https://www.jianshu.com/p/4fe773d6da4c#comments

相关文章

  • iOS启动原理(一)

    iOS启动原理(一) iOS启动原理(一)

  • iOS程序执行顺序和UIViewController 的生命周期

    一. iOS程序的启动执行顺序 ** 程序启动顺序图 ** iOS启动原理图.png ** 具体执行流程 ** 程...

  • 深入理解iOS App的启动过程

    前言 参考资料:深入理解iOS App的启动过程iOS 应用程序启动过程及原理总结iOS:App启动过程详解(不同...

  • iOS 性能优化

    iOS App 启动性能优化iOS离屏渲染优化(附DEMO) iOS Objective-C 数组遍历的性能及原理...

  • 2018-05-06

    iOS启动原理 iOS启动 大致可分为t1(pre-main)main方法执行前 和 main之后,也就是didF...

  • iOS启动原理

    背景 iOS的启动过程一直比较神秘,这方面的资料也不是太多,大多数的资料都来自2016年WWDC的一篇视频,本文的...

  • iOS启动原理

    1、main程序入口

  • iOS程序启动原理(下)

    接上篇 iOS程序启动原理(上) 下图是一个iOS程序启动的完整过程 main函数中执行了一个UIApplicat...

  • 2018-05-06

    iOS启动优化。 上一节讲了启动的原理, 现在来说说启动优化; pre-main: 1.减少动态库framewor...

  • 深入理解iOS App的启动过程

    深入理解iOS App的启动过程 前言 启动时间是衡量应用品质的重要指标。 本文首先会从原理上出发,讲解iOS系统...

网友评论

      本文标题:iOS启动原理

      本文链接:https://www.haomeiwen.com/subject/hsqphftx.html