1. 程式人生 > >Android系統啟動-Init篇

Android系統啟動-Init篇

基於Android 6.0的原始碼剖析, 分析Android啟動過程程序號為1的init程序的工作內容

/system/core/init/
  - init.cpp
  - init_parser.cpp
  - signal_handler.cpp

一、概述

init是Linux系統中使用者空間的第一個程序,程序號為1。Kernel啟動後,在使用者空間,啟動init程序,並呼叫init中的main()方法執行init程序的職責。對於init程序的功能分為4部分:

  • 分析和執行所有的init.rc檔案;
  • 生成裝置驅動節點; (通過rc檔案建立)
  • 處理子程序的終止(signal方式);
  • 提供屬性服務。

接下來從main()方法說起。

1.1 main

[-> init.cpp]

int main(int argc, char** argv) {
    ...
    umask(0); //設定檔案屬性0777
    
    klog_init();  //初始化kernel log,位於裝置節點/dev/kmsg【見小節1.2】
    klog_set_level(KLOG_NOTICE_LEVEL); //設定輸出的log級別
    // 輸出init啟動階段的log
    NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
    
    property_init(); //建立一塊共享的記憶體空間,用於屬性服務
    signal_handler_init();  //初始化子程序退出的訊號處理過程【見小節2.1】

    property_load_boot_defaults(); //載入default.prop檔案
    start_property_service();   //啟動屬性伺服器(通過socket通訊)【5.1】
    init_parse_config_file("/init.rc"); //解析init.rc檔案

    //執行rc檔案中觸發器為 on early-init的語句
    action_for_each_trigger("early-init", action_add_queue_tail);
    //等冷插拔裝置初始化完成
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    //裝置組合鍵的初始化操作
    queue_builtin_action(keychord_init_action, "keychord_init");
    // 螢幕上顯示Android靜態Logo 【見小節1.3】
    queue_builtin_action(console_init_action, "console_init");
    
    //執行rc檔案中觸發器為 on init的語句
    action_for_each_trigger("init", action_add_queue_tail);
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    
    char bootmode[PROP_VALUE_MAX];
    //當處於充電模式,則charger加入執行佇列;否則late-init加入佇列。
    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
       action_for_each_trigger("charger", action_add_queue_tail);
    } else {
       action_for_each_trigger("late-init", action_add_queue_tail);
    }
    //觸發器為屬性是否設定
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
     
    while (true) {
        if (!waiting_for_exec) {
            execute_one_command();
            restart_processes(); //【見小節1.4】
        }
        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }
        if (!action_queue_empty() || cur_action) {
            timeout = 0;
        }

        epoll_event ev;
        //迴圈 等待事件發生
        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) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

1.2 log系統

此時android的log系統還沒有啟動,採用kernel的log系統,開啟的裝置節點/dev/kmsg, 那麼可通過cat /dev/kmsg來獲取核心log。

接下來,設定log的輸出級別為KLOG_NOTICE_LEVEL(5),當log級別小於5時則會輸出到kernel log, 預設值為3.

#define KLOG_ERROR_LEVEL   3
#define KLOG_WARNING_LEVEL 4
#define KLOG_NOTICE_LEVEL  5
#define KLOG_INFO_LEVEL    6
#define KLOG_DEBUG_LEVEL   7
#define KLOG_DEFAULT_LEVEL  3 //預設為3

1.3 console_init_action

[-> init.cpp]

static int console_init_action(int nargs, char **args)
{
    char console[PROP_VALUE_MAX];
    if (property_get("ro.boot.console", console) > 0) {
        snprintf(console_name, sizeof(console_name), "/dev/%s", console);
    }

    int fd = open(console_name, O_RDWR | O_CLOEXEC);
    if (fd >= 0)
        have_console = 1;
    close(fd);

    fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC);
    if (fd >= 0) {
        const char *msg;
            msg = "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"  // console is 40 cols x 30 lines
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "\n"
        "             A N D R O I D ";
        write(fd, msg, strlen(msg));
        close(fd);
    }

    return 0;
}

這便是開機顯示的底部帶ANDROID字樣的畫面。

1.4 restart_processes

[-> init.cpp]

static void restart_processes()
{
    process_needs_restart = 0;
    service_for_each_flags(SVC_RESTARTING,
                           restart_service_if_needed);
}

