iOS启动优化之从exec()到main()

上一篇文章咱们讲到了启动优化须要的一些理论知识,这篇文章咱们讲一下从exec()到main()系统帮咱们作了哪些操做。缓存

什么是exec()?

exec()函数是一个系统调用,当启动一个应用程序的时候,系统内核把应用映射到新的地址空间,且每次起始位置都是随机的(由于使用了ASLR),并将起始位置到0x000000这段范围的进程权限都标记为不可读写不可执行。安全

  • 若是是32位进程,这个范围至少是4KB。
  • 若是是64位进程,至少是4GB。
  • 能够捕捉任何空指针引用。
  • 捕捉任何指针截断。

关于Dylibs

首先,内核加载动态连接库的帮助程序Dyld,让Dyld来启动应用的进程。Dyld的工做是:bash

  • 加载全部依赖的Dylib。
  • 它拥有和应用同样的权限。

Dyld的加载步骤

加载dylibs

  • 从主执行文件的header获取须要加载的依赖库列表。
  • 找到动态库的Mach-O文件。
  • 打开和开始运行每个文件。
  • 确保它是mach-o文件。
  • 找到代码签名并将其注册到内核。
  • 而后在该dylib的每一个segment上调用mmap()。

应用所依赖的dylib文件可能会依赖其它的dylib,因此dyld加载dylib的过程是一个递归的调用过程。ide

  • 加载应用程序全部的直接依赖项
  • 加载每一个dylib所依赖的dylib
  • 清洗和重复
  • 应用通常会加载100-400个dylib文件。
    • 大部分都是系统的dylib。
    • 系统的dylib会预加载和缓存那些dyld要作的工做,加载速度很快。

修复(Fix-ups)

在加载完全部依赖的dylib后,它们是彼此独立的,咱们须要将它们绑在到一块儿,这个过程就是修复。由于代码签名的存在,咱们没法修复指令,那么就不能让一个dylib调用另外一个dylib,这时须要加载更多的中间层。函数

现代的code-gen被称为动态PIC(位置无关代码),能够加载到该地址上,而且是动态的,也就是说地址被间接的分配了。当调用发生时,code_gen会在__DATA段中建立一个指向被调用者的指针,而后加载该指针并跳转过去。优化

因此dyld作的事情就是修正(fix-up)指针和数据,Fix-up有两种类型,rebasing(重设地址)和binding(绑定)。spa

Rebasing指的是在镜像内调整指针,Binding指的是在镜像外调整指针。指针

能够在任何二进制文件上使用dyldinfo指令来查看全部的修复:code

Rebasing

在过去,dyld会把dylib加载到指定的地址,全部指针和数据对于代码来讲都是对的,dyld无需作任何fix-up。现在使用了ASLR会将dylib加载到新的随机地址,这个随机地址跟代码和数据指向的旧地址会有误差,dyld须要修正这个误差(slide),Rebasing就是将dylib内部的指针地址都加上这个偏移量,计算方法以下:cdn

Slide = actual_address - preferred_address
复制代码

当咱们重设地址时,实际上在全部的Data页面上都产生了错误,而后对页面进行修改,就会产生COW(写入时复制),因此重设地址有时会很是昂贵。这可能会产生I/O瓶颈,但由于rebase的顺序是按地址排列的,因此从内核的角度来看这是个有次序的任务,它会进行预读,减小I/O消耗。

Binding

Binding是处理那些指向dylib外部的指针,这些指针经过名称进行绑定,实际上就是个字符串。运行时,dyld须要找到该符号指针的位置,这须要计算,遍历查找符号表,一旦找到,就将该值存储到数据指针里。计算复杂度比Rebasing要高的多,可是I/O不多,由于Rebasing完成了大部分I/O操做。

ObjC

  • 大多数ObjC的设置都是经过rebasing和binding来完成的,好比Class中指向超类的指针和指向方法的指针。
  • ObjC是个动态语言,能够用类的名字来实例化一个类的对象。这意味着ObjC运行时须要维护一张映射类名与类的全局表。当加载dylib时,其定义的全部类都须要注册到这个全局表中。
  • C++中有个问题叫作脆弱的基类问题。ObjC就没有这个问题,由于会在加载时经过fix-up动态类中改变实例变量的偏移量。
  • 能够定义类别,改变另外一个类中的方法。
  • 选择器是惟一的。

Initializers

  • C++编译器生成初始化器来完成那些抽象DATA的初始化。
  • ObjC中经过+load方法来完成该操做,但不建议使用+load方法。若是有+load方法,此时开始运行。
  • 全部的dylib都须要运行初始化器。
  • 从下往上开始运行初始化器,这样能够保证很安全的调用所依赖的内容。
  • 全部的初始化器调用完成以后,Dyld就会调用可执行文件的main()函数。

下期预告:iOS启动优化实践篇

关注公众号,获取更多文章内容

相关文章
相关标签/搜索