Android核心服务解析篇——Android系统的启动

Posted Red风信子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android核心服务解析篇——Android系统的启动相关的知识,希望对你有一定的参考价值。

  分类:

目录(?)[+]

从大的方面来说,Android系统的启动可以分为两个部分:第一部分是Linux核心的启动,第二部分是android系统的启动。第一部分主要包括系统引导,核心和驱动程序等,由于它们不属于本篇要讲的内容,这里就不再讨论。在本篇博客中,我们重点讲解Android系统的启动,这一过程主要经过两个阶段,分别是应用的初始化流程与system_service进程及核心服务的创建流程。


1.初始化流程


初始化流程,顾名思义,它完成Android的一些初始化工作,包括设置必要的环境变量,启动必要的服务进程,挂载必要的设备等,而这些工作将会为整个Android打下坚实的基础。


①应用的初始化流程


在核心启动完成以后,将进入Android文件系统及应用的初始化流程,此时将会转向执行init.c中的main()函数(路径:/system/core/init/init.c),该函数的执行流程如下图所示:





下面我们了解一下上图中的注解


注解1:/dev表示设备文件系统或者udev挂载点,/proc用来挂载存放系统过程性信息的虚拟文件系统,/sys用于挂载“sysfs文件系统”。由于前面调用了umask(0),因此mkdir(“/dev”,0755)得到的权限应该是0755.


注解2:init.rc的解析结果是形成action_list(on关键字相关的部分),service_list(service_关键字相关的部分)以及action_queue(需要执行的命令或服务),以便后续流程使用。


注解3:解析/proc/cmdline文件,将其中的属性导入Android系统全局变量。


注解4


Ⅰget_hardware_name()方法用于解析/proc/cpuinfo文件获取硬件信息,并用于拼接成一个init.<hardware_name>.rc文件,继续解析。


Ⅱ在解析init.rc文件的过程中,系统会根据该文件的内容形成一些需要命令,动作或者触发器的列表并将这些存入在内在中,以便在必要的时候使用。不同的厂商可能根据不同的硬件需求定制不同的.rc文件,这些.rc文件的名称一般为“init.<hardware_name>.rc”,而解析这些.rc文件的结果同样也会形成一些命令,动作或者触发器的列表,而这些列表将会合并解析init.rc所得的命令和动作的列表中,并且形成最终需要执行的命令和动作。


注解5:添加顺序为:early-init下的所有动作,wait_for_coldboot_done_action,property_init_action,keychord_init_action,console_init_action,set_init_properties_action,init下的动作,property_service_init_action,signal_init_action,check_startup_action,early-boot下的所有动作,boot下的所有动作,queue_property_triggers_action。这些动作组成了开机过程中看到的设备的状态,比如开机动画等。


注解6:这里会启动执行设置属性,创建或挂载动作以及启动服务等操作。需要注意是的这里启动的服务包括最重要的servicemanager和zygote服务进程。


至此,init进程进入死循环中处理一些消息以等待命令的到来。在这个过程中,我们将要了解以下知识。


Ⅰ在init运行的过程中产生了许多服务,它们是整个Android的基础,分别是ueventd,console,adbd,servicemanager,vold,netd,debuggerd,ril-daemon,surfaceflinger,zygote,drm,media,bootanim,dbus,bluetoothd,installd,flash_recovery,racoon,mtpd,keystore和dumstate。


Ⅱ整个init的行为甚至整个Android核心的属性都受到启动脚本init.rc的影响。


下面我们就重点介绍zygote的启动行为,详细了解init.rc的语法。


②init.rc的用法


Android初始化语言由声明的4个类型组成,它们分别是动作(action),命令(command),服务(service),和选项(option),以#开头的行表示注释。动作和服务声明新的一节并且有唯一的名字,所有的命令或者选项属于最近声明的节。如果下一个动作或者服务的名字已存在(也就是重名),则它将作为错误被忽略。


Ⅰ动作


动作是命令序列,它有一个触发器,用于确定行动应在何时发生。当发生某一个事件时,它可以匹配到一个动作触发器,并且该动作会被添加到要执行队列的尾部(除非它已经在队列中了)。


队列中的每个动作是按顺序出列的,具体如下所示:


on early-init

write /proc/1/oom-adj -16

setcon u:r:init:s0

start ueventd


动作表现为以下的形式:


