成都ios培训优化App的启动时间

上传人:宝路 文档编号:7671218 上传时间:2017-09-23 格式:DOCX 页数:11 大小:103.22KB
返回 下载 相关 举报
成都ios培训优化App的启动时间_第1页
第1页 / 共11页
成都ios培训优化App的启动时间_第2页
第2页 / 共11页
成都ios培训优化App的启动时间_第3页
第3页 / 共11页
成都ios培训优化App的启动时间_第4页
第4页 / 共11页
成都ios培训优化App的启动时间_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《成都ios培训优化App的启动时间》由会员分享,可在线阅读,更多相关《成都ios培训优化App的启动时间(11页珍藏版)》请在金锄头文库上搜索。

1、 达内教育 中国 IT 培训领导品牌成都 ios 培训:优化 App 的启动时间这是一篇学习笔记,从原理到实践讲述了如何优化 App 的启动时间。App 运行理论main() 执行前发生的事Mach-O 格式虚拟内存基础Mach-O 二进制的加载理论速成Mach-O 术语Mach-O 是针对不同运行时可执行文件的文件类型。文件类型:Executable: 应用的主要二进制Dylib: 动态链接库(又称 DSO 或 DLL)Bundle: 不能被链接的 Dylib,只能在运行时使用 dlopen() 加载,可当做 macOS 的插件。Image: executable,dylib 或 bundl

2、eFramework: 包含 Dylib 以及资源文件和头文件的文件夹 达内教育 中国 IT 培训领导品牌Mach-O 镜像文件Mach-O 被划分成一些 segement,每个 segement 又被划分成一些 section。segment 的名字都是大写的,且空间大小为页的整数。页的大小跟硬件有关,在 arm64 架构一页是 16KB,其余为 4KB。section 虽然没有整数倍页大小的限制,但是 section 之间不会有重叠。几乎所有 Mach-O 都包含这三个段(segment): _TEXT,_DATA 和 _LINKEDIT:_TEXT 包含 Mach header,被执行的

3、代码和只读常量( 如 C 字符串)。只读可执行(r-x)。_DATA 包含全局变量,静态变量等。可读写(rw-)。_LINKEDIT 包含了加载程序的元数据,比如函数的名称和地址。只读(r )。Mach-O Universal 文件FAT 二进制文件,将多种架构的 Mach-O 文件合并而成。它通过 Fat Header 来记录不同架构在文件中的偏移量,Fat Header 占一页的空间。按分页来存储这些 segement 和 header 会浪费空间,但这有利于虚拟内存的实现。虚拟内存虚拟内存就是一层间接寻址(indirection)。软件工程中有句格言就是任何问题都能通过添加一个间接层来解

