首先android是基于Linux的内核,只有先加载了kernel才能启动安卓,对于Linux来说android只是其上的一个应用程序。Android的启动大致可以形象的划分为三个过程:
Init->init.rc->zygote。从事嵌入式开发的人都知道,Linux加载完内核驱动后会挂载‘/’根文件系统,挂载完成后会执行‘/init’二进制程序,这也是内核启动后执行的第一个用户程序,android里面也是这样。这个程序的main函数位于android/system/core/init/init.c中,作为一个操作系统,初始化一般要完成以下几个工作:
1.创建需要的目录,挂载文件系统,输入和读取文件硬盘数据。
2.装载和设定全局的环境变量,为程序的运行搭建好必要的环境。
3.对于android来说,还需要运行Java虚拟机,这是安卓特有的跨平台特性。
4.加载和运行Framework框架,加载窗口桌面程序,也就是系统的GUI,最后将控制权交给用户后算启动完成。
根文件“/init”程序分析:
源码可以看出,init首先会创建一些必需的目录,如‘/dev’、‘/proc’、‘/sys’等。然后将设备mount到该目录下,mount完成后才可以创建设备节点。例如:
<span style="white-space:pre"></span>mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
<span style="white-space:pre"></span>mount("proc", "/proc", "proc", 0, NULL);
<span style="white-space:pre"></span>mount("sysfs", "/sys", "sysfs", 0, NULL);
创建串口输出节点,串口重定向输出:
open_devnull_stdio();
在klog_init函数中创建了"/dev/__kmsg__"节点,之后很快unlink掉了,该函数复制了一份串口输出日志,日志包括从kernel启动到android初始化之间的打印信息,通过dmesg命令就可以打印出来。
其中还有如下代码:
property_init();该函数主要是加载一些默认的property属性,这些属性存在于build.prop,default.prop文件中。
get_hardware_name(hardware, &revision);该函数用于从"/proc/cpuinfo"节点中获取cpu的硬件和版本信息,将这些信息写入prop属性变量中。
process_kernel_cmdline();从节点“/proc/cmdline”中获取bootargs的环境变量,并设置到全局变量中以便后续使用。
在程序中系统会读取和解析init.rc文件,这个文件中更像是一些命令集合,但是程序并没有立即执行这些命令。而是先解析出来,按照一定规则进行整理放入一个链表中,init.rc中的命令并不是像shell一样是一个直接的执行命令,而是有一个内部的映射,这些映射存在于init/keywords.h文件中,例如:
KEYWORD(mkdir, COMMAND, 1, do_mkdir);
KEYWORD(mount, COMMAND, 3, do_mount);
KEYWORD(rm, COMMAND, 1, do_rm);
KEYWORD(rmdir, COMMAND, 1, do_rmdir);
mkdir是rc中的命令,但do_mkdir才是真正的命令执行实体。
在init的后面有一个for循环,命令的执行是在其中完成的:
for(;;) {
execute_one_command();
restart_processes();
。。。。。。。。。。。。。。。
}其中execute_one_command会将命令提取出来,一条接一条的进行执行。命令中如果注册了一个service,那么它如果执行失败了,也还可以进行重新启动执行。
Service往往是单独的进程进行执行的,这些程序执行成功与否是系统关心的问题,系统需要监管这些进程,所以注册了一个信号处理函数handle_signal。如果子进程出现错误比如内存溢出等,这时会触发一个信号量,父进程在接收到该信号量后会到handle_signal中进行处理,父进程的有效管理,使得这些进程避免变成“野进程”或者“僵尸进程”。信号处理函数wait_for_one_process中,如果等待的进程程序出错或者超时会有“waitpid returned pid %d, status = %08x\”的打印,平时如果某个service出错反复执行时就会有该打印。直到所有的service成功启动后,init才会正常退出for循环。
Init.rc初始化文件分析:
分析init.rc文件,通过该文件可以知道系统都做了哪些事情,比如其中的几个关键命令:
on property:ro.debuggable=1
start console
这种语句表示系统会读取prop属性(全局注册表)中的值,如果property:ro.debuggable=1则执行条件以下的语句start console,所以打开android的打印串口是在这地方做的。
sysclktz 0
设置时区。
loglevel 3
设置log等级。
on property:ro.kernel.qemu=1
start adbd
根据build.prop中的该属性设置决定是否启动远程调试adbd。
service bootanim /system/bin/bootanimation
启动开机动画。
如果留意,Init.rc中会有一个以on开头后面跟一个字段的关键字,如:
on early-init 这种关键字和上面的prop后面跟着一个值的方式不相同。它主要用于表示android的启动阶段,init在加载这些命令时并不是按照从文件头到文件尾的方式,而是寻找这些关键字,按照启动顺序依次加载,执行时也按照这个顺序执行,例如这些阶段还有:
early-init、init、early-fs、early-boot、boot等。
在init.rc中还有一个很关键的阶段字,代码如下:
on emmc-fs
mount ext4 ext4@system /system ro
mount ext4 ext4@userdata /data nosuid nodev
mount ext4 ext4@cache /cache nosuid nodev
这几条命令的意思是将@system分区,以ext4的文件系统格式挂载到根目录/system下,ro表示只读的意思。emmc-fs阶段要开始于其它的阶段,因为只有将系统的核心目录挂载了,才能进行后续操作。Init.rc除了支持emmc,还支持nand,决定用哪一个是系统自动识别完成的,代码如下:
if ( check_flash_type() == NAND_TYPE) {
action_for_each_trigger("fs", action_add_queue_tail);
}
else if ( check_flash_type() == EMMC_TYPE) {
action_for_each_trigger("emmc-fs", action_add_queue_tail);
}
通过检查bootargs中的关键字“hinand”或者“mmcblk”知道flash的类型。
rc文件中有一个核心的关键字service,使用该关键字就是要把其后的命令扩展为一个服务,这个关键字的规则也是最复杂的,例如下面这条命令:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
这条命令使用service指令告诉系统将zygote加入到系统服务中,service的语法为:
service service_name 可执行程序路径 可执行程序的入口参数
/system/bin/app_process为实际可执行的程序,后面‘-’为执行的参数。socket用于服务所使用到的socket,后面参数依次为名称、类型、端口、地址。onrestart命令指定该服务重启的条件,即当满足这些条件后,zygote服务就需要重启;当然这些都是一些异常条件,也就是说如果media或者netd发生异常重启时,zgote就需要重启。
如果sevices中有oneshot关键字,则表示该service只执行一次,例如:
service bootanim /system/bin/bootanimation
class main
user root
group graphics
disabled
oneshot
表示开机动画执行一次,disabled表示如果出错或超时不会再次执行,更多具体的命令可参照init.rc文件。
Zygote卵孵化器分析:
从上面的分析可以看出,到目前为止都没有真正涉及到android的东西,如果没有zygote那么安卓顶多算一个linux系统。Zygote进程是所有APK应用的父进程,其它进程都是由该进程孵化产生的,所以将其拟化为一个孵化器。通过android系统的虚拟机可以加载Java的开发环境,从功能上来说,Java语言更类似于Shell和Base语言(属于伪代码),与硬件打交道的任务交给了虚拟机去完成。虚拟机的任务就是屏蔽平台的差异属性,使得同一份Java语言可以同时运行于X86平台和ARM平台。Java语言另外的优势是面向对象,在应用开发方面比C语言更好用,缺点是运行效率比C差。
Zygote的实际执行程序是/system/bin/app_process,app_process的代码位于frameworks/base/cmds/app_process/app_main.cpp中。
分析app_main.cpp的代码,app_process会在AndroidRuntime.startVm函数中创建第一个虚拟机:
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
同时app_process启动后会装载与framework的相关类以及resource资源,检查/system/framework/下的jar包,将其中的jar文件通过dexopt优化变为dex文件,同时安装到/data/dalvik-cache/目录中,这样下一次执行时直接到该目录下寻找。最后启动两个核心的类ZygoteInit.java和SystemServer.java类,这两个类都是以两个单独的进程启动的。SystemServer进程是Android系统的神经中枢,安卓应用直接交互的大部分系统服务都是在该进程中运行的,最关键的有:WindowManagerServer(Wms)、ActivityManagerSystemService(AmS)、PackageManagerServer(PmS)等,这些服务都是在该进程中以线程的方式启动的。
PackagemanagerService主要用来管理apk,该服务会解析apk中的组件,例如Activiey、Service等。系统初始化时会遍历/system/app/和/data/app/目录,将apk以包名的形式拷贝到/data/data/<pkgName>目录下,将apk中的class文件保存到/data/dalvik-cache/目录下,同时以相应的apk进行命名。服务利用PackageParser类解析apk中的AndroidManifest.xml文件获取包的一些信息,并将这些信息保存到/data/system/packages.xml和packages.list中,以便系统后续使用。这些信息应该只会读取一次,下次启动后会检查是否有更新?如果没有就使用上一次的文件。
第一次开机时该服务还会去创建一些目录,如/data/目录下的data、app-asec、app-lib、app-private等。同时解析/system/etc/permissions/platform.xml文件,知道系统都定义了哪些系统权限以及该权限对应可执行程序的uid。当用户安装应用时会弹出一个应用要求的权限,这些权限都是在这里定义的。从以上的步骤可以看出,第一次开机会做很多工作,这也就是为什么android系统第一次启动都会很慢的原因。
启动第一个Activity:
当所有的线程服务都启动完成后,其中的ActivityManagerService(AmS)服务会去检测其它服务是否完成,这个是通过调用systemReady()函数来完成的,最后会执行如下代码:
mMainStack.resumeTopActivityLocked(null);
也就是说AmS会执行最顶层的TopActivity,但是第一次开机TopActivity是没有的,这时候就会有:
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
if (mMainStack) {
ActivityOptions.abort(options);
return mService.startHomeActivityLocked(mCurrentUser);
}
}
也就是去加载HomeActivity,Android不像其它系统一样,将一个固定的Activity作为主界面程序加载,而是在AmS的starHomeActiviyLocked()中,系统发出一个catagory字段包含CATEGORY_HOME的intent。如下:
intent.setComponent(mTopComponent);
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
无论是哪个应用程序,只要声明自己为该类型后,那就可以被认为是Home程序,系统并没有选取任何一个“Home”程序,而是将这个权利交给了用户,用户的选择决定了系统启动的Home,这个就是第一个启动的Acivity(当用户使用安卓系统启动后,会有一个选取界面的操作框,通过按键用户可以选取合适的Activity作为主界面)。当AmS要启动一个activity时,需要根据intent携带的activity名称,调用内部的PackageManager查询该名称对应的具体信息,如果存在就启动activity,否则返回失败。
当系统的Lancher界面启动呈现完成后,系统的启动也就算完成了。
【本代码基于android4.2源码进行分析】