小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1.程序加载的原理
系统内核将可执行文件从磁盘中加载到内存中,内存中的二进制文件,我们称之为image
镜像文件。之后,系统会加载动态链接器dyld
。dyld
只会负责动态库的加载,主程序也会作为镜像形式被dyld
管理起来。
dyld
从可执行文件的依赖开始,递归加载所有以来的动态库。无论是动态链还是APP本身的可执行文件,它们都是image
镜像,而每个APP都是以image
为单位进行加载的。
2.编译过程
编译过程如下图所示:
- 源文件:
.h
、.m
、.cpp
等文件 - 预编译:替换宏,删除注释,展开头文件,词法分析、语法分析。生成.i文件
- 编译:转换成汇编语言,生成
.s
文件 - 汇编:把汇编语言文件转换为机器码文件,产生
.o
文件 - 链接:对
.o
文件中引用其他库的地方进行引用,生成最后的可执行文件
3.静态库&动态库
库(Library
):就是一段编译好的二进制代码,可以被系统加载,加上头文件就可以供别人使用,常用的库文件格式有:.a
、.dyld
、.framework
、.xcframework
、.tdb
3.1 什么时候会用到库?
- 某些代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只暴露出头文件
- 对于某些不会进行大改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的二进制,编译的时候只需要
Link
一下,不会浪费编译时间
3.2 什么是链接
库(Library
)在使用的时候需要链接(Link
)
链接的方式有两种:静态和动态。
3.3 什么是静态库?
静态库即静态链接库:可以简单的看成一组目标文件的集合。即很多目标文件经过压缩打包成形成的文件。Windows
下的.lib
,Linux
和Mac
下的.a
,Mac
独有的.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.dyli
b中,由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方法中设置断点,查看函数调用栈
可以发现应用启动时的初始方法,由dyld中的
_dyld_start
开始的。