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命令。該命令中兩個參數的含義例如以下所看到的。 |
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) |
|
listnode是一個表示位置的數據結構,能夠用來定義不同類型節點(比方動作或者服務)的運行順序 從左側的數據結構中能夠看出。這裏包括了兩個listnode的指針。它們用於指向前一個和後一個將要運行的節點 這些信息將幫助各種節點(動作,服務,以及命令等)組成一個雙向循環列表 |
|||
動作(action) |
|
action中包括4個表示節點位置信息的節點,它們分別表示它本身在全部動作中的位置(alist),在加入動作的隊列中的位置(qlist),以及在某個觸發器中的全部動作列表的位置(tlist) action 數據結構中包括了其它的重要信息,比方動作的名字(name),包括的全部命令列表(commands)以及當前命令 |
|||
服務(service) |
|
這個數據結構中包括了服務的信息。主要包括例如以下內容: ?該服務在全部服務列表中邏輯位置的數據結構“listnode”(slist) ?服務的基本信息。比方服務的名稱,進程的相關信息,所須要參數信息等 |
|||
命令(command) |
|
這個數據結構中包含下面內容: ?節點的位置信息(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系統的啟動