Android系统总体是围绕着Linux内核而建立的。从加电到系统启动,大体上经过:
bootloader,距离App开发者太远了,是厂商最为关心的东西。某种角度上讲,其特定性太高,比如加电后我就要从内存芯片的第多少个offset偏移指针开始跑程序。很多是底层协议的东西,理解起来晦涩难懂且帮助不大。所以这里选择直接从Linux内核作为第一起点,以更加通用的逻辑切入系统的启动。
在系统启动系列文章中,所确立的主线是从加电启动到第一个app(也就是launcher)启动。所以,有过一定了解的朋友应该能够get到,核心是zygote。像binder会另辟系列文章加以剖析。
Let’s go!
源码位置:kernel/common/arch/arm64/kernel/head.S
机器加电后,不同的CPU根据不同的内存偏移设定,开始制定对应的汇编指令。以arm64架构为例,在插电后执行到了这里,指向了start_kernel
源码位置:kernel/common/init.main.c
main.c将start_kernel暴露给汇编空间得以调用。被调用时,这时时处于内核态的,经过一些列内核的初始化,最后调用了arch_call_rest_init()
在rest_init中,通过kernel_thread开启了pid为1的进程(0已经在sched_init中初始化的idle thread占用走了,有兴趣自行研习下吧)
kernel_thread很简单粗暴,就是用来在内核态中开启一个线程的(也可以理解为进程,在内核态中并没有特殊的数据结构来区分线程和进程,大意可以理解为一个东西)
kernel_init这个function指针是重要的
经过裁剪,核心代码如图。其中两个execute_command是两个全局静态变量,接收外部指定:
arm结构没有设置,但结合其他架构配置文件的设置可以看到,基本就是指向了init
再结合最后的兜底代码
可以确定,内核的1号进程(pid)的执行内容是用户空间的init
通过对Soong编译系统的基本学习,不难发现,name为init的cc_binary就再system/core/init下:
同时,还可以发现,ueventd和watchdogd其实也是init这个应用程序所做的
通过Android.bp的指引,看下main.cpp做了什么:
通过namespace的限定,可以定位到init.cpp
init 作为第一个用户态的应用程序,做了非常多的工作,包括设置环境变量、挂载FS、权限设置等。基于探索Android系统启动的核心,我们挑最关心的部分看
在看源码之前,可以先去了解下Android初始化语言的规范文档。
初始化语言中,最重要的是两个角色:
可以理解为当XXX发生时候,怎么怎么样:
比如截图中:当到达early-init时,向某设备写入什么值。这里write这一行是command(命令),是具体的行为内容
而服务是声明、定义。
如图中所示,声明了名为ueventd的服务,内容在下边描述,具体的执行文件是 /sbin/ueventd
基于对于Action和Service的初步了解,下边看源码:
在开始解析配置文件init.rc等之前,创建了一个epoll句柄:
在创建epoll句柄后,马上注册了关于SIGCHLD的信号处理回调。
epoll是Linux中的一项多路复用技术,自2.5版本引入。
这里对于epoll的使用是说,init应用程序响应其所启动的各种子程序的SIGCHLD信号(即子程序退出、终止),根据相关数据,会有需要对一些子程序做重启操作。在这,是将SIGCHLD信号打到了socket上,让epoll监听此socket。在后边还有几处使用epoll来监听事件的操作,这里不做过多展开了
紧接着,去解析了Action和Service。解析后所构造出来的Action和Service实例存储在各自Manager、List的Vector中(详见service.h & action_manager.h)。
解析的过程:
通过源码可以看到,可以指定初始化配置文件的地方非常多,甚至可以用环境变量。翻遍AOSP源码,也没有人去设置ro.boot.init_rc这个环境变量,所以都会是init.rc。
init.rc在于文件系统的根目录,而其携带者是boot.img,也就是说他被编译打包在boot.img中。boot.img和system.img以及recovery.img,相信有同学会很熟悉,都是需要通过BootLoader刷机才可以进行更新的。所以常规渠道来讲,只要手机出厂,init.rc中相关信息就是不可修改的
而解析的动作,是由Parser执行的:
结合前边给出的一些init.rc的配置片段,确定service开头的line被ServiceParser去解析,而on被ActionParser去解析,import最后可以理解为递归的解析每一个所引入的其他rc文件。
具体对文本内容逐行解析的过程在system/core/init/parser.cpp#ParseData()中,本文不做具体解析
解析结束后:
上边说了,Service是静态的声明、定义。那么事件的驱动,一定是要靠Action推动的,所以触发的源头就是Action的流转:
这里ActionManager是把各种事件内容都放到了内部的队列中,并没有进行触发:
而其所能够接受的类型有三种:
其分别对应ActionManager的三个操作:
而init的main()中只用了1和3两种方式,很多都是通过内建Action的方式对已经解析出的 **actiions_**做了扩充。
下边就是真正会去触发action的地方:
while-true (A)
进入了无限循环,没有退出的case。这里的设计思路是:
关机或重启事件是一个优先需要处理的,其对应着property服务的处理过程(题外话):
而handle_property_set_fd内部最终就会对应着以下内容:
这样,也就不难理解,关机、重启的逻辑是怎么来的了。
while-true (B)
细节的内容通过图中注释已经做了说明。可以发现,只要AM的action队列中还有存货,就会按照顺序流水账一样的执行下去。
zygote孵化器进程