4、决。虚拟内存解决的是管理所有进程使用物理 RAM 的问题。通过添加间接层来让每个进程使用逻辑地址空间,它可以映射到 RAM 上的某个物理页上。这种映射不是一对一的,逻辑地址可能映射不到 RAM 上,也可能有多个逻辑地址映射到同一个物理 RAM 上。针对第一种情况,当进程要存储逻辑地址内容时会触发 page fault;第二种情况就是多进程共享内存。 达内教育 中国 IT 培训领导品牌对于文件可以不用一次性读入整个文件,可以使用分页映射(mmap()的方式读取。也就是把文件某个片段映射到进程逻辑内存的某个页上。当某个想要读取的页没有在内存中,就会触发 page fault,内核只会读入那一页,实

5、现文件的懒加载。也就是说 Mach-O 文件中的 _TEXT 段可以映射到多个进程,并可以懒加载,且进程之间共享内存。_DATA 段是可读写的。这里使用到了 Copy-On-Write 技术,简称 COW。也就是多个进程共享一页内存空间时,一旦有进程要做写操作,它会先将这页内存内容复制一份出来,然后重新映射逻辑地址到新的 RAM 页上。也就是这个进程自己拥有了那页内存的拷贝。这就涉及到了 clean/dirty page 的概念。dirty page 含有进程自己的信息,而 clean page 可以被内核重新生成 (重新读磁盘)。所以 dirty page 的代价大于 clean page。

6、Mach-O 镜像 加载所以在多个进程加载 Mach-O 镜像时 _TEXT 和 _LINKEDIT 因为只读,都是可以共享内存的。而 _DATA 因为可读写,就会产生 dirty page。当 dyld 执行结束后,_LINKEDIT 就没用了,对应的内存页会被回收。安全ASLR(Address Space Layout Randomization):地址空间布局随机化,镜像会在随机的地址上加载。这其实是一二十年前的旧技术了。代码签名:可能我们认为 Xcode 会把整个文件都做加密 hash 并用做数字签名。其实为了在运行时验证 Mach-O 文件的签名,并不是每次重复读入整个文件,而是把每

7、页内容都生成一个单独的加密散列值,并存储在 _LINKEDIT 中。这使得文件每页的内容都能及时被校验确并保不被篡改。从 exec() 到 main()exec() 是一个系统调用。系统内核把应用映射到新的地址空间,且每次起始位置都是随机的(因为使用 ASLR)。并将起始位置到 0x000000 这段范围的进程权限都标记为不可读写不可执行。如果是 32 位进程,这个范围至少是 4KB;对于 64 位进程则至少是 4GB。NULL 指针引用和指针截断误差都是会被它捕获。 达内教育 中国 IT 培训领导品牌dyld 加载 dylib 文件Unix 的前二十年很安逸,因为那时还没有发明动态链接库。有

8、了动态链接库后,一个用于加载链接库的帮助程序被创建。在苹果的平台里是 dyld,其他 Unix 系统也有 ld.so。 当内核完成映射进程的工作后会将名字为 dyld 的 Mach-O 文件映射到进程中的随机地址,它将 PC 寄存器设为 dyld 的地址并运行。dyld 在应用进程中运行的工作是加载应用依赖的所有动态链接库,准备好运行所需的一切,它拥有的权限跟应用一样。下面的步骤构成了 dyld 的时间线:Load dylibs - Rebase - Bind - ObjC - Initializers加载 Dylib从主执行文件的 header 获取到需要加载的所依赖动态库列表,而 head

9、er 早就被内核映射过。然后它需要找到每个 dylib,然后打开文件读取文件起始位置,确保它是 Mach-O 文件。接着会找到代码签名并将其注册到内核。然后在 dylib 文件的每个 segment 上调用 mmap()。应用所依赖的 dylib 文件可能会再依赖其他 dylib,所以 dyld 所需要加载的是动态库列表一个递归依赖的集合。一般应用会加载 100 到 400 个 dylib 文件,但大部分都是系统 dylib,它们会被预先计算和缓存起来,加载速度很快。Fix-ups在加载所有的动态链接库之后,它们只是处在相互独立的状态,需要将它们绑定起来,这就是 Fix-ups。代码签名使得我

10、们不能修改指令,那样就不能让一个 dylib 的调用另一个 dylib。这时需要加很多间接层。现代 code-gen 被叫做动态 PIC(Position Independent Code),意味着代码可以被加载到间接的地址上。当调用发生时,code-gen 实际上会在 _DATA 段中创建一个指向被调用者的指针,然后加载指针并跳转过去。 达内教育 中国 IT 培训领导品牌所以 dyld 做的事情就是修正(fix-up)指针和数据。 Fix-up 有两种类型,rebasing 和 binding。Rebasing 和 BindingRebasing:在镜像内部调整指针的指向Binding:将指

11、针指向镜像外部的内容可以通过命令行查看 rebase 和 bind 等信息:xcrun dyldinfo -rebase -bind -lazy_bind myapp.app/myapp通过这个命令可以查看所有的 Fix-up。rebase,bind,weak_bind,lazy_bind 都存储在 _LINKEDIT 段中,并可通过 LC_DYLD_INFO_ONLY 查看各种信息的偏移量和大小。建议用 MachOView 查看更加方便直观。从 dyld 源码层面简要介绍下 Rebasing 和 Binding 的流程。ImageLoader 是一个用于加载可执行文件的基类,它负责链接镜像,

12、但不关心具体文件格式,因为这些都交给子类去实现。每个可执行文件都会对应一个 ImageLoader 实例。ImageLoaderMachO 是用于加载 Mach-O 格式文件的 ImageLoader 子类,而 ImageLoaderMachOClassic 和 ImageLoaderMachOCompressed 都继承于 ImageLoaderMachO,分别用于加载那些 _LINKEDIT 段为传统格式和压缩格式的 Mach-O 文件。因为 dylib 之间有依赖关系,所以 ImageLoader 中的好多操作都是沿着依赖链递归操作的,Rebasing 和 Binding 也不例外,分别

13、对应着 recursiveRebase() 和 recursiveBind() 这两个方法。因为是递归,所以会自底向上地分别调用 doRebase() 和 doBind()方法,这样被依赖的 dylib 总是先于依赖它的 dylib 执行 Rebasing 和 Binding。传入 doRebase() 和 doBind() 的参数包含一个 LinkContext 上下文,存储了可执行文件的一堆状态和相关的函数。 达内教育 中国 IT 培训领导品牌在 Rebasing 和 Binding 前会判断是否已经 Prebinding。如果已经进行过预绑定(Prebinding),那就不需要 Reba

14、sing 和 Binding 这些 Fix-up 流程了,因为已经在预先绑定的地址加载好了。ImageLoaderMachO 实例不使用预绑定会有五个原因:Mach-O Header 中 MH_PREBOUND 标志位为 0镜像加载地址有偏移(这个后面会讲到 )依赖的库有变化镜像使用 flat-namespace,预绑定的一部分会被忽略LinkContext 的环境变量禁止了预绑定ImageLoaderMachO 中 doRebase() 做的事情大致如下:如果使用预绑定,fgImagesWithUsedPrebinding 计数加一,并 return;否则进入第二步如果 MH_PREBOUN

15、D 标志位为 1(也就是可以预绑定但没使用),且镜像在共享内存中,重置上下文中所有的 lazy pointer。( 如果镜像在共享内存中,稍后会在 Binding 过程中绑定,所以无需重置)如果镜像加载地址偏移量为 0,则无需 Rebasing,直接 return;否则进入第四步调用 rebase() 方法,这才是真正做 Rebasing 工作的方法。如果开启 TEXT_RELOC_SUPPORT 宏,会允许 rebase() 方法对_TEXT 段做写操作来对其进行 Fix-up。所以其实 _TEXT 只读属性并不是绝对的。ImageLoaderMachOClassic 和 ImageLoad

16、erMachOCompressed 分别实现了自己的 doRebase() 方法。实现逻辑大同小异,同样会判断是否使用预绑定,并在真正的 Binding 工作时判断 TEXT_RELOC_SUPPORT 宏来决定是否对 _TEXT 段做写操 达内教育 中国 IT 培训领导品牌作。最后都会调用 setupLazyPointerHandler 在镜像中设置 dyld 的 entry point,放在最后调用是为了让主可执行文件设置好 _dyld 或_program_vars。Rebasing在过去,会把 dylib 加载到指定地址,所有指针和数据对于代码来说都是对的,dyld 就无需做任何 fix-up 了。如今用了 A

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号