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(onkeyword相关的部分),service_list(service_keyword相关的部分)以及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域port而且将它的fd传递到被启动的进程上
<type>必须是dgram,stream,seqpacket.设置用户和组的默认值为0
user <username>在运行该服务之前变换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是一个函数的指针,能够依据keyword指向两种不同的解析方法——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文件的keyword及其使用需求,例如以下表。

假设你想改动或编写自己的.rc文件,那么请关注下表。




keyword类型參数个数
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的服务keyword等知识。


服务keyword备注
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)
userUserManagerService用户管理服务(Context.USER_SERVICE)


以下以获取声音服务为例介绍获取服务的方法:


AudioService as=(AudioService)context.getSystemService(Context.AUDIO_SERVICE);


此时整个系统也就完毕了启动工作,这也意味着我们能够開始使用Android设备了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值