檢查service_list中的所有服務,對於帶有SVC_RESTARTING標誌的服務,則都會呼叫其相應的restart_service_if_needed。

static void restart_service_if_needed(struct service *svc)
{
    time_t next_start_time = svc->time_started + 5;

    if (next_start_time <= gettime()) {
        svc->flags &= (~SVC_RESTARTING);
        service_start(svc, NULL); 
        return;
    }

    if ((next_start_time < process_needs_restart) ||
        (process_needs_restart == 0)) {
        process_needs_restart = next_start_time;
    }
}

之後再呼叫service_start來啟動服務。

二、訊號處理

在init.cpp的main()方法中,通過signal_handler_init()來初始化訊號處理過程。

主要工作:

  • 初始化signal控制代碼;
  • 迴圈處理子程序;
  • 註冊epoll控制代碼;
  • 處理子程序的終止;

2.1 signal_handler_init

[-> signal_handler.cpp]

void signal_handler_init() {
    int s[2];
    // 建立socket pair
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        exit(1);
    }
    signal_write_fd = s[0];
    signal_read_fd = s[1];
    //當捕獲訊號SIGCHLD,則寫入signal_write_fd
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler;
    //SA_NOCLDSTOP使init程序只有在其子程序終止時才會受到SIGCHLD訊號
    act.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGCHLD, &act, 0);
    reap_any_outstanding_children(); 【見小節2.2】
    register_epoll_handler(signal_read_fd, handle_signal);  //【見小節2.3】
}

每個程序在處理其他程序傳送的signal訊號時都需要先註冊,當程序的執行狀態改變或終止時會產生某種signal訊號,init程序是所有使用者空間程序的父程序,當其子程序終止時產生SIGCHLD訊號,init程序呼叫訊號安裝函式sigaction(),傳遞引數給sigaction結構體,便完成訊號處理的過程。

這裡有兩個重要的函式:SIGCHLD_handler和handle_signal,如下:

//寫入資料
static void SIGCHLD_handler(int) {
    //向signal_write_fd寫入1,直到成功為止
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
    }
}

//讀取資料
static void handle_signal() {
    char buf[32];
    //讀取signal_read_fd資料,放入buf
    read(signal_read_fd, buf, sizeof(buf));
    reap_any_outstanding_children(); 【見流程3-1】
}

2.2 reap_any_outstanding_children

[-> signal_handler.cpp]

static void reap_any_outstanding_children() {
    while (wait_for_one_process()) { }
}

static bool wait_for_one_process() {
    int status;
    //等待任意子程序,如果子程序沒有退出則返回0,否則則返回該子程序pid。
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        ERROR("waitpid failed: %s\n", strerror(errno));
        return false;
    }
    service* svc = service_find_by_pid(pid); //根據pid查詢到相應的service
    std::string name;

    if (!svc) {
        return true;
    }

    //當flags為RESTART,且不是ONESHOT時,先kill程序組內所有的子程序或子執行緒
    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        kill(-pid, SIGKILL);
    }

    //移除當前服務svc中的所有建立過的socket
    for (socketinfo* si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    //當flags為EXEC時,釋放相應的服務
    if (svc->flags & SVC_EXEC) {
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }
    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    //對於ONESHOT服務,使其進入disabled狀態
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }
    //禁用和重置的服務,都不再自動重啟
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        svc->NotifyStateChange("stopped"); //設定相應的service狀態為stopped
        return true;
    }

    //服務在4分鐘內重啟次數超過4次,則重啟手機進入recovery模式
    time_t now = gettime();
    if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
                return true;
            }
        } else {
            svc->time_crashed = now;
            svc->nr_crashed = 1;
        }
    }
    svc->flags &= (~SVC_RESTART);
    svc->flags |= SVC_RESTARTING;

    //執行當前service中所有onrestart命令
    struct listnode* node;
    list_for_each(node, &svc->onrestart.commands) {
        command* cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }
    //設定相應的service狀態為restarting
    svc->NotifyStateChange("restarting");
    return true;
}

另外:通過getprop | grep init.svc 可檢視所有的service執行狀態。狀態總共分為:running, stopped, restarting

2.3 register_epoll_handler