on <trigger>

<command>

<command>

<command>

.........


触发器是一些字符串,这些字符串可用于匹配一定类型的事件,并且用于触发动作。下表罗列了一些触发器的定义。




触发器说明
boot当初始化流程触发的时候,boot是首先被触发的动作(在完成/init.conf文件加载之后)
<name>=<value>当以<name>命名的属性被设为特定的值<value>时,该触发器发生
device-added-<path>当添加设备节点时,device-added-<path>定义的触发器运行
device-removed-<path>当移除设备节点时,device-removed-<path>定义的触发器运行
service-exited-<name>当指定的服务退出时,service-exited-<name>类型的触发器运行
<string>自定义的触发器,可由init代码负责管理

Ⅱ命令


命令是组成动作的成员,也就是说,动作由一个个命令组成。下表罗列了动作支持的命令。




命令说明
exec  <path> [<argument>]*fork并执行程序(<path>)。这在程序完成执行之前将阻塞一切进程,因此最好避免使用exec命令。该命令中两个参数的含义如下所示。
❶<path>:可执行文件的路径
❷[<argument>]*:可执行文件所需的参数,参数个数可以是0或者多个
export <name> <value>设置名字为<name>的环境变量为<value>
ifup <interface>打开网络接口<interface>
import <filename>解析一个初始化配置文件,导入系统中
hostname <name>设置主机名
chdir <directory>修改工作目录,它的功能和cd命令一样
chmod <octal-mode> <path>修改文件的访问权限
chown <owner> <group> <path>修改<path>指定的问题的所有者和组
chroot <directory>修改进程根目录为<directory>
class_start <serviceclass>启动<serviceclass>类别的服务,如果它们没有运行的话
class_stio <serviceclass>停止<serviceclass>类别的服务,如果它们已经处于运行状态的话
domainname <name>设置域名
insmod <path>在<path>上安装模块
mkdir <path> [mode] [owner] [group]创建一个目录,其中目录路径以及名称由<path>指明。这里可以通过参数给定目录的模式,所有者和组。如果没有提供[mode] [owner] [group],则用权限755来创建目录,并且它属于root用户root组
mount <type> <device> <dir> [<mountoption>]*尝试在目录<dir>上挂载被命名的设备,<device>可能是mtd@name的形式,以便指定名为mtd块的设备。<mountoption>包括ro,rw,remount和noatime等
setkeyTBD
setprop <name> <value> 设置系统属性<name> 为<value>
setrlimit <resource> <cur> <max>设置指定资源的使用限制
start <service>启动指定的服务,如果服务还没有运行的话
stop <service>停止指定的服务,如果服务目前正在运行的话
symlink <target> <path>用值<target>来在<path>上创建一个符号链接
sysclktz <mins_west_of_gmt>设置系统闹钟基准(如果系统闹钟为GMT,则为0)
trigger <event>触发一个事件。用于执行该触发器中的操作
write <path> <string> [<string>]*在<path>上打开文件并且用write(2)来将一个或多个字符串写到文件上。


在init.rc中,Android 定义了若干动作,并且这些动作用于完成Android的初始化工作。下面以其中一个动作的配置来说明一下:


on fs

mount yaffs2 mtd@system /system

mount yaffs2 mtd@system /system ro remount

mount yaffs2 mtd@userdata /data nosuid nodev

mount yaffs2 mtd@cache /cache nosuid nodev


这个例子配置了一个触发器为fs的动作,它由4条命令组成,这4条命令都使用mount命令挂载设备。


Ⅲ服务


服务是一些程序,当它们退出的时候,init启动并且(选择性地)重新启动。服务表现为以下形式:


service <name> <pathname> [<argument>]*

<option>

<option>

...........


其中各个参数的含义如下所示:


❶<name>:为服务指定一个名字。


❷<pathname>:指定服务需要执行的文件路径。


❸[<argument>]*:启动服务所需要的参数,参数个数可以是0个或者多个。


Ⅳ选项


选项是服务的修改器,可以影响如何以及何时初始化运行服务。下表罗列了选项列表。




