iOS启动优化之Mach-O相关术语

2,374 阅读4分钟

扫一扫关注公众号,获得更多iOS相关内容


这篇文章讲的是OS启动优化相关的理论部分。

Mach-O术语

Mach-O是运行时可执行文件的文件类型,这些文件类型包括:

  • 可执行文件:应用中最重要的二进制文件,也是应用扩展文件的主二进制文件。
  • Dylib:动态链接库(又称DSO或DLL)。动态链接库包括:iOS中用到的所有系统framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks(Block)。
  • Bundle:捆绑包,不能被链接的Dylib,只能在运行时使用dlopen()加载,macOS的插件会用到它。

Image:指的是任意这三种类型。 Framework:这里指的是一个特殊的Dylib,它有一个目录结构用来存储该Dylib所需的文件。

所有动态链接库和静态库.a和所有类文件.o文件最终都由Dyld(apple的动态链接器)加载到内存中。

Mach-O镜像文件

Mach-O的镜像文件被分割成一些segment(段),segment名字都是大写的。所有的segments都是page(页)大小的整数倍。

页的大小跟硬件有关:

  • arm64上是16KB
  • 其余的为4KB

一个Segment包含若干个sections(分区),所以section名字都是小写的,分区不遵循页面大小,分区间是不重叠的。

几乎所有的二进制文件都包含这三个段(segment):__TEXT,__DATA和__LINKEDIT:

  • TEXT段是文件的开头,包含了Mach的头文件、被执行的代码和只读常量(如C字符串)。只读可执行(r-x)。
  • DATA段是重写段,包含所有的:全局变量,静态变量等。可读写(rw-)。
  • LINKEDIT段包含加载程序的元数据,比如函数的名称和地址。只读(r-)。

Mach-O的通用文件

假设你生成一个64位的iOS应用,那么你就会有一个Mach-O文件,如果你也想让它在32位的机器上运行,Xcode里会发生什么变化呢? Xcode会重新生成一个Mach-O文件,这个是为32位生成的armv7s,之后Xcode会将这两个文件合并成第三个文件,这个文件就是Mach-O的通用文件。

这个文件前端有一个头文件,所有的头文件都有一个所有体系的列表,它们的偏移值也在文件里,该文件占用一页空间大小。

虚拟内存

虚拟内存是一个间接层。每个进程都是一个逻辑地址空间,映射到RAM(随机存取存储器)的某个物理页, 这种映射不一定是一对一的。

特点如下:

  • 如果有一个逻辑地址不映射任何物理RAM,当进程访问该地址时,就会触发page defult,内核会停止该线程,并试图找出解决方案。

  • 当多个逻辑地址映射到同一物理RAM上时,会造成多进程共享内存。

  • 基于文件的映射

    • 不用一次性将整个文件读入物理RAM,可以使用分页映射(mmap()函数)的方式读取。也就是把文件某个段映射到进程逻辑内存的某个页上。
    • lazy reading(懒加载)。

    总结:Dylib或者imgae的TEXT段可以映射到多个进程,将会造成读取迟缓,而所有这些页面可以在进程间共享。

  • Copy-On-Write(写入时复制),简称COW。也就是多个进程共享一页内存空间时,一旦有进程要做写操作,它会将这页内存内容复制一份出来,然后重新映射逻辑地址到新的RAM页上。也就是这个进程自己拥有了那份内存的拷贝。

  • Dirty vs. clean pages:上面复制出来的副本被认为是脏页面,脏页面是指含有进程特定信息的页面。干净页面是指内核可以按照需要重新建立的页面,比如重新读取磁盘。脏页面比干净页面要昂贵的多,

  • Permissions(权限界限):这里指可以标记一个页面,可读,可写或可执行,或者是它们的任意组合。

Mach-O镜像加载

在多个进程加载Mach-O镜像时 __TEXT和__LINKEDIT是只读的,都是可以共享内存的。而__DATA是可读写的,会产生dirty page。当dyld执行结束后,__LINKEDIT就没用了,对应的内存页就会被回收。

安全

两点安全问题会影响Dyld:

  • ASLR:地址空间布局随机化。镜像会在随机的地址上加载。
  • 代码签名:为了在运行时验证Mach-O文件的签名,并不是每次重复读入整个文件,而是把每页的内容都生成一个单独的加密散列值,并存储在__LINKEDIT中。这使得文件每页的内容在读取时都能及时校验并确保不被篡改。

下期预告:iOS启动优化之从exec()到main()