深入理解 Android 卷I - 第3章 深入理解init

原文地址:http://wiki.jikexueyuan.com/project/deep-android-v1/

第3章  深入理解init

本章主要內容

·  深入分析init。

本章涉及的源代碼文件名及位置

下面是本章分析的源碼文件名及其位置。

·  init.c

system/core/init/init.c

·  parser.c

system/core/init/parser.c

·  builtins.c

system/core/init/builtins.c

·  keywords.h

system/core/init/keywords/h

·  init.rc

system/core/rootdir/init.rc

·  properties_service.c

system/core/init/properties_service.c

·  libc_init_dynamic.c

bionic/libc/bionic/libc_init_common.c

·  libc_init_common.c

bionic/libc/bionic/libc_init_common.c

·  properties.c

system/core/libcutils/properties.c

3.1  概述

 init是一個進程,確切地說,它是Linux系統中用戶空間的第一個進程。由於Android是基於linux內核的,所以init也是android系統中用戶空間的第一個進程,它的進程號是1。作爲天字第一號的進程,init被賦予了很多極其重要的工作職責,本章將關注其中兩個比較重要的職責:

·  init進程負責創建系統中的幾個關鍵進程,尤其是下一章要介紹的Zygote,它更是Java世界的開創者。那麼,init進程是如何創建Zygote的呢?

·  Android系統有很多屬性,於是init就提供了一個property service(屬性服務)來管理它們。那麼這個屬性服務是怎麼工作的呢?

如上所述,本章將通過下面兩方面內容來分析init:

·  init如何創建zygote。

·  init的屬性服務是如何工作的。

 

3.2  init分析

init進程的入口函數是main,它的代碼如下所示:

[-->init.c]

int main(int argc, char **argv)

{

    intdevice_fd = -1;

    intproperty_set_fd = -1;

    intsignal_recv_fd = -1;

    intkeychord_fd = -1;

    int fd_count;

    ints[2];

    intfd;

    structsigaction act;

    chartmp[PROP_VALUE_MAX];

    structpollfd ufds[4];

    char*tmpdev;

    char*debuggable;

   

   //設置子進程退出的信號處理函數,該函數爲sigchld_handler。

   act.sa_handler = sigchld_handler;

    act.sa_flags= SA_NOCLDSTOP;

   act.sa_mask = 0;

   act.sa_restorer = NULL;

   sigaction(SIGCHLD, &act, 0);

  

   ......//創建一些文件夾,並掛載設備,這些是和Linux相關的,不擬做過多討論。

   mkdir("/dev/socket", 0755);

   mount("devpts", "/dev/pts", "devpts", 0,NULL);

   mount("proc", "/proc", "proc", 0, NULL);

   mount("sysfs", "/sys", "sysfs", 0, NULL);

 

    //重定向標準輸入/輸出/錯誤輸出到/dev/_null_。

open_devnull_stdio();

/*

設置init的日誌輸出設備爲/dev/__kmsg__,不過該文件打開後,會立即被unlink了,

這樣,其他進程就無法打開這個文件讀取日誌信息了。

*/

   log_init();

   

   //上面涉及很多和Linux系統相關的知識,不熟悉的讀者可自行研究,它們不影響我們的分析

   //解析init.rc配置文件

   parse_config_file("/init.rc");

 

    ......

    //下面這個函數通過讀取/proc/cpuinfo得到機器的Hardware名,我的HTCG7手機爲bravo。

   get_hardware_name();

snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);

//解析這個和機器相關的配置文件,我的G7手機對應文件爲init.bravo.rc。

   parse_config_file(tmp);

 

/*

解析完上述兩個配置文件後,會得到一系列的Action(動作),下面兩句代碼將執行那些處於

early-init階段的Action。init將動作執行的時間劃分爲四個階段:early-init、init、

early-boot、boot。由於有些動作必須在其他動作完成後才能執行,所以就有了先後之分。哪些

動作屬於哪個階段由配置文件決定。後面會介紹配置文件的相關知識。

*/

   action_for_each_trigger("early-init", action_add_queue_tail);

   drain_action_queue();

 

/*

創建利用Uevent和Linux內核交互的socket。關於Uevent的知識,第9章中對

Vold進行分析時會做介紹。

    */

   device_fd = device_init();

    //初始化和屬性相關的資源

property_init();

//初始化/dev/keychord設備,這和調試有關,本書不討論它的用法。讀者可以自行研究,

//內容比較簡單。

   keychord_fd = open_keychord();

 

    ......

/*

  INIT_IMAGE_FILE定義爲”/initlogo.rle”,下面這個函數將加載這個文件作爲系統的開機

 畫面,注意,它不是開機動畫控制程序bootanimation加載的開機動畫文件。

*/

if(load_565rle_image(INIT_IMAGE_FILE) ) {

   /*

如果加載initlogo.rle文件失敗(可能是沒有這個文件),則會打開/dev/ty0設備,並

輸出”ANDROID”的字樣作爲開機畫面。在模擬器上看到的開機畫面就是它。

*/

      ......

      }

   }

 

    if(qemu[0])

       import_kernel_cmdline(1);

   ......

//調用property_set函數設置屬性項,一個屬性項包括屬性名和屬性值。

   property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");

 

    ......//執行位於init階段的動作

   action_for_each_trigger("init", action_add_queue_tail);

   drain_action_queue();

 

    //啓動屬性服務

   property_set_fd = start_property_service();

 

/*

調用socketpair函數創建兩個已經connect好的socket。socketpair是Linux的系統調用,

不熟悉的讀者可以利用man socketpair查詢相關信息。後面就會知道它們的用處了。

*/

    if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {

       signal_fd = s[0];

       signal_recv_fd = s[1];

        ......

    }

 

    ......

 

    //執行配置文件中early-boot和boot階段的動作。

   action_for_each_trigger("early-boot", action_add_queue_tail);

   action_for_each_trigger("boot", action_add_queue_tail);

   drain_action_queue();

