Linux 孵化Android的開端-init進程

版權聲明:本文參考@gityuan 袁神相關文章,轉載請注明出處。
注:限于作者水平有限,文中有不對的地方還請指教。

本文基于Android 6.0,涉及源碼如下:

/system/core/rootdir/init.rc
/system/core/rootdir/init.zygote32.rc
/system/core/init/init.cpp
/system/core/init/init_parser.cpp //解析各個服務都是靠它完成
/system/core/init/signal_handler.cpp

概述

Init 進程屬于Linux系統中用戶空間第一個進程,進程號(PID)為1;Linux Kernel啟動后,就會在用戶空間啟動Init 進程,對于Init 進程,使用C++ 語言實現,所以該進程的入口函數是main()方法,在Android源碼中源碼位于:

/system/core/init/init.cpp

作為一個系統工程師,或多或少都知道Init.rc文件,我們可以通過該文件配置一些native 的守護進程,比如:servicemanager,installd,adbd,ueventd 等等;為何我們在該文件中的配置會在系統中有效,這就涉及到系統對于init.rc腳本的解析和執行;init.rc腳本的解析執行就是上述Init進程實現;Init 進程有如下幾個功能:

    1. 解析并運行所有init.rc腳本。
    2. 生成設備驅動節點。
    3. 處理子進程終結(Signal方式)。
    4. 提供屬性服務。

接下來從代碼的角度來分析Init進程是具體做到上述功能的。
由于代碼太長,將會提取部分關鍵代碼并添加注釋:

一:main 函數

1.1:init.cpp main函數

int main(int argc, char** argv) {
    ......
    add_environment("PATH", _PATH_DEFPATH); //添加path 環境變量
    open_devnull_stdio();
    klog_init();     //初始化Kernel Log
    property_init(); //創建一塊共享的內存空間,用于屬性服務
    selinux_initialize(is_first_stage); //初始化 selinux
    signal_handler_init();//初始化子進程終止處理
    property_load_boot_defaults(); //加載/default.prop文件
    start_property_service();   //啟動屬性服務器(通過socket通信)
    init_parse_config_file("/init.rc"); //解析init.rc文件

    action_for_each_trigger("early-init", action_add_queue_tail);//執行init.rc中觸發器為on early-init的語句
    queue_builtin_action(console_init_action, "console_init");//屏幕上顯示Android靜態Logo 見1.2
    action_for_each_trigger("init", action_add_queue_tail);
    action_for_each_trigger("late-init", action_add_queue_tail);
 
    while (true) {
        if (!waiting_for_exec) {
            execute_one_command();
            restart_processes();//*啟動有需要的進程
        }
        ......
        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:console_init_action

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

static int console_init_action(int nargs, char **args)
{
    int fd;
    if (console[0]) {
        snprintf(console_name, sizeof(console_name), "/dev/%s", console);
    }
    fd = open(console_name, O_RDWR);
    if (fd >= 0)
        have_console = 1;
    close(fd);
    fd = open("/dev/tty0", O_WRONLY);
    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 ";//這就是Android 原聲底部顯示的字樣,靜態顯示
        write(fd, msg, strlen(msg));
        close(fd);
    }
    return 0;
}

二:信號處理

 /system/core/init/signal_handler.cpp //第二節的所有內容都是在該文件

2.1:signal_handler_init

在init進程main 方法中,通過調用signal_handler_init 函數來初始化信號處理過程;signal_handler_init定義于:

void signal_handler_init() {
    // Create a signalling mechanism for SIGCHLD.
    int s[2];
    //sockerpair:調用一對已經存在的socke(socketpair是一個SysCall 命令)
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        ERROR("socketpair failed: %s\n", strerror(errno));
        exit(1);
    }
    signal_write_fd = s[0];
    signal_read_fd = s[1];
    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_handler = SIGCHLD_handler;//設置信號處理函數句柄,即當有數據時,會調用該方法,向上面的  
    //socket 的fd 中寫入數據,后面epoll 監控到socket 中的fd可讀時,就會調用注冊的函數去處理
    act.sa_flags = SA_NOCLDSTOP;//該標志表示init進程只有在子進程終止時才會收到SIGHCLD信號
    sigaction(SIGCHLD, &act, 0);
    reap_any_outstanding_children();
    register_epoll_handler(signal_read_fd, handle_signal); //初始化EPOLL,此處會調用handle_signal方法
}

每個進程在處理其他進程發送的signal信號的時候都會先去注冊,當進程的運行狀態或者進程死亡的時候會產生某種signal信號,Init進程是所有用戶空間進程的父進程,當子進程終止的時候,會產生SIGCHLD信號,sigaction方法將SIGCHLD信號傳遞給sigaction 結構體;到此信號的處理過程就會結束。
對于具體的信號處理函數可以查看SIGCHLD_handler 實現,實質就是向上面的Socket 寫數據;
當socket 的fd 中有數據后,epoll會調用注冊的handle_signal來處理,具體可以參考該方法的實現;

static void handle_signal() {  
    // Clear outstanding requests.  
    char buf[32];  
    read(signal_read_fd, buf, sizeof(buf));  
  
    reap_any_outstanding_children();  
}  

handle_signal方法大概就是將socket fd中的數據讀入到buf 中,然后調用 reap_any_outstanding_children來具體處理服務的退出以及重啟工作;在該方法中,就是根據我們在init.rc腳本中的是否有定義各種標志來確定當服務掛掉后是否要重啟,以及清理一些數據;reap_any_outstanding_children 具體會會調用wait_for_one_process方法來處理;

2.2:reap_any_outstanding_children()::wait_for_one_process()

static bool wait_for_one_process() {
    int status;
    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);

    std::string name;
    if (svc) {
        name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
    } else {
        name = android::base::StringPrintf("Untracked pid %d", pid);
    }

    NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());

    if (!svc) {
        return true;
    }

    // TODO: all the code from here down should be a member function on service.

    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
        kill(-pid, SIGKILL);
    }
    //移除當前服務svc中的所有創建過的socket
    // Remove any sockets we may have created.
    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) {
        INFO("SVC_EXEC pid %d finished...\n", svc->pid);
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }

    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    //對于ONESHOT服務,使其進入disabled狀態
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }
    //禁用和重置的服務,都不再自動重啟
    // Disabled and reset processes do not get restarted automatically.
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        svc->NotifyStateChange("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) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                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;

    // Execute all onrestart commands for this service.
    struct listnode* node;
  //執行當前service中所有onrestart命令
    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;
}

該方法會根據init.rc腳本中各個服務的各種標志來進行相關操作;

2.3:register_epoll_handler 注冊EPOLL

該方法在signal_handler.cpp中調用,但是在init.cpp 中定義。

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

綜上:init進程對子進程退出處理過程:當init子進程退出時,會產生SIGCHLD信號,并發送給init進程,通過socket套接字傳遞數據,調用到wait_for_one_process()方法,wait_for_one_process根據服務是否有oneshot,onrestart 等標識來進行后續的處理。具體的處理可以順著該方法繼續跟進。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容