赞
踩
我们知道,操作系统是电脑、手机上最基本的软件,任何其他的软件都必须在操作系统的支持下才能够运行。同理,软件的启动也必须在操作系统的支持下才能够运行。对于iOS系统来说,操作系统内核是XNU(X is not Unix),那么在一个app的启动过程中,XNU发挥了什么作用呢?本篇文章,我们来探究一下这个问题。
XNU的代码是开源的,可以从苹果开源代码平台上下载XNU的代码,通过分析XNU的源码,可以大致了解XNU是如何加载Mach-O文件以及dyld的。
XNU内核启动后,启动的第一个进程是launchd,launchd启动之后会启动其他的守护进程。XNU启动launchd的过程是 load_init_program() -> load_init_program_at_path()。可以看一下这两个函数的源码。
load_init_program()部分代码:
void load_init_program(proc_t p)
{
……
error = ENOENT;
// 核心代码在这里,加载初始化程序,对 init_programs数组遍历
for (i = 0; i < sizeof(init_programs)/sizeof(init_programs[0]); i++) {
// 调用load_init_program_at_path方法
error = load_init_program_at_path(p, (user_addr_t)scratch_addr, init_programs[i]);
if (!error)
return;
}
panic("Process 1 exec of %s failed, errno %d", ((i == 0) ? "<null>" : init_programs[i-1]), error);
}
init_programs是一个数组,可以看一下该数组的定义:
// 内核的debug模式下可以加载供调试的launchd,非debug模式下,只加载launchd
// launchd负责进程管理
static const char * init_programs[] = {
#if DEBUG
"/usr/local/sbin/launchd.debug",
#endif
#if DEVELOPMENT || DEBUG
"/usr/local/sbin/launchd.development",
#endif
"/sbin/launchd",
};
可以看出,load_init_program的作用就是加载launchd,加载launchd使用的方法是load_init_program_at_path函数。load_init_program_at_path的部分代码如下:
static int load_init_program_at_path(proc_t p, user_addr_t scratch_addr, const char* path)
{
……
/*
* Set up argument block for fake call to execve.
*/
init_exec_args.fname = argv0;
init_exec_args.argp = scratch_addr;
init_exec_args.envp = USER_ADDR_NULL;
/*
* So that init task is set with uid,gid 0 token
*/
set_security_token(p);
// 会调用execve方法
return execve(p, &init_exec_args, retval);
}

load_init_program_at_path调用了execve()函数,实际上,execve是加载Mach-O文件流程的入口函数。因为launchd进程比较特殊,所以多了两个方法。因此,接下来我们就从execve()函数开始分析。
上面说到了,execve()函数是加载Mach-O文件的入口,看一下execve()函数做了哪些事情:
/*
uap是对可执行文件的封装,uap->fname可以得到执行文件的文件名
uap->argp 可以得到执行文件的参数列表
uap->envp 可以得到执行文件的环境变量列表
*/
int execve(proc_t p, struct execve_args *uap, int32_t *retval)
{
struct __mac_execve_args muap;
muap.fname = uap->fname;
muap.argp = uap->argp;
muap.envp = uap->envp;
muap.mac_p = USER_ADDR_NULL;
// 调用了__mac_execve方法
err = __mac_execve(p, &muap, retval);
return(err);
}

可以看到,execve()函数的作用主要是进行了一些赋值,然后调用了__mac_execve()函数,看来核心操作在__max_execve()函数中。
__mac_execve()函数会使用fork_create_child()函数启动新进程,之后使用新的进程,生成新的task。__mac_execve()函数的主要功能就是干这个,之后就调用了exec_activate_image()函数。
__mac_execve()函数的部分代码:
int __mac_execve(proc_t p, struct __mac_execve_args *uap, int32_t *retval)
{
// 新的task定义
task_t new_task = NULL;
boolean_t should_release_proc_ref = FALSE;
boolean_t exec_done = FALSE;
boolean_t in_vfexec = FALSE;
void *inherit = NULL;
context.vc_thread = current_thread();
context.vc_ucred = kauth_cred_proc_ref(p); /* XXX must NOT be kauth_cred_get() */
/* Initialize the common data in the image_params structure */
// 使用uap初始化imgp结构体中的一些通用数据
imgp->ip_user_fname = uap->fname;
imgp->ip_user_argv = uap->argp;
imgp->ip_user_envv = uap->envp;
imgp->ip_vattr = vap;
imgp->ip_origvattr = origvap;
imgp->ip_vfs_context = &context;
imgp-

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。