......    

   

//init關注來自四個方面的事情。

    ufds[0].fd= device_fd;//device_fd用於監聽來自內核的Uevent事件

   ufds[0].events = POLLIN;

   ufds[1].fd = property_set_fd;//property_set_fd用於監聽來自屬性服務器的事件

ufds[1].events= POLLIN;

//signal_recv_fd由socketpair創建,它的事件來自另外一個socket。

   ufds[2].fd = signal_recv_fd;

   ufds[2].events = POLLIN;

   fd_count = 3;

if(keychord_fd > 0) {

   //如果keychord設備初始化成功,則init也會關注來自這個設備的事件。

       ufds[3].fd = keychord_fd;

       ufds[3].events = POLLIN;

       fd_count++;

}

......

 

#if BOOTCHART

    ......//與Boot char相關,不做討論了。

/*

Boot chart是一個小工具,它能對系統的性能進行分析,並生成系統啓動過程的圖表,

以提供一些有價值的信息,而這些信息最大的用處就是幫助提升系統的啓動速度。

    */

#endif

  for(;;) {

        //從此init將進入一個無限循環。

       int nr, i, timeout = -1;

 

       for (i = 0; i < fd_count; i++)

           ufds[i].revents = 0;

       

        //在循環中執行動作

       drain_action_queue();

       restart_processes(); //重啓那些已經死去的進程

......

#if BOOTCHART

        ...... // Boot Chart相關

#endif

        //調用poll等待一些事情的發生

        nr= poll(ufds, fd_count, timeout);

       ......

       //ufds[2]保存的是signal_recv_fd,用於接收來自socket的消息。

        if(ufds[2].revents == POLLIN) {

           //有一個子進程去世,init要處理這個事情

            read(signal_recv_fd, tmp, sizeof(tmp));

           while (!wait_for_one_process(0))

               ;

           continue;

        }

 

        if(ufds[0].revents == POLLIN)

           handle_device_fd(device_fd);//處理Uevent事件

        if(ufds[1].revents == POLLIN)

           handle_property_set_fd(property_set_fd);//處理屬性服務的事件。

        if(ufds[3].revents == POLLIN)

           handle_keychord(keychord_fd);//處理keychord事件。

    }

 

    return0;

}

從上面的代碼中可知,init的工作任務還是很重的。上面的代碼雖已省略了不少行,可結果還是很長,不過從本章要分析的兩個知識點來看,可將init的工作流程精簡爲以下四點:

·  解析兩個配置文件,其中,將分析對init.rc文件的解析。

·  執行各個階段的動作,創建Zygote的工作就是在其中的某個階段完成的。

·  調用property_init初始化屬性相關的資源,並且通過property_start_service啓動屬性服務。

·  init進入一個無限循環,並且等待一些事情的發生。重點關注init如何處理來自socket和來自屬性服務器相關的事情。

精簡工作流程,是以後分析代碼時常用的方法。讀者在分析代碼的過程中,也可使用這種方法。

3.2.1  解析配置文件

根據上面的代碼可知,在init中會解析兩個配置文件,其中一個是系統配置文件init.rc,另外一個是和硬件平臺相關的配置文件。以HTC G7手機爲例,這個配置文件名爲init.bravo.rc,其中bravo是硬件平臺的名稱。對這兩個配置文件進行解析,調用的是同一個parse_config_file函數。下面就來看這個函數,在分析過程中以init.rc爲主。

[-->parser.c]

int parse_config_file(const char *fn)

{

char *data;

data = read_file(fn, 0);//讀取配置文件的內容,這個文件是init.rc。

if (!data) return -1;

parse_config(fn,data); //調用parse_config做真正的解析

return 0;

}

讀取完文件的內容後,將調用parse_config進行解析,這個函數的代碼如下所示:

[-->parser.c]

static void parse_config(const char *fn, char*s)

{

struct parse_state state;

char *args[SVC_MAXARGS];

int nargs;

 

nargs = 0;

state.filename = fn;

state.line = 1;

state.ptr = s;

state.nexttoken = 0;

state.parse_line = parse_line_no_op; //設置解析函數,不同的內容用不同的解析函數

for (;;) {

    switch(next_token(&state)) {

      case T_EOF:

           state.parse_line(&state, 0, 0);

           return;

      caseT_NEWLINE:

           if (nargs) {

              //得到關鍵字的類型

               int kw = lookup_keyword(args[0]);

               if (kw_is(kw, SECTION)) { //判斷關鍵字類型是不是SECTION。

                    state.parse_line(&state,0, 0);

                   parse_new_section(&state,kw, nargs, args);//解析這個SECTION。

               } else {

                   state.parse_line(&state, nargs, args);

               }

               nargs = 0;

           }

           break;

       case T_TEXT:

          ......

           break;

        }

    }

}

上面就是parse_config函數,代碼雖短,實際卻比較複雜。從整體來說,parse_config首先會找到配置文件的一個section,然後針對不同的 section使用不同的解析函數來解析。那麼,什麼是section呢?這和init.rc文件的組織結構有關。先不必急着去看init.rc,還是先到代碼中去尋找答案。

1. 關鍵字定義

keywords.h這個文件定義了init中使用的關鍵字,它的用法很有意思,先來看這個文件,代碼如下所示:

[-->keywords.h]

#ifndef KEYWORD //如果沒有定義KEYWORD宏,則走下面的分支

......//聲明一些函數,這些函數就是前面所說Action的執行函數。

int do_class_start(int nargs, char **args);

int do_class_stop(int nargs, char **args);

......

int do_restart(int nargs, char **args);

......

#define __MAKE_KEYWORD_ENUM__  //定義一個宏

/*

定義KEYWORD宏,雖然有四個參數,不過這裏只用第一個,其中K_##symbol中的##表示連接

的意思,即最後得到的值爲K_symbol。symbol其實就是init.rc中的關鍵字

*/

#define KEYWORD(symbol, flags, nargs, func)K_##symbol,

enum { //定義一個枚舉,這個枚舉定義了各個關鍵字的枚舉值。

   K_UNKNOWN,

#endif

......

//根據上面KEYWORD的定義,這裏將得到一個枚舉值K_class,

   KEYWORD(class,       OPTION,  0, 0)

   KEYWORD(class_start, COMMAND, 1, do_class_start)//K_class_start,

KEYWORD(class_stop,  COMMAND, 1, do_class_stop)//K_class_stop,

KEYWORD(on,          SECTION, 0, 0)//K_on,

   KEYWORD(oneshot,     OPTION,  0, 0)

   KEYWORD(onrestart,   OPTION,  0, 0)

   KEYWORD(restart,     COMMAND, 1,do_restart)

   KEYWORD(service,     SECTION, 0,0)

    ......

   KEYWORD(socket,      OPTION,  0, 0)

   KEYWORD(start,       COMMAND, 1,do_start)

   KEYWORD(stop,        COMMAND, 1,do_stop)

    ......

#ifdef __MAKE_KEYWORD_ENUM__

   KEYWORD_COUNT,

};

#undef __MAKE_KEYWORD_ENUM__

#undef KEYWORD //取消KEYWORD宏定義

#endif

keywords.h好像沒什麼奇特,不過是個簡單的頭文件。爲什麼說它的用法很有意思呢?來看代碼中是如何使用它的,如下所示:

[-->parser.c]

......//parser.c中將包含keywords.h頭文件,而且還不只一次!!

//第一次包含keywords.h,根據keywords.h的代碼,我們首先會得到一個枚舉定義

#include "keywords.h"

/*

重新定義KEYWORD宏,這回四個參數全用上了,看起來好像是一個結構體。其中#symbol表示

一個字符串,其值爲“symbol”。

*/

#define KEYWORD(symbol, flags, nargs, func) \

    [K_##symbol ] = { #symbol, func, nargs + 1, flags, },

 

//定義一個結構體keyword_info數組,它用來描述關鍵字的一些屬性,請注意裏面的註釋內容。

struct {

    constchar *name;  //關鍵字的名。

    int(*func)(int nargs, char **args);//對應關鍵字的處理函數。

unsignedchar nargs;//參數個數,每個關鍵字的參數個數是固定的。

//關鍵字的屬性,有三種屬性,COMMAND、OPTION和SECTION。其中COMMAND有對應的處理函數

   unsigned char flags;

} keyword_info[KEYWORD_COUNT] = {

[ K_UNKNOWN ] = { "unknown", 0, 0, 0},

/*

第二次包含keywords.h,由於已經重新定了KEYWORD宏,所以以前那些作爲枚舉值的關鍵字

現在變成keyword_info數組的索引了。

*/

#include "keywords.h"   

};

#undef KEYWORD

 

//一些輔助宏,幫助我們快速操作keyword_info中的內容。

#define kw_is(kw, type) (keyword_info[kw].flags& (type))

#define kw_name(kw) (keyword_info[kw].name)

#define kw_func(kw) (keyword_info[kw].func)

#define kw_nargs(kw) (keyword_info[kw].nargs)

現在領略了keywords.h的神奇之處了吧?原來它幹了兩件事情:

·  第一次包含keyworks.h時,它聲明瞭一些諸如do_classstart這樣的函數,另外還定義了一個枚舉,枚舉值爲K_class,K_mkdir等關鍵字。

·  第二次包含keywords.h後,得到了一個keyword_info結構體數組,這個keyword_info結構體數組以前面定義的枚舉值爲索引,存儲對應的關鍵字信息,這些信息包括關鍵字名、處理函數、處理函數的參數個數,以及屬性。

目前,關鍵字信息中最重要的就是symbol和flags了。什麼樣的關鍵字被認爲是section呢?根據keywords.h的定義,symbol爲下面兩個的關鍵字表示section:

KEYWORD(on,          SECTION, 0, 0)

KEYWORD(service,     SECTION, 0, 0)

有了上面的知識,再來看配置文件init.rc的內容。

2. init.rc的解析

init.rc的內容如下所示:(我們截取了部分內容,注意,其中的註釋符號是#。)

[-->init.rc]

on init  #根據上面的分析,on關鍵字標示一個section,對應的名字是”init”

 ......  #下面所有的內容都屬於這個section,直到下一個section開始時。

 exportPATH /sbin:/system/sbin:/system/bin:/system/xbin

 exportLD_LIBRARY_PATH /system/lib

 exportANDROID_BOOTLOGO 1 #根據keywords.h的定義,export表示一個COMMAND

export ANDROID_ROOT /system

 exportANDROID_ASSETS /system/app

...... #省略部分內容

on boot  #這是一個新的section,名爲”boot”

   ifup lo#這是一個COMMAND

   hostname localhost

   domainname localdomain

    ......

   #class_start也是一個COMMAND,對應函數爲do_class_start,很重要,切記。

    class_startdefault 

    ......

#下面這個section的意思是:待屬性persist.service.adb.enable的值變爲1後,

#需要執行對應的COMMAND,這個COMMAND是start adbd

     onproperty:persist.service.adb.enable=1

         start adbd //start是一個COMMAND

     on property:persist.service.adb.enable=0

         stopadbd

    ......

#service也是section的標示,對應section的名爲“zygote“

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

 --start-system-server

    socketzygote stream 666  #socket關鍵字表示OPTION

   onrestart write /sys/android_power/request_state wake #onrestart也是OPTION

   onrestart write /sys/power/state on

   onrestart restart media

#一個section,名爲”media”

service media /system/bin/mediaserver

    usermedia

    groupsystem audio camera graphics inet net_bt net_bt_admin net_raw

iopriort 4

從上面對init.rc的分析中可知:

·  一個section的內容從這個標示section的關鍵字開始,到下一個標示section的地方結束。

·  init.rc中出現了名爲boot和init的section,這裏的boot和init,就是前面介紹的動作執行四個階段中的boot和init。也就是說,在boot階段執行的動作都是由boot這個section定義的。

另外還可發現,zygote被放在了一個servicesection中。下面以zygote這個section爲例,介紹service是如何解析的。

3.2.2  解析service

zygote對應的service section內容是:

[-->init.rc::zygote]

service zygote /system/bin/app_process -Xzygote/system/bin –zygote \ --start-system-server

socketzygote stream 666  #socket是OPTION

#下面的onrestart是OPTION,而write和restart是COMMAND

    onrestartwrite /sys/android_power/request_state wake

   onrestart write /sys/power/state on

onrestartrestart media

解析section的入口函數是parse_new_section,它的代碼如下所示:

[-->parser.c]

void parse_new_section(struct parse_state*state, int kw,

                       int nargs, char **args)

{

   switch(kw) {

    caseK_service:  //解析service,用parse_service和parse_line_service

       state->context = parse_service(state, nargs, args);

        if(state->context) {

           state->parse_line = parse_line_service;

            return;

        }

       break;

    caseK_on: //解析on section

        ......//讀者可以自己研究

       break;

    }

   state->parse_line = parse_line_no_op;

}

其中,service解析時,用到了parse_service和parse_line_service兩個函數,在分別介紹它們之前,先看init是如何組織這個service的。

1. service結構體

init中使用了一個叫service的結構體來保存和service section相關的信息,不妨來看這個結構體,代碼如下所示:

[-->init.h::service結構體定義]

struct service {

 //listnode是一個特殊的結構體,在內核代碼中用得非常多,主要用來將結構體鏈接成一個

  //雙向鏈表。init中有一個全局的service_list,專門用來保存解析配置文件後得到的service。

   struct listnode slist; 

    constchar *name; //service的名字,對應我們這個例子就是”zygote”。

    constchar *classname; //service所屬class的名字,默認是”defult”

   unsigned flags;//service的屬性

    pid_tpid;    //進程號

    time_ttime_started;   //上一次啓動的時間

    time_ttime_crashed;  //上一次死亡的時間

    intnr_crashed;        //死亡次數

     uid_tuid;     //uid,gid相關

    gid_tgid;

    gid_tsupp_gids[NR_SVC_SUPP_GIDS];

    size_tnr_supp_gids;

   /*

有些service需要使用socket,下面這個socketinfo用來描述socket的相關信息。

我們的zygote也使用了socket,配置文件中的內容是socket zygote stream 666。

它表示將創建一個AF_STREAM類型的socket(其實就是TCP socket),該socket的名爲“zygote”,

讀寫權限是666。

   */

structsocketinfo *sockets; 

//service一般運行在單獨的一個進程中,envvars用來描述創建這個進程時所需的環境變量信息。

    structsvcenvinfo *envvars; 

   /*

  雖然關鍵字onrestart標示一個OPTION,可是這個OPTION後面一般跟着COMMAND,

 下面這個action結構體可用來存儲command信息,馬上就會分析到它。

*/

    structaction onrestart;

   

    //和keychord相關的內容

    int*keycodes;

    intnkeycodes;

    intkeychord_id;

    //io優先級設置

    intioprio_class;

    intioprio_pri;

    //參數個數

    intnargs;

    //用於存儲參數

    char*args[1];

}; 

我們現在已瞭解的service的結構體,相對來說還算是清晰易懂的。而zygote中的那三個onrestart該怎麼表示呢?請看service中使用的這個action結構體:

[-->init.h::action結構體定義]

struct action {

/*

一個action結構體可存放在三個雙向鏈表中,其中alist用於存儲所有action,

qlist用於鏈接那些等待執行的action,tlist用於鏈接那些待某些條件滿足後

就需要執行的action。

*/

    structlistnode alist;

   structlistnode qlist;

    structlistnode tlist;

 

   unsigned hash;

    constchar *name;

   

   //這個OPTION對應的COMMAND鏈表,以zygote爲例,它有三個onrestart option,所以

  //它對應會創建三個command結構體。

    structlistnode commands;

    structcommand *current;

};

瞭解了上面的知識後,你是否能猜到parse_service和parse_line_service的作用了呢?馬上就來看它們。

2. parse_service

parse_service的代碼如下所示:

[-->parser.c]

static void *parse_service(struct parse_state*state, int nargs, char **args)

{

    structservice *svc; //聲明一個service結構體

    ......

    //init維護了一個全局的service鏈表,先判斷是否已經有同名的service了。

    svc =service_find_by_name(args[1]);

    if(svc) {

       ......  //如果有同名的service,則不能繼續後面的操作。

       return 0;

    }

   

nargs-= 2;

    svc =calloc(1, sizeof(*svc) + sizeof(char*) * nargs);

    ......

   svc->name = args[1];

svc->classname= "default";//設置classname爲”default”,這個很關鍵!

   memcpy(svc->args, args + 2, sizeof(char*) * nargs);

   svc->args[nargs] = 0;

   svc->nargs = nargs;

svc->onrestart.name= "onrestart";

 

   list_init(&svc->onrestart.commands);

    //把zygote這個service加到全局鏈表service_list中。

   list_add_tail(&service_list, &svc->slist);

    returnsvc;

}

parse_service函數只是搭建了一個service的架子,具體的內容尚需由後面的解析函數來填充。來看service的另外一個解析函數parse_line_service。

3. parse_line_service

parse_line_service的代碼如下所示:

[-->parser.c]

static void parse_line_service(structparse_state *state, int nargs,

char **args)

{

    structservice *svc = state->context;

    structcommand *cmd;

    int i,kw, kw_nargs;

    ......

   svc->ioprio_class = IoSchedClass_NONE;

    //其實還是根據關鍵字來做各種處理。

    kw =lookup_keyword(args[0]);

    switch(kw) {

    caseK_capability:

       break;

    caseK_class:

        if(nargs != 2) {

           ......

        }else {

           svc->classname = args[1];

        }

       break;

    ......

caseK_oneshot:

   /*

這是service的屬性,它一共有五個屬性,分別爲:

SVC_DISABLED:不隨class自動啓動。下面將會看到class的作用。

SVC_ONESHOT:退出後不需要重啓,也就是這個service只啓動一次就可以了。

SVC_RUNNING:正在運行,這是service的狀態。

SVC_RESTARTING:等待重啓,這也是service的狀態。

SVC_CONSOLE:該service需要使用控制檯 。

SVC_CRITICAL:如果在規定時間內該service不斷重啓,則系統會重啓並進入恢復模式。

zygote沒有使用任何屬性,這表明它:會隨着class的處理自動啓動;

退出後會由init重啓;不使用控制檯;即使不斷重啓也不會導致系統進入恢復模式。

       */

       svc->flags |= SVC_ONESHOT;

       break;

    caseK_onrestart: //根據onrestart的內容,填充action結構體的內容

       nargs--;

       args++;

        kw= lookup_keyword(args[0]);

        ......

        //創建command結構體

       cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);

       cmd->func = kw_func(kw);

       cmd->nargs = nargs;

       memcpy(cmd->args, args, sizeof(char*) * nargs);

        //把新建的command加入到雙向鏈表中。

       list_add_tail(&svc->onrestart.commands, &cmd->clist);

       break;

    ......

    caseK_socket: { //創建socket相關信息

       struct socketinfo *si;

        ......

        si= calloc(1, sizeof(*si));

        if(!si) {

           parse_error(state, "out of memory\n");

           break;

        }

       si->name = args[1]; //socket的名字

       si->type = args[2]; //socket的類型

       si->perm = strtoul(args[3], 0, 8); //socket的讀寫權限

        if(nargs > 4)

           si->uid = decode_uid(args[4]);

        if(nargs > 5)

           si->gid = decode_uid(args[5]);

       si->next = svc->sockets;

       svc->sockets = si;

       break;

    }

    ......

   default:

       parse_error(state, "invalid option '%s'\n", args[0]);

    }

}

parse_line_service將根據配置文件的內容填充service結構體,那麼,zygote解析完後會得到什麼呢?圖3-1表示了zygote解析後的結果:

image

圖3-1  zygote解析結果示意圖

從上圖中可知:

·  service_list鏈表將解析後的service全部鏈接到了一起,並且是一個雙向鏈表,前向節點用prev表示,後向節點用next表示。

·  socketinfo也是一個雙向鏈表,因爲zygote只有一個socket,所以畫了一個虛框socket做爲鏈表的示範。

·  onrestart通過commands指向一個commands鏈表,zygote有三個commands。

zygote這個service解析完了,現在就是“萬事俱備,只欠東風”了。接下來要了解的是,init是如何控制service的。

3.2.3  init控制service

先看service是如何啓動的。

1.啓動zygote

init.rc中有這樣一句話:

#class_start是一個COMMAND,對應的函數爲do_class_start,很重要,切記。

 class_startdefault

class_start標示一個COMMAND,對應的處理函數爲do_class_start,它位於boot section的範圍內。爲什麼說它很重要呢?

還記得init進程中的四個執行階段嗎?當init進程執行到下面幾句話時,do_class_start就會被執行了。

//將bootsection節的command加入到執行隊列

action_for_each_trigger("boot",action_add_queue_tail);

//執行隊列裏的命令,class可是一個COMMAND,所以它對應的do_class_start會被執行。

drain_action_queue();

下面來看do_class_start函數:

[-->builtins.c]

int do_class_start(int nargs, char **args)

{

/*

args爲do_class_start的參數,init.rc中只有一個參數,就是default。

下面這個函數將從service_list中尋找classname爲”default”的service,然後

調用service_start_if_not_disabled函數。現在讀者明白了service結構體中

classname的作用了嗎?

*/

service_for_each_class(args[1],service_start_if_not_disabled);

return 0;

}

我們已經知道,zygote這個service的classname的值就是“default”,所以會針對這個service調用service_start_if_not_disabled,這個函數的代碼是:

[-->parser.c]

static void service_start_if_not_disabled(structservice *svc)

{

if (!(svc->flags & SVC_DISABLED)) {

     service_start(svc,NULL); //zygote可沒有設置SVC_DISABLED

 }

}

service_start函數的代碼如下所示:

[-->init.c]

void service_start(struct service *svc, constchar *dynamic_args)

{

    structstat s;

    pid_tpid;

    intneeds_console;

    int n;

 

   svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING));

   svc->time_started = 0;

   

    if(svc->flags & SVC_RUNNING) {

       return;//如果這個service已在運行,則不用處理

    }

  /*

service一般運行於另外一個進程中,這個進程也是init的子進程,所以啓動service前需要判斷

對應的可執行文件是否存在,zygote對應的可執行文件是/system/bin/app_process

*/

    if(stat(svc->args[0], &s) != 0) {

      svc->flags |= SVC_DISABLED;

       return;

    }

    ......

   pid =fork(); //調用fork創建子進程

