iOS App启动流程——理论知识篇

554 阅读4分钟

一、Mach-O结构

1. Mach-O术语:

  • Executable(可执行文件)
  • Dylib(动态库)
  • Bundle —— iOS特有的动态库,不可链接,只能运行时使用dlopen()

Image —— Executable、Dylib、Bundle都是 Image

Framework —— 带有特殊目录结构的Dylib,用来保存资源和头文件。

2. Mach-O内部结构

Mach-O文件可以分成segment。通常大写字母表示,例如__TEXT, __DATA, __LINKEDIT


segment的大小由硬件决定的,是页大小的整数倍。arm64的页大小是16KB,其他的是4KB。

  • __TEXT:__TEXT位于文件的头部,包含Mach header,机器指令以及readonly的常量,例如c字符串。
  • __DATA:readwrite,包含全局变量
  • __LINKEDIT:包含函数的名称和地址相关的信息

3. Mach-O通用文件

为32位和64位设备编译会分别获得一个Mach-O文件。将两个文件合并后会获得一个Mach-O通用文件,可以同时运行在32位和64位的设备上。


Fat Header记录其中的架构以及偏移位置。

二、虚拟内存

every problem can be solved by adding a level of indirection.

虚拟内存解决的问题是,多进程系统如何管理物理内存。

每个进程是由物理内存页映射的逻辑地址空间。这种映射并不是一一对应的,例如有的逻辑地址不对应物理内存空间,也有可能不同的逻辑地址对应着相同的物理内存

Page fault

有些逻辑地址没有对应的内存空间,当程序访问该逻辑地址时,会触发page fault。

此时内核就会停下当前的线程看下一步应该如何做。

共享内存

不同的进程中的逻辑地址空间可以映射相同的物理内存空间,这两个进程共享内存中的内容。

file backed pages

可以通过mmap()调用,告诉虚拟内存,将文件中的某一片段映射到进程中的某段地址空间,而不是将整个文件都读进内存。没当第一次访问之前没有访问过的地址空间,都会触发一个page fault,内核会从文件中读取一页。从而实现了文件的懒读取,提升加载速度。

copy on write

当两个进程访问__DATA内容的时候,一个进程只读,一个进程想要写,这是就会出现copy on write。内核会将这一页copy到新的地址空间,然后重新映射。这时,进程都有自己copy出来的这一页。只读的那一页是clean page,拷贝出来的进行写操作的这一页叫做dirty page。dirty page意味着其中包含了进程相关的信息。对于clean page,内核之后可以重新生成,重新从硬盘中读取。dirty page代价更为昂贵。

加载动态库的过程


假设动态库的大小为8页,两个进程需要加载这个动态库。

进程1:

  1. dyld需要加载Mach-O的header,即图中RAM1。发现没有映射,所以触发page fault,将RAM1读取到物理内存中,然后映射到进程1的虚拟内存中。
  2. dyld读取Mach-O header,然后发现还需要一些信息在__LINKEDIT中。此时加载__LINKEDIT中的信息,触发page fault,将RAM2读取到物理内存中,然后映射到进程1的虚拟内存中。
  3. dyld读取__LINKEDIT内容,需要修改__DATA page的内容使动态库可运行。此时同上,加载__DATA中的信息,触发page fault,将RAM3读取到物理内存中,然后映射到进程1的虚拟内存中。
  4. 当dyld要往__DATA page中写内容的时候,就会触发copy on write。RAM3就变成的dirty page。

进程2:

  1. dyld需要加载Mach-O的header,即图中RAM1。内核会告诉dyld,内存中已经有这个页了。直接映射到进程2的虚拟内存中,无需IO。
  2. 同理,RAM2也是。
  3. 读取RAM3的时候,内核会查看RAM3的clean page依然在内存中,如果在,直接映射,如果不在,重新加载进内存。当dyld需要往__DATA page中写内容的时候,就会触发copy on write。RAM3就变成的dirty page。

只有dyld加载动态库的时候,才会需要__LINKEDIT,一旦完成后,__LINKEDIT中的内容可以回收。

此时,一共用了2个dirty page 和1个clean page。如果是直接读取整个文件到内容中,则一共产生了16个dirty page!


main()之前



参考