1. 程式人生 > >Android核心服務解析篇(三)——Android系統的啟動

Android核心服務解析篇(三)——Android系統的啟動

onf med cin gets get lld 系統屬性 基本 安裝模塊

從大的方面來說。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>[email protected]<mountoption>包含ro,rw,remount和noatime等
setkey TBD
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 [email protected] /system

mount yaffs2 [email protected] /system ro remount

mount yaffs2 [email protected] /data nosuid nodev

mount yaffs2 [email protected] /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 類型 參數個數
capability OPTION 0
chdir COMMAND 1
chroot COMMAND 1
class OPTION 0
class_start COMMAND 1
class_stop COMMAND 1
class_reset COMMAND 1
console OPTION 0
critical OPTION 0
disabled OPTION 0
domainname COMMAND 1
exec COMMAND 1
export COMMAND 2
group OPTION 0
hostname COMMAND 1
ifup COMMAND 1
insmod COMMAND 1
import SECTION 1
keycodes OPTION 0
mkdir COMMAND 1
mount COMMAND 3
on SECTION 0
oneshot OPTION 0
onrestart OPTION 0
restart COMMAND 1
rm COMMAND 1
rmdir COMMAND 1
service SECTION 0
setenv OPTION 2
setkey COMMAND 0
setprop COMMAND 2
setrlimit COMMAND 3
socket OPTION 0
start COMMAND 1
stop COMMAND 1
trigger COMMAND 1
Symlink COMMAND 1
sysclktz COMMAND 1
user OPTION 0
wait COMMAND 1
write COMMAND 2
copy COMMAND 2
chown COMMAND 2
chmod COMMAND 2
loglevel COMMAND 1
load_persist_props COMMAND 0
ioprio OPTION 0


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 備註
entropy EntropyService 熵服務
power PowerManagerService 電源管理服務(Context.POWER_SERVICE)
activity ActivityManagerService Activity管理服務
telephony.registry TelephonyRegistry 電話服務
package PackageManagerService 包管理服務
account AccountManagerService 賬戶管理服務(Context.ACCOUT_SERVICE)
battery BatteryService 電池服務
vibrator VibratorService 振動服務
alarm AlarmManagerService 報警服務(Context.ALARM_SERVICE)
window WindowManagerService 窗體服務(Context.WINDOW_SERVICE)
bluetooth BluetoothService 藍牙服務(BluetoothAdapter.BLUETOOTH_SERVICE)
statusbar StatusBarManagerService 狀態欄服務(Context.STATUS_BAR_SERVICE)
input_method InputMethodManagerService 輸入法管理服務(Context.INPUT_METHOD_SERVICE)
location LocationManagerService 位置管理服務(Context.LOCATION_SERVICE)
wallpaper WallpaperManagerService 壁紙管理服務(Context.WALLPAPER_SERVICE)
audio AudioService 聲音服務(Context.AUDIO_SERVICE)
user UserManagerService 用戶管理服務(Context.USER_SERVICE)


以下以獲取聲音服務為例介紹獲取服務的方法:


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


此時整個系統也就完畢了啟動工作,這也意味著我們能夠開始使用Android設備了。



Android核心服務解析篇(三)——Android系統的啟動