if(pid == 0) {

    //pid爲零,我們在子進程中

       struct socketinfo *si;

       struct svcenvinfo *ei;

       char tmp[32];

       int fd, sz;

       

//得到屬性存儲空間的信息並加到環境變量中,後面在屬性服務一節中會碰到使用它的地方。

       get_property_workspace(&fd, &sz);

       add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);

        //添加環境變量信息

       for (ei = svc->envvars; ei; ei = ei->next)

           add_environment(ei->name, ei->value);

        //根據socketinfo創建socket

       for (si = svc->sockets; si; si = si->next) {

           int s = create_socket(si->name,

                                  !strcmp(si->type,"dgram") ?

                                  SOCK_DGRAM :SOCK_STREAM,

                                  si->perm,si->uid, si->gid);

           if (s >= 0) {

               //在環境變量中添加socket信息。

                publish_socket(si->name, s);

           }

        }

       ......//設置uid,gid等

     setpgid(0, getpid());

       if(!dynamic_args) {

        /*

執行/system/bin/app_process,這樣就進入到app_process的main函數中了。

fork、execve這兩個函數都是Linux系統上常用的系統調用。

        */

            if (execve(svc->args[0], (char**)svc->args, (char**) ENV) < 0) {

              ......

           }

        }else {

          ......

    }

 

   ......//父進程init的處理,設置service的信息,如啓動時間、進程號,以及狀態等。

   svc->time_started = gettime();

   svc->pid = pid;

   svc->flags |= SVC_RUNNING;