[-> signal_handler.cpp]

void register_epoll_handler(int fd, void (*fn)()) {
    epoll_event ev;
    ev.events = EPOLLIN; //可讀
    ev.data.ptr = reinterpret_cast<void*>(fn);
    //將fd的可讀事件加入到epoll_fd的監聽佇列中
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        ERROR("epoll_ctl failed: %s\n", strerror(errno));
    }
}

三、rc檔案語法

rc檔案語法是以行尾單位,以空格間隔的語法,以#開始代表註釋行。rc檔案主要包含Action、Service、Command、Options,其中對於Action和Service的名稱都是唯一的,對於重複的命名視為無效。

3.1 Action

Action: 通過trigger,即以 on開頭的語句,決定何時執行相應的service。

  • on early-init; 在初始化早期階段觸發;
  • on init; 在初始化階段觸發;
  • on late-init; 在初始化晚期階段觸發;
  • on boot/charger: 當系統啟動/充電時觸發,還包含其他情況,此處不一一列舉;
  • on property:<key>=<value>: 當屬性值滿足條件時觸發;

3.2 Service

服務Service,以 service開頭,由init程序啟動,一般運行於另外一個init的子程序,所以啟動service前需要判斷對應的可執行檔案是否存在。init生成的子程序,定義在rc檔案,其中每一個service,在啟動時會通過fork方式生成子程序。

例如: service servicemanager /system/bin/servicemanager代表的是服務名為servicemanager,服務的路徑,也就是服務執行操作時執行/system/bin/servicemanager。

3.3 Command

下面列舉常用的命令

  • class_start <service_class_name>: 啟動屬於同一個class的所有服務;
  • start <service_name>: 啟動指定的服務,若已啟動則跳過;
  • stop <service_name>: 停止正在執行的服務
  • setprop <name> <value>:設定屬性值
  • mkdir <path>:建立指定目錄
  • symlink <target> <sym_link>: 建立連線到<target>的<sym_link>符號連結;
  • write <path> <string>: 向檔案path中寫入字串;
  • exec: fork並執行,會阻塞init程序直到程式完畢;
  • exprot <name> <name>:設定環境變數;
  • loglevel <level>:設定log級別

3.4 Options

Options是Services的可選項,與service配合使用

  • disabled: 不隨class自動啟動,只有根據service名才啟動;
  • oneshot: service退出後不再重啟;
  • user/group: 設定執行服務的使用者/使用者組,預設都是root;
  • class:設定所屬的類名,當所屬類啟動/退出時,服務也啟動/停止,預設為default;
  • onrestart:當服務重啟時執行相應命令;
  • socket: 建立名為/dev/socket/<name>的socket
  • critical: 在規定時間內該service不斷重啟,則系統會重啟並進入恢復模式

default: 意味著disabled=false,oneshot=false,critical=false。

四、啟動服務

4.1 啟動順序

on early-init
on init
on late-init //掛載檔案系統,啟動核心服務
    trigger post-fs
    trigger load_system_props_action 
    trigger post-fs-data //掛載data
    trigger load_persist_props_action
    trigger firmware_mounts_complete
    trigger boot
    
on post-fs
    start logd
    mount rootfs rootfs / ro remount
    mount rootfs rootfs / shared rec 
    mount none /mnt/runtime/default /storage slave bind rec
    ...
     
on post-fs-data
    start logd
    start vold //啟動vold
    ...
    
on boot
    ...
    class_start core //啟動core class

由on early-init -> init -> late-init -> boot。

先啟動core class, 至於main class的啟動是由vold.decrypt的以下4個值的設定所決定的, 該過程位於system/vold/cryptfs.c檔案。

on nonencrypted
    class_start main
    class_start late_start
    
on property:vold.decrypt=trigger_restart_min_framework
    class_start main

on property:vold.decrypt=trigger_restart_framework
    class_start main
    class_start late_start

on property:vold.decrypt=trigger_reset_main
    class_reset main
        
on property:vold.decrypt=trigger_shutdown_framework
    class_reset late_start
    class_reset main

4.2 服務啟動(Zygote)

在init.zygote.rc檔案中,zygote服務定義如下:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

通過init_parser.cpp完成整個service解析工作,此處就不詳細展開講解析過程,該過程主要是建立一個名”zygote”的service結構體,一個socketinfo結構體(用於socket通訊),以及一個包含4個onrestart的action結構體。

Zygote服務會隨著main class的啟動而啟動,退出後會由init重啟zygote,即使多次重啟也不會進入recovery模式。zygote所對應的可執行檔案是/system/bin/app_process,通過呼叫pid =fork()建立子程序,通過execve(svc->args[0], (char**)svc->args, (char**) ENV),進入App_main.cpp的main()函式。故zygote是通過fork和execv共同建立的。

流程如下:

zygote_init

而關於Zygote重啟在前面的訊號處理過程中講過,是處理SIGCHLD訊號,init程序重啟zygote程序,更多關於Zygote內容見Zygote篇

4.3 服務重啟

init_oneshot

當init子程序退出時,會產生SIGCHLD訊號,併發送給init程序,通過socket套接字傳遞資料,呼叫到wait_for_one_process()方法,根據是否是oneshot,來決定是重啟子程序,還是放棄啟動。

所有的Service裡面只有servicemanager ,zygote ,surfaceflinger這3個服務有onrestart關鍵字來觸發其他service啟動過程。

//zygote可觸發media、netd重啟
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

//servicemanager可觸發healthd、zygote、media、surfaceflinger、drm重啟
service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

//surfaceflinger可觸發zygote重啟
service surfaceflinger /system/bin/surfaceflinger
    class core
    user system
    group graphics drmrpc
    onrestart restart zygote

由上可知:

  • zygote:觸發media、netd以及子程序(包括system_server程序)重啟;
  • system_server: 觸發zygote重啟;
  • surfaceflinger:觸發zygote重啟;
  • servicemanager: 觸發zygote、healthd、media、surfaceflinger、drm重啟

所以,surfaceflinger,servicemanager,zygote自身以及system_server程序被殺都會觸發Zygote重啟。

五、屬性服務

當某個程序A,通過property_set()修改屬性值後,init程序會檢查訪問許可權,當權限滿足要求後,則更改相應的屬性值,屬性值一旦改變則會觸發相應的觸發器(即rc檔案中的on開頭的語句),在Android Shared Memmory(共享記憶體區域)中有一個_system_property_area_區域,裡面記錄著所有的屬性值。對於程序A通過property_get()方法,獲取的也是該共享記憶體區域的屬性值。

5.1 初始化

[-> property_service.cpp]

void property_init() {
    //用於保證只初始化_system_property_area_區域一次
    if (property_area_initialized) {
        return;
    }
    property_area_initialized = true;
    if (__system_property_area_init()) {
        return;
    }
    pa_workspace.size = 0;
    pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (pa_workspace.fd == -1) {
        ERROR("Failed to open %s: %s\n", PROP_FILENAME, strerror(errno));
        return;
    }
}

在properyty_init函式中,先呼叫init_property_area函式,建立一塊用於儲存屬性的共享記憶體,而共享記憶體是可以跨程序的。

關於載入的prop檔案

通過load_all_load_all_propsprops()方法,載入以下:

  1. /system/build.prop;
  2. /vendor/build.prop;
  3. /factory/factory.prop;
  4. /data/local.prop;
  5. /data/property路徑下的persist屬性

5.2 屬性類別

  1. 屬性名以ctl.開頭,則表示是控制訊息,控制訊息用來執行一些命令。例如:
    • setprop ctl.start bootanim 檢視開機動畫;
    • setprop ctl.stop bootanim 關閉開機動畫;
    • setprop ctl.start pre-recovery 進入recovery模式。
  2. 屬性名以ro.開頭,則表示是隻讀的,不能設定,所以直接返回。
  3. 屬性名以persist.開頭,則需要把這些值寫到對應檔案中去。

以上內容轉自:gityuan的Android系統啟動-Init篇

 -----------------------------------------------------------分割線-------------------------------------------------------------------------------

以下內容為個人小結:

serviceManager、surfaceflinger、zygote本身都會導致zygote的重啟,而zygote重啟會導致system_server重啟,而system_server的重啟,system_server在重啟前肯定會先把屬於它管理的應用程序都殺死。service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server這個service宣告就表明zygote的引數是--start-system-server,所以會去重啟system_server