选项说明
critical这是一个对于设备来说比较关键的服务,如果它在4分钟内退出超过4次,那么设备将重新启动并进入recovery模式。
disabled这个服务不能通过类别自动启动,它必须通过服务名字来显示启动
setenv <name> <value>设置启动进程中环境变量(由<name>指定)的值为<value>
socket <name> <type> <perm> [<user> [<group>]]创建名为/dev/socket/<name>的一个Unix域端口并且将它的fd传递到被启动的进程上
<type>必须是dgram,stream,seqpacket.设置用户和组的默认值为0
user <username>在执行该服务之前变换用户名,如果进程需要Linux的能力,就不能使用该命令
group <groupname> [<groupname>]*在执行该服务之前变换组名
oneshot在服务退出时不要重新启动它
class <name>为服务指定一个类名。一个被命名的类中的所有服务都可以一起被启动或停止。如果服务没有通过类选项来指定的话,它是在类default中的
onrestart当服务重新启动时,执行一条命令


下面以init.rc文件中的配置为例简要说明一个服务的配置:


service zygote /system/bin/app_process -Xzygote /system/bin -zygote

--start-system-server

class main

socket zygote stream 666

onrestart write /sys/android_power/request_state wake

onrestart write /sys/power/state on

onrestart restart media 

onrestart restart netd


在上面代码中,第一行配置了一个名为zygote的服务,这个服务将会运行/system/bin/app_process,剩余部分为参数(以空格分割)。


剩下的几行代码声明了此服务的选项。这说明zygote是一个类型为main的服务(classmain)并且它会创建一个socket,这个socket的类型为stream,权限为666(socket zygote stream 666)。当重启此服务的时候,需要完成以下事情。


❶写/sys.android_power/request_state为wake


❷写/sys/power/state为on


❸重新启动media服务


❹重新启动netd服务


init.rc文件需要在init启动期被解析成系统可以识别的数据结构。前面我们读懂了init.rc的含义,下面我们就来看看init是如何保存和组织这些信息的,首先,我们来看看在init中如何表示动作,服务和命令,如下表所示:




组件数据结构说明
列表节点(listnode)
<<struct>>
listnode
+next:listnode
+prev:listnode
 

listnode是一个表示位置的数据结构,可以用来定义不同类型节点(比如动作或者服务)的执行顺序
从左侧的数据结构中可以看出,这里包含了两个listnode的指针,它们用于指向前一个和后一个将要执行的节点
这些信息将帮助各种节点(动作,服务,以及命令等)组成一个双向循环列表
动作(action)
<<struct>>
action
+alist::listnode
+qlist:listnode
+tlist:listnode
+hash:signed int
+*name:char
+commands:listnode
+*current:command
 

action中包含4个表示节点位置信息的节点,它们分别表示它本身在所有动作中的位置(alist),在添加动作的队列中的位置(qlist),以及在某个触发器中的所有动作列表的位置(tlist)
action 数据结构中包含了其他的重要信息,比如动作的名字(name),包含的所有命令列表(commands)以及当前命令
服务(service)
<<struct>>
service
+slist:listnode
+*name:char
+*classname:char
+flags:unsigned
+pid:pid_t
+time_started:time_t
+time_crashed:time_t
+nr_crashed:int
+uid:uid_t
+gid:gid_t
+supp_gids[NR_SVC_SUPP_GIDS]:gid_t
+*sockets:socketinfo
+*envvars:svcenvinfo
+onrestart:action
+*keycodes:int
+nkeycodes:int
+keychord_id:int
+ioprio_class:int
+ioprio_pri:int
+nargs:int
+*arg[1]:char
 

这个数据结构中包含了服务的信息,主要包括如下内容:

❶该服务在所有服务列表中逻辑位置的数据结构“listnode”(slist)

❷服务的基本信息,比如服务的名称,进程的相关信息,所需要参数信息等
命令(command)
<<struct>>
command
+clist:listnode
+(*func)(int nargs,char **args):iint
+nargs:int
+*args[1]:char
 

这个数据结构中包括以下内容:

❶节点的位置信息(clist)

❷命令需要执行的函数的函数指针(func)

❸参数信息:nargs和args[1]


最后,我们通过解析init.rc中的一个片段来说明解析过程。


开始解析之前,需要了解整个解析过程至关重要的一个数据结构,那就是parse_state,它保存了整个解析过程中所处的状态,下图显示了它的“成分”



<<struct>>
parse_state
+*ptr:char
+*text:char
+line:int
+nexttoken:int
+*context:void
+(*parse_line)(struct parse_state *state,int nargs,char **args):void
+*filename:char
 


③用init解析整个init.rc文件


现在我们 回到init启动的初期,这里它调用了init_parse_config_file()方法,而这个方法就是解析init.rc文件的入口。用init解析整个init.rc文件的流程如下图所示。






下面我们了解一下上图中的注解、


注解1:state是一个被命名为parser_satte的结构体,用于保存当前文件的解析状态信息,包括解析的文件(filename),当前解析的行号(line),当前解析的文字指针(ptr),指示下一个动作的变量(nexttoken)以及解析这一行需要的函数指针(parse_line)等。


注解2:next_token()函数位于/system/core/init/parse.c中,用于分析init.rc文件的内容。它只返回3个状态,分别是:T_EOF(文件结束),T_NEWLINE(一行结束)和T_TEXT(表示遇到第一个空格)。


注解3:init.rc中每一行的信息通过空格被分割为若干段,而这些信息共同组成args[INIT_PARSER_MAXARGS]的内容,并由nargs计数。例如on fs经过解析后,这一行分为两段(分别是on和fs),分别存放在args中,计数器的值为2.。


注解4:init.rc的每一行经过分割后,需要分析其类型(由lookup_keyword返回)。/system/core/init/keywords.h中定义了所有关键字的类型。在片段KEYWORD(on,SECTION,0,0)中,on关键字是一个SECTION,有0个(也就是不需要)参数,没有对应的触发函数(也就是最后一个0)。


注解5:state.parse_line是一个函数的指针,可以根据关键字指向两种不同的解析方法——parse_line_service(处理服务的选项)和parse_line_action(处理行为的命令)。按照这个流程,init完成整个init.rc文件的解析,并生成service_list和action_list,后续流程所需要的信息将从这两个列表中获取,将需要执行的命令或启动的服务加入action_queue中,这样就完成了Android系统基础部分的启动。


在启动的过程中,需要特别注意的是,我们通过action_for_each_trigger()方法声明需要执行的命令队列,该方法的代码如下所示:


void action_for_each_trigger(const char * trigger,void (*func)(struct action *act))

struct listnode * node;

struct action *act;

list_for_each(node,&action_list)

act=node_to_item(node,struct action,alist);

if(!strcmp(act->name,trigger))

func(act);


在上述代码中,list_for_each()用于遍历action_list中的每一个节点,返回节点在列表中的位置信息,然后通过node_to_item()方法生成一个action的信息,最后执行func()函数。


action_for_each_trigger()方法在init.rc中是这样调用的:


action_for_each_trigger(early-init,action_add_queue_tail);


它的含义是,在action_list中查找名字为early-init的节点,并将其信息通过action_add_queue_tail()方法加入action_queue队列的尾部。


然后在init的无限循环中遍历action_queue中的每一个节点,执行它们所包含的命令。


讲到这里,我们了解了init如何对待init.rc文件的内容。下面扩展一下知识,概要介绍一下Android系统中*.rc文件的关键字及其使用需求,如下表。如果你想修改或编写自己的.rc文件,那么请关注下表。




关键字类型参数个数
capabilityOPTION0
chdirCOMMAND1
chrootCOMMAND1
classOPTION0
class_startCOMMAND1
class_stopCOMMAND1
class_resetCOMMAND1
consoleOPTION0
criticalOPTION0
disabledOPTION0
domainnameCOMMAND1
execCOMMAND1
exportCOMMAND2
groupOPTION0
hostnameCOMMAND1
ifupCOMMAND1
insmodCOMMAND1
importSECTION1
keycodesOPTION0
mkdirCOMMAND1
mountCOMMAND3
onSECTION0
oneshotOPTION0
onrestartOPTION0
restartCOMMAND1
rmCOMMAND1
rmdirCOMMAND1
serviceSECTION0
setenvOPTION2
setkeyCOMMAND0
setpropCOMMAND2
setrlimitCOMMAND3
socketOPTION0
startCOMMAND1
stopCOMMAND1
triggerCOMMAND1
SymlinkCOMMAND1
sysclktzCOMMAND1
userOPTION0
waitCOMMAND1
writeCOMMAND2
copyCOMMAND2
chownCOMMAND2
chmodCOMMAND2
loglevelCOMMAND1
load_persist_propsCOMMAND0
ioprioOPTION0


2.创建system_service进程


在init进程的启动过程中,比较重要的部分由孵化进程启动system_service进程,下面详细介绍一下这个部分。system_service进程将会为我们创建一些重要的Android核心服务,包括ActivityManagerService,PackageManagerService和PowerManagerService等,这些将成为应用程序的基础,并为应用程序提供必要的接口。


①创建流程


完成应用程序的初始化之后,init进程将创建一个名叫system_service的重要进程,而我们将在此进程中创建Android核心服务。下图显示了system_process进程以及核心服务的创建过程。




注解1:init进程会按顺序启动各种类型的服务(包括core和main)。首先启动core类型的服务。然后启动main类型的服务。由于孵化服务为main类型,所以它会在core类型的服务之后启动。因此,这里启动用于管理服务的服务——servicemanager。启动和入口如下所示。


❶启动:service zygote /system/bin/app_process -Xzygote /system/bin --zygote--start-system-server


❷入口:/frameworks/base/cmds/app_process/app_main.cpp的main()函数。


注解2:此时转向/frameworks/base/core/jni/AndroidRuntime.cpp的start()函数。


注解3:启动代码如下:


jmethodId startMeth=env->GetStaticMethodID(startClass,"main",....);

env->CallStaticVoidMethod(startClass,startMeth,strArray);


此时转向com.android.internal.os.ZygoteInit的main()方法执行。


注解4


❶加载frameworks下的preloaded-classes类。


❷加载framework-res.apk下的资源。


注解5:孵化进程的主要目的就是孵化出system_process进程,这个时候流程将转向/frameworks/base/services/Java/com/android/server/SystemServer.java的main()方法执行,而自身进入死循环成为守护进程。


注解6:init1()调用本地android_server_SystemServer_init1(/frameworks/base/services/jni/com_android_server_SystemServer.cpp)后,通过libAndroid_servers.So的system_init()函数启动两个服务并启动init2()、


注解7:这里启动并注册剩余的必需服务(比如包服务和Activity服务等)。最终会启动Launcher来到桌面,至此整个启动过程完成。


②system_service简介


system_service进程非常重要,它创建了许多重要的服务,那么如何加入system_service中并接受管理呢?具体如下面的代码所示:


try

Slog.i(TAG,"Backup Service");

ServiceManager.addService(Context.BACKUP_SERVICE,new BackupManagerService(context));

catch(Throwable e)

Slog.e(TAG,"Failure starting Backup Service",e);


以上代码展示了system_process如何将备份服务加入服务管理器中的,其中粗体部分的代码完成了两件事情:第一,创建备份服务;第二,使用ServiceManager的addService()方法将创建出来的备份服务实例加入服务管理器中加以管理。


下表列出了system_service的服务关键字等知识。


服务关键字备注
entropyEntropyService熵服务
powerPowerManagerService电源管理服务(Context.POWER_SERVICE)
activityActivityManagerServiceActivity管理服务
telephony.registryTelephonyRegistry电话服务
packagePackageManagerService包管理服务
accountAccountManagerService账户管理服务(Context.ACCOUT_SERVICE)
batteryBatteryService电池服务
vibratorVibratorService振动服务
alarmAlarmManagerService报警服务(Context.ALARM_SERVICE)
windowWindowManagerService窗口服务(Context.WINDOW_SERVICE)
bluetoothBluetoothService蓝牙服务(BluetoothAdapter.BLUETOOTH_SERVICE)
statusbarStatusBarManagerService状态栏服务(Context.STATUS_BAR_SERVICE)
input_methodInputMethodManagerService输入法管理服务(Context.INPUT_METHOD_SERVICE)
locationLocationManagerService位置管理服务(Context.LOCATION_SERVICE)
wallpaperWallpaperManagerService壁纸管理服务(Context.WALLPAPER_SERVICE)
audioAudioservice声音服务(Context.AUDIO_SERVICEÿ

以上是关于Android核心服务解析篇——Android系统的启动的主要内容,如果未能解决你的问题,请参考以下文章

“无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析

Android系统启动流程解析init进程启动过程

android核心服务初探

福利Android Framwork之PKMS源码解析---调用方式

Android进阶篇最新Android源码精编解析,有效阅读源码的法门

Android P JobScheduler服务源码解析—— 使用Job需要注意的点

(c)2006-2019 SYSTEM All Rights Reserved IT常识