Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android核心服務解析篇(一)——Android系統的啟動

Android核心服務解析篇(一)——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..rc文件,繼續解析。

 

Ⅱ在解析init.rc文件的過程中,系統會根據該文件的內容形成一些需要命令,動作或者觸發器的列表並將這些存入在內在中,以便在必要的時候使用。不同的廠商可能根據不同的硬件需求定制不同的.rc文件,這些.rc文件的名稱一般為“init..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

 

 
 

 

 
 

 

 
 

.........

 

觸發器是一些字符串,這些字符串可用於匹配一定類型的事件,並且用於觸發動作。下表羅列了一些觸發器的定義。

 

 

 

 

觸發器 說明 boot 當初始化流程觸發的時候,boot是首先被觸發的動作(在完成/init.conf文件加載之後) = 當以命名的屬性被設為特定的值時,該觸發器發生 device-added- 當添加設備節點時,device-added-定義的觸發器運行 device-removed- 當移除設備節點時,device-removed-定義的觸發器運行 service-exited- 當指定的服務退出時,service-exited-類型的觸發器運行 自定義的觸發器,可由init代碼負責管理

 

Ⅱ命令

 

命令是組成動作的成員,也就是說,動作由一個個命令組成。下表羅列了動作支持的命令。

 

 

 

 

命令 說明 exec []* fork並執行程序()。這在程序完成執行之前將阻塞一切進程,因此最好避免使用exec命令。該命令中兩個參數的含義如下所示。
?:可執行文件的路徑
?[]*:可執行文件所需的參數,參數個數可以是0或者多個
export 設置名字為的環境變量為 ifup 打開網絡接口 import 解析一個初始化配置文件,導入系統中 hostname 設置主機名 chdir 修改工作目錄,它的功能和cd命令一樣 chmod 修改文件的訪問權限 chown 修改指定的問題的所有者和組 chroot 修改進程根目錄為 class_start 啟動類別的服務,如果它們沒有運行的話 class_stio 停止類別的服務,如果它們已經處於運行狀態的話 domainname 設置域名 insmod 上安裝模塊 mkdir [mode] [owner] [group] 創建一個目錄,其中目錄路徑以及名稱由指明。這裡可以通過參數給定目錄的模式,所有者和組。如果沒有提供[mode] [owner] [group],則用權限755來創建目錄,並且它屬於root用戶root組 mount []* 嘗試在目錄 上掛載被命名的設備,可能是mtd@name的形式,以便指定名為mtd塊的設備。包括ro,rw,remount和noatime等 setkey TBD setprop 設置系統屬性 setrlimit 設置指定資源的使用限制 start 啟動指定的服務,如果服務還沒有運行的話 stop 停止指定的服務,如果服務目前正在運行的話 symlink 用值來在上創建一個符號鏈接 sysclktz 設置系統鬧鐘基准(如果系統鬧鐘為GMT,則為0) trigger 觸發一個事件。用於執行該觸發器中的操作 write []*上打開文件並且用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 []*

 

 

...........

 

其中各個參數的含義如下所示:

 

?:為服務指定一個名字。

 

?:指定服務需要執行的文件路徑。

 

?[]*:啟動服務所需要的參數,參數個數可以是0個或者多個。

 

Ⅳ選項

 

選項是服務的修改器,可以影響如何以及何時初始化運行服務。下表羅列了選項列表。

 

 

 

 

選項 說明 critical 這是一個對於設備來說比較關鍵的服務,如果它在4分鐘內退出超過4次,那麼設備將重新啟動並進入recovery模式。 disabled 這個服務不能通過類別自動啟動,它必須通過服務名字來顯示啟動 setenv 設置啟動進程中環境變量(由指定)的值為 socket [ []] 創建名為/dev/socket/的一個Unix域端口並且將它的fd傳遞到被啟動的進程上
必須是dgram,stream,seqpacket.設置用戶和組的默認值為0
user 在執行該服務之前變換用戶名,如果進程需要Linux的能力,就不能使用該命令 group []* 在執行該服務之前變換組名 oneshot 在服務退出時不要重新啟動它 class 為服務指定一個類名。一個被命名的類中的所有服務都可以一起被啟動或停止。如果服務沒有通過類選項來指定的話,它是在類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
+next:listnode
+prev:listnode   listnode是一個表示位置的數據結構,可以用來定義不同類型節點(比如動作或者服務)的執行順序
從左側的數據結構中可以看出,這裡包含了兩個listnode的指針,它們用於指向前一個和後一個將要執行的節點
這些信息將幫助各種節點(動作,服務,以及命令等)組成一個雙向循環列表 動作(action) <>
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) <>
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) <>
command
+clist:listnode
+(*func)(int nargs,char **args):iint
+nargs:int
+*args[1]:char   這個數據結構中包括以下內容:

?節點的位置信息(clist)

?命令需要執行的函數的函數指針(func)

?參數信息:nargs和args[1]

 

 

最後,我們通過解析init.rc中的一個片段來說明解析過程。

 

開始解析之前,需要了解整個解析過程至關重要的一個數據結構,那就是parse_state,它保存了整個解析過程中所處的狀態,下圖顯示了它的“成分”

 

 

 

<>
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文件,那麼請關注下表。

 

 

 

 

關鍵字 類型 參數個數 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的服務關鍵字等知識。

 

 

服務關鍵字 類 備注 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設備了。
 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved