2018年5月10日 星期四

Android啟動篇— init原理(二)

原文 https://www.cnblogs.com/pepsimaxin/p/6740413.html

================================================== ====== ============================================ ============
= 【原創文章】:參考部分博客內容,學習之餘進行了大量的篩減細化分析= = 【特殊申明】:避諱抄襲侵權之嫌疑,特此說明,歡迎轉載!=    
================================================= ======= =========================================== =============
【前言】
  Android啟動篇— init原理(一)中講解分init進程分析init創建系統目錄並掛在相應系統文件、初始化屬性域、設置系統屬性、啟動配置屬性服務端等一系列複雜工作,很多工作和知識點跟Linux關係很大,所以沒有作過多介紹,而本此對於init.rc的解析則是重中之重,所以單獨拿出來進行詳細分析。
複製代碼
int main( int argc, char ** argv) {
     /* 01.創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/•
    /* 05. 重新設置屬性*/
    /* 06. 創建epoll句柄*/
    /* 07. 裝載子進程信號處理器*/
    /* 08. 設置默認系統屬性*/
    /* 09. 啟動配置屬性的服務端*/
    /* 10.匹配命令和函數之間的對應關係*/ ----------------------------------- -------------------------------------------------- ------ // Android啟動篇— init原理(一)中講解
    /* 11.解析init.rc */ 
    Parser & parser = Parser::GetInstance();        // 構造解析文件用的parser對象
     / / 增加ServiceParser為一個section,對應name為service 
    parser.AddSectionParser( " service " ,std::make_unique ());
     // 增加ActionParser為一個section,對應name為action 
    parser.AddSectionParser( " on " , std::make_unique ());
     //增加ImportParser為一個section,對應name為service 
    parser.AddSectionParser( " import " , std::make_unique ());
    parser.ParseConfig( " /init.rc " );       // 開始實際的解析過程
複製代碼
【正文】
  init.rc是一個配置文件,內部由Android初始化語言編寫(Android Init Language)編寫的腳本,主要包含五種類型語句:Action、Command、Service、Option和Import,在分析代碼的過程中我們會詳細介紹。
  init.rc的配置代碼在:system/core/rootdir/init.rc 中
  init.rc文件是在init進程啟動後執行的啟動腳本,文件中記錄著init進程需執行的操作。
  init.rc文件大致分為兩大部分,一部分是以“on”關鍵字開頭的動作列表(action list):
on early- init       // Action類型語句 
    # Set init and its forked children ' s oom_adj.      // #:註釋符號 
    write /proc/ 1 /oom_score_adj - 1000
    ... ...
    start ueventd
  Action類型語句格式:
on  [&& ]*      // 設置觸發器  
     
          // 動作觸發之後要執行的命令
  另一部分是以“service”關鍵字開頭的服務列表(service list): 如Zygote
service ueventd /sbin/ ueventd
     class core
    critical
    seclabel u:r:ueventd:s0
  Service類型語句格式:
service   [  ]*    // <執行程序路徑><傳遞參數>   
   // option是service的修飾詞,影響什麼時候、如何啟動services   
     
   ...
  借助系統環境變量或Linux命令,動作列表用於創建所需目錄,以及為某些特定文件指定權限,而服務列表用來記錄init進程需要啟動的一些子進程。如上面代碼所示,service關鍵字後的第一個字符串表示服務(子進程)的名稱,第二個字符串表示服務的執行路徑。
  值得一提的是在Android 7.0中對init.rc文件進行了拆分,每個服務一個rc文件。我們要分析的zygote服務的啟動腳本則在init.zygoteXX.rc中定義。
  在init.rc的import段我們看到如下代碼:
import /init.${ro.zygote}.rc      //可以看出init.rc不再直接引入一個固定的文件,而是根據屬性ro.zygote的內容來引入不同的文件
  說明:
  從android5.0開始,android開始支持64位的編譯,zygote本身也就有了32位和64位的區別,所以在這裡用ro.zygote屬性來控制啟動不同版本的zygote進程。
  init.rc位於/system/core/rootdir下。在這個路徑下還包括四個關於zygote的rc文件。分別是Init.zygote32.rc,Init.zygote32_64.rc,Init.zygote64.rc,Init.zygote64_32.rc,由硬件決定調用哪個文件。
  這裡拿32位處理器為例,init.zygote32.rc的代碼如下所示:
複製代碼
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system- server
     class main         # class是一個option,指定zygote服務的類型為main 
    socket zygote stream 660 root system           # socket關鍵字表示一個option,創建一個名為dev/socket/zygote,類型為stream,權限為660的socket 
    onrestart write /sys/android_power/ request_state wake           # onrestart是一個option,說明在zygote重啟時需要執行的command 
    onrestart write /sys /power/ state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks
複製代碼
  “service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server”
  在Init.zygote32.rc中,定義了一個zygote服務:zygote,由關鍵字service告訴init進程創建一個名為zygote的進程,這個進程要執行的程序是:/system/bin/app_process,給這個進程四個參數:
    · -Xzygote:該參數將作為虛擬機啟動時所需的參數
    · /system/bin:代表虛擬機程序所在目錄
    · --zygote:指明以ZygoteInit.java類中的main函數作為虛擬機執行入口
    · --start-system-server:告訴Zygote進程啟動SystemServer進程
   接下來,我們回到源碼當中,繼續分析main函數:
複製代碼
    /* 11.解析init.rc */ 
    Parser & parser = Parser::GetInstance();        // 構造解析文件用的parser對象
     // 增加ServiceParser為一個section,對應name為service 
    parser.AddSectionParser( " service " , std::make_unique ());
     // 增加ActionParser為一個section,對應name為action 
    parser.AddSectionParser( " on " , std::make_unique ());
     // 增加ImportParser為一個section,對應name為service 
    parser.AddSectionParser( " import " , std::make_unique ());
    parser.ParseConfig( "/init.rc");       //開始實際的解析過程
複製代碼
  說明:
  上面在解析init.rc文件時使用了Parser類(在init目錄下的init_parser.h中定義), 初始化ServiceParser用來解析“service”塊,ActionParser用來解析"on"塊,ImportParser用來解析“import ”塊,“import”是用來引入一個init配置文件,來擴展當前配置的。
  /system/core/init/readme.txt 中對init文件中的所有關鍵字做了介紹,主要包含了Actions, Commands, Services, Options, and Imports等,可自行學習解讀。
  分析init.rc的解析過程:函數定義於system/core/init/ init_parser.cpp中
複製代碼
bool Parser:: ParseConfig ( const std:: string & path) {
     if (is_dir(path.c_str())) {           //判斷傳入參數是否為目錄地址
          return ParseConfigDir(path);      //遞歸目錄,最終還是靠ParseConfigFile來解析實際的文件 
    }
    return ParseConfigFile(path);         //傳入傳輸為文件地址 
} 
複製代碼
  繼續分析ParseConfigFile():
複製代碼
bool Parser:: ParseConfigFile ( const std:: string & path) {
    ... ...
    Timer t;
    std:: string data;
     if (!read_file(path.c_str(), &data)) {        // 讀取路徑指定文件中的內容,保存為字符串形式
        return  false ;
}
... ...
    ParseData(path, data);         // 解析獲取的字符串
    ... ...
}
複製代碼
  跟踪ParseData():
複製代碼
void Parser:: ParseData ( const std:: string & filename, const std:: string & data) {
    ... ...
    parse_state state;
    ... ...
    std::vector string > args;

    for (;;) {
         switch (next_token(&state)) {     // next_token以行為單位分割參數傳遞過來的字符串,最先走到T_TEXT分支
        case T_EOF:
             if (section_parser) {
                section_parser ->EndSection();     // 解析結束
            }
             return ;
         case T_NEWLINE:
            state.line ++ ;
             if (args.empty()) {
                 break ;
            }
            // 在前文創建parser時,我們為service,on,import定義了對應的parser 
             // 這裡就是根據第一個參數,判斷是否有對應的parser 
            if (section_parsers_.count(args[ 0 ])) {
                 if (section_parser) {
                     // 結束上一個parser的工作,將構造出的對象加入到對應的service_list與action_list中 
                    section_parser-> EndSection();
                }
                // 獲取參數對應的parser 
                section_parser = section_parsers_[args[ 0 ]]. get ();
                std:: string ret_err;
                 // 調用實際parser的ParseSection函數
                if (!section_parser->ParseSection(args, & ret_err)) {
                    parse_error( &state, " %s\n " , ret_err.c_str());
                    section_parser = nullptr;
                }
            } else  if (section_parser) {
                std:: string ret_err;
                 // 如果第一個參數不是service,on,import
                 // 則調用前一個parser的ParseLineSection函數
                 // 這里相當於解析一個參數塊的子項
                if (!section_parser-> ParseLineSection(args , state.filename,
                                                             state.line, & ret_err)) {
                    parse_error( &state, " %s\n " , ret_err.c_str());
                }
            }
            args.clear();        // 清空本次解析的數據
            break ;
         case T_TEXT:
            args.emplace_back(state.text);      // 將本次解析的內容寫入到args中
            break ;
        }
    }
}
複製代碼
  至此,init.rc解析完,接下來init會執行幾個重要的階段:
複製代碼
int main( int argc, char ** argv) {
    /* 01. 創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/•
    /* 05. 重新設置屬性*/
    /* 06. 創建epoll句柄*/
    /* 07. 裝載子進程信號處理器*/
    /* 08. 設置默認系統屬性*/
    /* 09. 啟動配置屬性的服務端*/
    /* 10.匹配命令和函數之間的對應關係*/ 
    /* 11.解析init.rc*/ 
------------------------- -------------------------------------------------- - 
  /* 12.向執行隊列中添加其他action */
    // 獲取ActionManager對象,需要通過am對命令執行順序進行控制 
    ActionManager& am = ActionManager::GetInstance();
     // init執行命令觸發器主要分為early-init,init,late-init,boot等 
    am.QueueEventTrigger ( " early-init " );     // 添加觸發器early-init,執行on early-init內容

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... 
    am.QueueBuiltinAction(wait_for_coldboot_done_action, " wait_for_coldboot_done " );
     // ... so that we can start queuing up actions that require stuff from /dev. 
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, " mix_hwrng_into_linux_rng " );
    am.QueueBuiltinAction(keychord_init_action, " keychord_init " );
    am.QueueBuiltinAction(console_init_action, " console_init " );

    // Trigger all the boot actions to get us started. 
    am.QueueEventTrigger( " init " );         // 添加觸發器init,執行on init內容,主要包括創建/掛在一些目錄,以及symlink等

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
     // wasn't ready immediately after wait_for_coldboot_done 
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, " mix_hwrng_into_linux_rng " );

    // Don't mount filesystems or start core system services in charger mode. 
    if (bootmode == " charger " ) {
    am.QueueEventTrigger( " charger " );     // on charger階段 
    } else  if (strncmp(bootmode.c_str(), " ffbm " , 4 ) == 0 ) {
    NOTICE( " Booting into ffbm mode\n " );
    am.QueueEventTrigger( " ffbm " );
    } else {
    am.QueueEventTrigger( " late-init " );           // 非充電模式添加觸發器last-init 
    }

    // Run all property triggers based on current state of the properties. 
    am.QueueBuiltinAction(queue_property_triggers_action, " queue_property_triggers " );
複製代碼
  在last-init最後階段有如下代碼:
複製代碼
# Mount filesystems and start core system services.
 on late - init 
    trigger early - fs

    # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter
    # ' --early ' can be specified to skip entries with ' latemount ' .
    # /system and / vendor must be mounted by the end of the fs stage,
    # while /data is optional.
    trigger fs
    trigger post - fs

    # Load properties from /system/ + / factory after fs mount. Place
    # this  in another action so that the load will be scheduled after the prior
    # issued fs triggers have completed.
    trigger load_system_props_action

    # Mount fstab in init.{$device}.rc by mount_all with ' --late ' parameter
    # to only mount entries with ' latemount ' . This is needed if  ' --early '  is 
    # specified in the previous mount_all command on the fs stage.
    # With /system mounted and properties form /system + / factory available,
    # some services can be started.
    trigger late - fs

    # Now we can mount / data. File encryption requires keymaster to decrypt
    # /data, which in turn can only be loaded when system properties are present.
    trigger post -fs- data

    # Load persist properties and override properties ( if enabled) from / data.
    trigger load_persist_props_action

    # Remove a file to wake up anything waiting for firmware.
    trigger firmware_mounts_complete

    trigger early - boot
    trigger boot
複製代碼
  可見出發了on early-boot和on boot兩個Action。
  我們看一下on boot:
複製代碼
on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    ... ... class_start core
    
複製代碼
  在on boot 的最後class_start core 會啟動class為core的服務,這些服務包括ueventd、logd、healthd、adbd(disabled)、lmkd(LowMemoryKiller)、servicemanager、vold、debuggerd、surfaceflinger、bootanim(disabled)等。
  回到主題,分析trigger觸發器的代碼,QueueEventTrigger():位於system/core/init/action.cpp
void ActionManager:: QueueEventTrigger ( const std:: string & trigger) {
    trigger_queue_.push(std::make_unique  (trigger));
}
  此處QueueEventTrigger函數就是利用參數構造EventTrigger,然後加入到trigger_queue_中。後續init進程處理trigger事件時,將會觸發相應的操作。
  再看一下QueueBuiltinAction()函數:同樣位於system/core/init/action.cpp
複製代碼
void ActionManager::QueueBuiltinAction(BuiltinFunction func,
                                    const std:: string & name) {
     // 創建action 
    auto action = std::make_unique( true );
    std::vector string > name_vector{name};

    // 保證唯一性
    if (!action-> InitSingleTrigger(name)) {
         return ;
    }

    // 創建action的cmd,指定執行函數和參數 
    action-> AddCommand(func, name_vector);

    trigger_queue_.push(std::make_unique (action. get ()));
    actions_.emplace_back(std::move(action));
}
複製代碼
  QueueBuiltinAction函數中構造新的action加入到actions_中,第一個參數作為新建action攜帶cmd的執行函數;第二個參數既作為action的trigger name,也作為action攜帶cmd的參數。
  接下來繼續分析main函數:
複製代碼
int main( int argc, char ** argv) {
    /* 01. 創建文件系統目錄並掛載相關的文件系統*/
    /* 02. 屏蔽標準的輸入輸出/初始化內核log系統*/
    /* 03. 初始化屬性域*/
    /* 04. 完成SELinux相關工作*/•
    /* 05. 重新設置屬性*/
    /* 06. 創建epoll句柄*/
    /* 07. 裝載子進程信號處理器*/
    /* 08. 設置默認系統屬性*/
    /* 09. 啟動配置屬性的服務端*/
    /* 10.匹配命令和函數之間的對應關係*/ 
    /* 11.解析init.rc*/ 
    /* 12.向執行隊列中添加其他action */ 
------------ -------------------------------------------------- ----- 
    /* 13.處理添加到運行隊列的事件*/     while ( true ) {
    // 判斷是否有事件需要處理
        if (! waiting_for_exec) {
             // 依次執行每個action中攜帶command對應的執行函數
am. ExecuteOneCommand ();
         // 重啟一些掛掉的進程            restart_processes();     

        }

        // 以下決定timeout的時間,將影響while循環的間隔
        int timeout = - 1 ;
         // 有進程需要重啟時,等待該進程重啟
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000 ;
             if (timeout < 0 )
                timeout = 0 ;
        }

        // 有action待處理,不等待
        if (am.HasMoreCommands()) {
            timeout = 0 ;
        }

        // bootchart_sample應該是進行性能數據採樣 
        bootchart_sample(& timeout);

        epoll_event ev;
        // 沒有事件到來的話,最多阻塞timeout時間
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1 , timeout));
         if (nr == - 1 ) {
            ERROR( " epoll_wait failed: %s\n " , strerror(errno));
        } else  if (nr == 1 ) {
             // 有事件到來,執行對應處理函數
             // 根據上文知道,epoll句柄(即epoll_fd)主要監聽子進程結束,及其它進程設置系統屬性的請求 
            (( void (* )()) ev.data.ptr)();
        }
    }
    return  0 ;
} // end main
複製代碼
  看一下ExecuteOneComand()函數:同樣位於system/core/init/action.cpp
複製代碼
void ActionManager:: ExecuteOneCommand () {
     // Loop through the trigger queue until we have an action to execute
     // 當前的可執行action隊列為空, trigger_queue_隊列不為空
    while (current_executing_actions_.empty() && ! trigger_queue_. empty()) {
     // 循環遍歷action_隊列,包含了所有需要執行的命令,解析init.rc獲得
        for ( const auto& action : actions_) {
             // 獲取隊頭的trigger,檢查actions_列表中的action的trigger,對比是否相同
            if (trigger_queue_.front()->CheckTriggers(* action)) {
                 // 將所有具有同一trigger的action加入當前可執行action隊列
                current_executing_actions_.emplace(action. get ());
            }
        }
        // 將隊頭trigger出棧
        trigger_queue_.pop();
    }

    if (current_executing_actions_.empty()) {    // 當前可執行的actions隊列為空就返回
        return ;
    }

    auto action = current_executing_actions_.front(); // 獲取當前可執行actions隊列的首個action

    if (current_command_ == 0 ) {
        std:: string trigger_name = action-> BuildTriggersString();
        INFO( " processing action (%s)\n " , trigger_name.c_str());
    }

    action ->ExecuteOneCommand(current_command_);      // 執行當前的命令

    // If this was the last command in the current action, then remove
     // the action from the executing list.
     // If this action was oneshot, then also remove it from actions_. 
    ++current_command_;       // 不斷疊加,將action _中的所有命令取出
    if (current_command_ == action-> NumCommands()) {
        current_executing_actions_.pop();
        current_command_ = 0 ;
         if (action-> oneshot()) {
            auto eraser = [&action] (std::unique_ptr& a) {
                 return a. get () == action;
            };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
        }
    }
}
複製代碼
  我們來觀察一下init.rc的開頭部分:
import / init.environ.rc
import / init.usb.rc
import / init.${ro.hardware}.rc
import / init.usb.configfs.rc
 import /init.${ro.zygote}.rc       //後面我們即將重點分析zygote進程
  通過ro.zygote的屬性import對應的zygote的rc文件。
  
  我們查看init.zygote64_32.rc:
複製代碼
service  zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name= zygote
     class main 
    socket zygote stream 660 root system
    onrestart write /sys/android_power/ request_state wake
    onrestart write /sys/power/ state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/ tasks

service  zygote_secondary/system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
    class main 
    socket zygote_secondary stream660root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks
複製代碼
  可以看到zygote的class是main,它是在on nonencrypted時被啟動的,如下:
複製代碼
on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    ... ...
    class_start core

on nonencrypted 
    # A / B update verifier that marks a successful boot.
    exec - root cache -- /system/bin/ update_verifier nonencrypted
    class_start main 
    class_start late_start
複製代碼
  至此,Init.cpp的main函數分析完畢!init進程已經啟動完成,一些重要的服務如core服務和main服務也都啟動起來,並啟動了zygote(/system/bin/app_process64)進程,zygote初始化時會創建虛擬機,啟動systemserver等。

沒有留言: