总览
- 利用已经被内核映射到内存中的可执行文件,
instantiateFromLoadedImage
生成 ImageLoader - 将依赖库加载进内存,生成对应的 ImageLoader(
loadInsertedDylib
) - 链接可执行文件(
link
) - 链接依赖库(
link
) - 调用所有 Image 的初始化方法 Initializers,包括动态库和可执行文件,核心系统库、objc自举(
initializeMainExecutable
) - 返回程序入口函数 main 的地址(
sMainExecutable->getMain()
)
简化过的代码如下:
//
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
......
// 1. instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
......
// 2. load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) {
loadInsertedDylib(*lib);
}
}
......
// 3. link main executable
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
......
// 4. link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
image->setNeverUnloadRecursive();
}
}
// 5. run all initializers
initializeMainExecutable();
// 6. main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getMain();
return result;
}
可执行文件和依赖库的链接
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)
{
// 递归加载
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);
// 递归Rebase 修复 ASLR 造成的地址错位的问题,增加一个偏移量
// 主要是 IO 操作
this->recursiveRebase(context);
// 递归符号绑定,将指针指向 image 外部的内容。需要查询符号表,性能消耗主要是 CPU 计算
this->recursiveBind(context, forceLazysBound, neverUnload);
}
Initializers
void initializeMainExecutable()
{
......
// 首先执行动态库的 initialzers
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// 随后执行可执行文件的 initialzers
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// 进行终止化
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// 如果设置了环境变量 DYLD_PRINT_STATISTICS,可以在 Xcode debug 的时候在控制台打印
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}
这里的 initialzers
注意不是 Objective-C 中的 initialzers 方法,而是 C++静态对象初始化构造器。
在 Xcode 中,可以通过设置环境变量 DYLD_PRINT_STATISTICS
打印所有 initialzers 方法。
运行 App 后,可以看到控制台打印
通过打印可以发现,其中排第一个的是 libSystem.B.dylib,排在最后的是可执行文件的方法。
在 libSystem.B.dylib 的 initialzers 函数里的 libdispatch_init 调用到了 Runtime 初始化方法 _objc_init。
通过在 Xcode 中设置 _objc_init 符号断点,可以看到在控制台打印 dyld: calling initializer function 0x1ba93e7c0 in /usr/lib/libSystem.B.dylib
后, _objc_init 被调用了,不过调用栈将中间的调用过程全部隐去了。
objc 源码部分
查看 libdispatch源码,可以发现 objc 的踪迹。
libdispatch_init -> _os_object_init -> _objc_init
void
libdispatch_init(void)
{
......
_os_object_init();
.....
}
void
_os_object_init(void)
{
_objc_init();
......
}
这下终于来到了熟悉的 objc4 的源码,这里我们重点关注在 dyld 中注册了的三个回调。
三个回调时机依次是 objc image 的 mapped、initialized、unmapped 三个阶段
void _objc_init(void)
{
// so many init
......
// 在 dyld 中注册了三个回调
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
map_images
当 dyld 完成映射(mapped)后,开始执行。其中核心函数是 _read_images
。
完成的工作主要是:
- 从库中对应的 segment 读取 class、protocol、category 信息,载入到 Runtime。
- 将 class、protocol、category 信息向 Runtime 注册结构
- 将 category 需要加到对应的 class 上。
- realized 类(重新确定布局,在 class_ro_t 基础上创建 class_rw_t)
其中需要说明的是 class 中 class_ro_t 为 unrealized class,class_rw_t 为 realized class。
如果想要使用 class,必须是 class_rw_t。class_ro_t 经过 resolve 后,会转化为 class_rw_t。non-lazy class 要求在初始化进行 realize。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// 注册 selector 到一个全局表中
for(......) {
SEL sel = sel_registerNameNoLock(name, isBundle);
}
// 获取 classes,并 realize
classref_t const *classlist = _getObjc2ClassList(hi, &count);
......
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// protocol
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
// 注册结构
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
// categories
for() {
// 在 class 中注册 category,
// 如果 class 已经 realize,重建 class
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
}
// Realize non-lazy classes (for +load methods and static instances)
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for(......) {
// 将类添加到 table 中
addClassTableEntry(cls);
realizeClassWithoutSwift(cls, nil);
}
// Realize newly-resolved future classes
for(......) {
realizeClassWithoutSwift(cls, nil);
}
}
realizeClass 干的事情包括:
- 递归 Realize 父类和元类
- 重新设置 class、superclass、metaclass 之间的关系
- 重新计算 instance variable layout
- 将 class 中的编译时已经确定的 class_ro_t 的内容(method、property、protocol)放到 class_rw_t 结构体上,并增加 category 的部分
static Class realizeClass(Class cls)
{
// realize 父类和元类
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));
// 重新设置关系
cls->superclass = supercls;
cls->initClassIsa(metacls);
// 重新计算 layout
// 如果 superclass 和原 class 空间重叠,需要对原 class 的实例重新计算位置并调整
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls);
}
methodizeClass 函数干的事包括:
- 将 class 中的 class_ro_t 的内容(method、property、protocol)放到 class_rw_t 结构体上,
- 增加 category 的部分
static void methodizeClass(Class cls)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
// 方法增加
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
// 属性增加
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
// 协议增加
rw->protocols.attachLists(&protolist, 1);
}
// Attach categories,将 method、property、protocol 插入
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
load_images
是 dyld 的第二个注册回调,其核心是调用 load 方法。
- 查找并保存所有类,主类存在
loadable_classes
中,其排列顺序是父类在前、子类在后,分类存在loadable_categories
中。 - 先调用
loadable_classes
中类的 load 方法,然后调用loadable_categories
中分类的 load 方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
void call_load_methods(void)
{
......
void *pool = objc_autoreleasePoolPush();
do {
// 1. 首先调用类的 load
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. 调用分类的 load
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
......
}
// 调用 load 方法,并没有走消息发送机制,而是直接进行调用
(*load_method)(cls, @selector(load));
启动时间优化
目前为止是 App 启动时,pre-main 部分,也就是还没有走到 App 入口的 main 函数。
load dylibs
iOS App 一般需要加载 100~400 个 dylibs。其中包括了系统和开发者引入的。加载 dylibs 会消耗 App 的启动时间。可以做优化的部分是自己引入的 dylib。Apple 在 WWDC 上建议,建议尽量将第三方 dylibs 个数控制在 6 个以内。
优化方案:
- 使用静态库替代动态库,如果使用 Cocoapods 管理第三方库的话,可以将 podfile 中的
use_frameworks!
注释掉,然后pod install
来将动态库变更为静态库。 - 合并动态库,减少动态库的数量,这一块笔者没有进行尝试。
Rebase/Binding
这两步主要是对 image 内部指针的修复。因此只要指针数量越少,修复的耗时就会变少,其中的关键是减少 _DATA 段中指针的数量。
优化方案:
- 减少类、方法的数量,比如删除废弃的类、方法
- 使用 Swift 的 struct(WWDC 介绍结构内部有做优化,指针数量少)
- 减少 C++ 虚函数的数量(这块可优化的空间较小)