iOS小知识之应用程序的加载

464 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1.程序加载的原理

系统内核将可执行文件从磁盘中加载到内存中,内存中的二进制文件,我们称之为image镜像文件。之后,系统会加载动态链接器dylddyld只会负责动态库的加载,主程序也会作为镜像形式被dyld管理起来。
dyld从可执行文件的依赖开始,递归加载所有以来的动态库。无论是动态链还是APP本身的可执行文件,它们都是image镜像,而每个APP都是以image为单位进行加载的。

2.编译过程

编译过程如下图所示:

image-7.png

  • 源文件:.h.m.cpp等文件
  • 预编译:替换宏,删除注释,展开头文件,词法分析、语法分析。生成.i文件
  • 编译:转换成汇编语言,生成.s文件
  • 汇编:把汇编语言文件转换为机器码文件,产生.o文件
  • 链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件

3.静态库&动态库

库(Library):就是一段编译好的二进制代码,可以被系统加载,加上头文件就可以供别人使用,常用的库文件格式有:.a.dyld.framework.xcframework.tdb

3.1 什么时候会用到库?
  • 某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件
  • 对于某些不会进行大改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的二进制,编译的时候只需要Link一下,不会浪费编译时间
3.2 什么是链接

库(Library)在使用的时候需要链接(Link) 链接的方式有两种:静态和动态。

3.3 什么是静态库?

静态库即静态链接库:可以简单的看成一组目标文件的集合。即很多目标文件经过压缩打包成形成的文件。Windows下的.libLinuxMac下的.aMac独有的.framework
缺点:浪费内存和磁盘空间,模块更新困难

3.4 什么是动态库?

与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。格式有:.framework.dyld.tdb
缺点:会导致一些性能损失。但是可以优化,比如延迟绑定(Lazy Binding)技术

4. dyld

4.1 dyld是什么?

dyld:动态链接器,加载所有的库和可执行文件
libdyld.dylib:给我们的程序提供在Runtime期间能使用动态连接功能

4.2 加载程序的过程
  • 调用fork函数,创建一个process(进程)调用execve或其衍生函数,在该进程上加载,执行我们的Mach-O文件
  • 将文件加载到内存
  • 开始分析Mach-O中的mach_header,以确认它时有效的Mach-O文件
  • 验证通过,根据mach_header解析load commands。根据解析结果,将程序各个部分加载到指定的地址空间,同时设置保护标记
  • LC_LOAD_DYLINKEN中加载dyld
  • dyld开始工作
4.3 dyld的工作是什么?
  • 执行自身初始化配置加载环境LC_DYLD_INFO_ONLY
  • 加载当前程序链接的所有动态库到指定的内存中LC_LOAD_DYLIB
  • 搜索所有的动态库,绑定需要在调用程序之前用的符号(非懒加载符号)LC_DYSYMTAB
  • 在间接符号表(indirect symbol table)中,将需要绑定的导入符号真是地址替换LC_DYSYMTAB
  • 向程序提供在Runtime时使用dyld的接口函数(存在libdyld.dylib中,由LC_LOAD_DYLIB提供)
  • 配置Runtime,执行所有动态库。image中使用的全局构造函数
  • dyld调用程序入口函数,开始执行程序LC_MAIN
4.4 程序启动的初始方法

在ViewController中,加入load方法

@implementation ViewController 

+ (void)load{
    NSLog(@"ViewController load方法");
}

@end

在main.m中加入C++构造函数

__attribute__((constructor)) void func(){ 
    printf("\n C++构造函数:%s \n",__func__); 
}

在main.m函数中,增加NSLog打印

int main(int argc, char * argv[]) { 
    NSString * appDelegateClassName; 
    @autoreleasepool {
        NSLog(@"main函数");
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]); 
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName); 
}

------------------------- 

//输出结果: 
ViewController load方法 
C++构造函数:func 
main函数
  • load方法 -> C++构造函数 -> main函数

main函数为程序入口,但load方法和C++构造函数的执行时机比main函数更早,它们时被谁调用的? 在load方法中设置断点,查看函数调用栈

image-8.png 可以发现应用启动时的初始方法,由dyld中的_dyld_start开始的。