//每一個service都有一個屬性,zygote的屬性爲init.svc.zygote,現在設置它的值爲running

   notify_service_state(svc->name, "running");

}

原來,zygote是通過fork和execv共同創建的!但service結構中的那個onrestart好像沒有派上用場,原因何在?

2. 重啓zygote

根據名字,就可猜到onrestart應該是在zygote重啓時用的。下面先看在zygote死後,它的父進程init會有什麼動作:

[-->init.c]

static void sigchld_handler(int s)

{  //當子進程退出時,init的這個信號處理函數會被調用

   write(signal_fd, &s, 1); //往signal_fd write數據

}

signal_fd,就是在init中通過socketpair創建的兩個socket中的一個,既然會往這個signal_fd中發送數據,那麼另外一個socket就一定能接收到,這樣就會導致init從poll函數中返回:

[-->init.rc::main函數代碼片斷]

 nr =poll(ufds, fd_count, timeout);

 ......

 if(ufds[2].revents == POLLIN) {

   read(signal_recv_fd, tmp, sizeof(tmp));

       while (!wait_for_one_process(0))//調用wait_for_one_process函數處理

         ;

       continue;

 }

 ......

//直接看這個wait_for_one_process函數:

static int wait_for_one_process(int block)

{

    pid_tpid;

    intstatus;

    structservice *svc;

    structsocketinfo *si;

    time_tnow;

    structlistnode *node;

    structcommand *cmd;

 

while( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 &&

errno == EINTR );

    if(pid <= 0) return -1;

    //找到死掉的那個service,現在應該找到了代表zygote的那個service。

svc = service_find_by_pid(pid);

   ......

 

if(!(svc->flags & SVC_ONESHOT)) {

    //殺掉zygote創建的所有子進程,這就是zygote死後,Java世界崩潰的原因。

       kill(-pid, SIGKILL);

   }

 

    //清理socket信息,不清楚的讀者可以通過命令man 7 AF_UNIX查詢一下相關知識。

    for(si = svc->sockets; si; si = si->next) {

       char tmp[128];

       snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s",si->name);

        unlink(tmp);

    }

 

   svc->pid = 0;

   svc->flags &= (~SVC_RUNNING);

 

   if(svc->flags & SVC_ONESHOT) {

       svc->flags |= SVC_DISABLED;

    }

   ......

now= gettime();

/*

如果設置了SVC_CRITICAL標示,則4分鐘內該服務重啓次數不能超過4次,否則

機器會重啓進入recovery模式。根據init.rc的配置,只有servicemanager進程

享有此種待遇。

*/

    if(svc->flags & SVC_CRITICAL) {

        if(svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {

           if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {

              ......

               sync();

               __reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,

                        LINUX_REBOOT_CMD_RESTART2, "recovery");

               return 0;

           }

        }else {

           svc->time_crashed = now;

           svc->nr_crashed = 1;

        }

    }

 

   svc->flags |= SVC_RESTARTING;

//設置標示爲SVC_RESTARTING,然後執行該service onrestart中的COMMAND,這些內容就

//非常簡單了,讀者可以自行學習。

   list_for_each(node, &svc->onrestart.commands) {

       cmd = node_to_item(node, struct command, clist);

       cmd->func(cmd->nargs, cmd->args);

}

//設置init.svc.zygote的值爲restarting。

   notify_service_state(svc->name, "restarting");

    return0;

}

通過上面的代碼,可知道onrestart的作用了,但zygote本身又在哪裏重啓的呢?答案就在下面的代碼中:

[-->init.c::main函數代碼片斷]

for(;;) {

       int nr, i, timeout = -1;

       for (i = 0; i < fd_count; i++)

           ufds[i].revents = 0;

       drain_action_queue(); //poll函數返回後,會進入下一輪的循環

       restart_processes(); //這裏會重啓所有flag標誌爲SVC_RESTARTING的service。

       ......

}

這樣,zygote又回來了!

3.2.4  屬性服務

我們知道,Windows平臺上有一個叫註冊表的東西。註冊表可以存儲一些類似key/value的鍵值對。一般而言,系統或某些應用程序會把自己的一些屬性存儲在註冊表中,即使下次系統重啓或應用程序重啓,它還能夠根據之前在註冊表中設置的屬性,進行相應的初始化工作。Android平臺也提供了一個類型機制,可稱之爲屬性服務(property service)。應用程序可通過這個屬性機制,查詢或設置屬性。讀者可以用adb shell登錄到真機或模擬器上,然後用getprop命令查看當前系統中有哪些屬性。即如我的HTC G7測試結果,如圖3-2所示:(圖中只顯示了部分屬性)

image

圖3-2  HTC G7屬性示意圖

這個屬性服務是怎麼實現的呢?下面來看代碼,其中與init.c和屬性服務有關的代碼有下面兩行:

property_init();

property_set_fd = start_property_service();

分別來看看它們。

1. 屬性服務初始化

(1)創建存儲空間

先看property_init函數,代碼如下所示:

[-->property_service.c]

void property_init(void)

{

init_property_area();//初始化屬性存儲區域

//加載default.prop文件

   load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);

}

在properyty_init函數中,先調用init_property_area函數,創建一塊用於存儲屬性的存儲區域,然後加載default.prop文件中的內容。再看init_property_area是如何工作的,它的代碼如下所示:

[-->property_service.c]

static int init_property_area(void)

{

   prop_area *pa;

 

   if(pa_info_array)

       return -1;

/*

初始化存儲空間,PA_SIZE是這塊存儲空間的總大小,爲32768字節,pa_workspace

爲workspace類型的結構體,下面是它的定義:

typedef struct {

    void *data;   //存儲空間的起始地址

    size_tsize;  //存儲空間的大小

    int fd;   //共享內存的文件描述符

} workspace;

init_workspace函數調用Android系統提供的ashmem_create_region函數創建一塊

共享內存。關於共享內存的知識我們在第7章會接觸,這裏,只需把它當做一塊普通的內存就

可以了。

    */

   if(init_workspace(&pa_workspace, PA_SIZE))

       return -1;

 

   fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

  

//在32768個字節的存儲空間中,有PA_INFO_START(1024)個字節用來存儲頭部信息

   pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);

 

    pa =pa_workspace.data;

   memset(pa, 0, PA_SIZE);

   pa->magic = PROP_AREA_MAGIC;

   pa->version = PROP_AREA_VERSION;

//__system_property_area__這個變量由bionic libc庫輸出,有什麼用呢?

       __system_property_area__ = pa;

 

    return0;

}

上面的內容比較簡單,不過最後的賦值語句可是大有來頭。__system_property_area__是bionic libc庫中輸出的一個變量,爲什麼這裏要給它賦值呢?

原來,雖然屬性區域是由init進程創建,但Android系統希望其他進程也能讀取這塊內存裏的東西。爲做到這一點,它便做了以下兩項工作:

·  把屬性區域創建在共享內存上,而共享內存是可以跨進程的。這一點,已經在上面的代碼中見到了,init_workspace函數內部將創建這個共享內存。

·  如何讓其他進程知道這個共享內存呢?Android利用了gcc的constructor屬性,這個屬性指明瞭一個__libc_prenit函數,當bionic libc庫被加載時,將自動調用這個__libc_prenit,這個函數內部就將完成共享內存到本地進程的映射工作。

(2)客戶端進程獲取存儲空間

關於上面的內容,來看相關代碼:

[-->libc_init_dynamic.c]

//constructor屬性指示加載器加載該庫後,首先調用__libc_prenit函數。這一點和Windows上

//動態庫的DllMain函數類似

void __attribute__((constructor))__libc_prenit(void);

void __libc_prenit(void)

{

    ......

     __libc_init_common(elfdata); //調用這個函數

    ......

}

__libc_init_common函數爲:

[-->libc_init_common.c]

void __libc_init_common(uintptr_t *elfdata)

{

   ......

   __system_properties_init();//初始化客戶端的屬性存儲區域

}

[-->system_properties.c]

int __system_properties_init(void)

{

   prop_area *pa;

    int s,fd;

   unsigned sz;

    char*env;

 

.....

//還記得在啓動zygote一節中提到的添加環境變量的地方嗎?屬性存儲區域的相關信息

//就是在那兒添加的,這裏需要取出來使用了。

    env =getenv("ANDROID_PROPERTY_WORKSPACE");

    //取出屬性存儲區域的文件描述符。關於共享內存的知識,第7章中將會進行介紹。

    fd =atoi(env);

    env =strchr(env, ',');

    if(!env) {

       return -1;

    }

    sz =atoi(env + 1);

//映射init創建的那塊內存到本地進程空間,這樣本地進程就可以使用這塊共享內存了。

//注意,映射的時候指定了PROT_READ屬性,所以客戶端進程只能讀屬性,而不能設置屬性。

    pa =mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);

   

    if(pa== MAP_FAILED) {

       return -1;

    }

 

   if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION)) {

       munmap(pa, sz);

       return -1;

    }

 

   __system_property_area__ = pa;

    return0;

}

上面代碼中很多地方和共享內存有關,在第7章中會對與共享內存有關問題進行介紹,讀者也可先行學習有關共享內存的知識。

總之,通過這種方式,客戶端進程可以直接讀取屬性空間,但沒有權限設置屬性。客戶端進程又是如何設置屬性呢?

2. 啓動屬性服務器

(1)啓動屬性服務器

init進程會啓動一個屬性服務器,而客戶端只能通過和屬性服務器交互才能設置屬性。先來看屬性服務器的內容,它由start_property_service函數啓動,代碼如下所示:

[-->Property_servie.c]

int start_property_service(void)

{

    intfd;

  

   /*

       加載屬性文件,其實就是解析這些文件中的屬性,然後把它設置到屬性空間中去。Android系統

      一共提供了四個存儲屬性的文件,它們分別是:

     #definePROP_PATH_RAMDISK_DEFAULT "/default.prop"

#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"

#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"

#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"

*/

  

   load_properties_from_file(PROP_PATH_SYSTEM_BUILD);

   load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);

load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);

//有一些屬性是需要保存到永久介質上的,這些屬性文件則由下面這個函數加載,這些文件

//存儲在/data/property目錄下,並且這些文件的文件名必須以persist.開頭。這個函數

//很簡單,讀者可自行研究。

    load_persistent_properties();

   //創建一個socket,用於IPC通信。

    fd =create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);

    if(fd< 0) return -1;

   fcntl(fd, F_SETFD, FD_CLOEXEC);

   fcntl(fd, F_SETFL, O_NONBLOCK);

   listen(fd, 8);

    returnfd;

}

屬性服務創建了一個用來接收請求的socket,可這個請求在哪裏被處理呢?事實上,在init中的for循環那裏已經進行相關處理了。

(2)處理設置屬性請求

接收請求的地方是在init進程中,代碼如下所示:

[-->init.c::main函數片斷]

if (ufds[1].revents == POLLIN)

           handle_property_set_fd(property_set_fd);

當屬性服務器收到客戶端請求時,init會調用handle_property_set_fd進行處理。這個函數的代碼如下所示:

[-->property_service.c]

void handle_property_set_fd(int fd)

{

   prop_msg msg;

    int s;

    int r;

    intres;

    structucred cr;

    structsockaddr_un addr;

   socklen_t addr_size = sizeof(addr);

   socklen_t cr_size = sizeof(cr);

    //先接收TCP連接

    if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {

       return;

    }

 

    //取出客戶端進程的權限等屬性。

    if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {

        ......

       return;

    }

   //接收請求數據

    r = recv(s,&msg, sizeof(msg), 0);

   close(s);

    ......

 

   switch(msg.cmd) {

    casePROP_MSG_SETPROP:

       msg.name[PROP_NAME_MAX-1] = 0;

       msg.value[PROP_VALUE_MAX-1] = 0;

        /*

如果是ctl開頭的消息,則認爲是控制消息,控制消息用來執行一些命令,例如用

adb shell登錄後,輸入setprop ctl.start bootanim就可以查看開機動畫了,

關閉的話就輸入setpropctl.stop bootanim,是不是很有意思呢?

        */

       if(memcmp(msg.name,"ctl.",4) == 0) {

           if (check_control_perms(msg.value, cr.uid, cr.gid)) {

               handle_control_message((char*) msg.name + 4, (char*) msg.value);

           }

           ......

        }else {

           //檢查客戶端進程是否有足夠的權限

           if (check_perms(msg.name, cr.uid, cr.gid)) {

               //然後調用property_set設置。

               property_set((char*) msg.name, (char*) msg.value);

           }

           ......

        }

       break;

 

   default:

       break;

    }

}

當客戶端的權限滿足要求時,init就調用property_set進行相關處理,這個函數比較簡單,代碼如下所示:

[-->property_service.c]

int property_set(const char *name, const char*value)

{

   prop_area *pa;

   prop_info *pi;

 

    intnamelen = strlen(name);

    intvaluelen = strlen(value);

    ......

    //從屬性存儲空間中尋找是否已經存在該屬性

    pi =(prop_info*) __system_property_find(name);

 

if(pi!= 0) {

    //如果屬性名以ro.開頭,則表示是隻讀的,不能設置,所以直接返回。

       if(!strncmp(name, "ro.", 3)) return -1;

 

        pa= __system_property_area__;

        //更新該屬性的值

       update_prop_info(pi, value, valuelen);

       pa->serial++;

       __futex_wake(&pa->serial, INT32_MAX);

}else {

   //如果沒有找到對應的屬性,則認爲是增加屬性,所以需要新創建一項。注意,Android支持

   //最多247項屬性,如果目前屬性的存儲空間中已經有247項,則直接返回。

        pa= __system_property_area__;

       if(pa->count == PA_COUNT_MAX) return -1;

 

        pi= pa_info_array + pa->count;

       pi->serial = (valuelen << 24);

       memcpy(pi->name, name, namelen + 1);

        memcpy(pi->value, value, valuelen +1);

 

       pa->toc[pa->count] =

           (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));

 

       pa->count++;

       pa->serial++;

       __futex_wake(&pa->serial, INT32_MAX);

    }

    //有一些特殊的屬性需要特殊處理,這裏,主要是以net.change開頭的屬性。

    if(strncmp("net.", name, strlen("net.")) == 0)  {

        if(strcmp("net.change", name) == 0) {

           return 0;

        }

       property_set("net.change", name);

    } elseif (persistent_properties_loaded &&

       strncmp("persist.", name,strlen("persist.")) == 0) {

       //如果屬性名以persist.開頭,則需要把這些值寫到對應文件中去。

      write_persistent_property(name, value);

}

/*

還記得init.rc中的下面這句話嗎?

on property:persist.service.adb.enable=1

         startadbd

當persist.service.adb.enable屬性置爲1後,就會執行start adbd這個command,

這是通過property_changed函數來完成的,它非常簡單,讀者可以自己閱讀。

*/

   property_changed(name, value);

    return0;

}

好,屬性服務端的工作已經瞭解了,下面看客戶端是如何設置屬性的。

(3)客戶端發送請求

客戶端通過property_set發送請求,property_set由libcutils庫提供,代碼如下所示:

[-->properties.c]

int property_set(const char *key, const char*value)

{

   prop_msg msg;

   unsigned resp;

 

   ......

   msg.cmd = PROP_MSG_SETPROP;//設置消息碼爲PROP_MSG_SETPROP。

   strcpy((char*) msg.name, key);

   strcpy((char*) msg.value, value);

    //發送請求

    returnsend_prop_msg(&msg);

}

 

static int send_prop_msg(prop_msg *msg)

{

    int s;

    int r;

    //建立和屬性服務器的socket連接

    s =socket_local_client(PROP_SERVICE_NAME,

                           ANDROID_SOCKET_NAMESPACE_RESERVED,

                            SOCK_STREAM);

    if(s< 0) return -1;

    //通過socket發送出去

   while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {

       if((errno == EINTR) || (errno == EAGAIN)) continue;

       break;

    }

 

    if(r== sizeof(prop_msg)) {

        r= 0;

    } else{

        r= -1;

    }

 

   close(s);

    returnr;

}

至此,屬性服務器就介紹完了。總體來說,還算比較簡單。

3.3  本章小結

本章講解了init進程如何解析zygote,以及屬性服務器的工作原理,旨在幫助讀者認識這個天字號第一進程。從整體來說,init.rc的解析難度相對最大。相信讀者通過以上實例分析,已經理解了init.rc的解析原理。另外,inti涉及很多和Linux系統相關的知識,有興趣的讀者可以自行研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章