Android系統(tǒng)啟動(一)init進程啟動過程

1. init簡介

init進程是Android系統(tǒng)中用戶空間的第一個進程,進程號為1瓷式,是Android系統(tǒng)啟動流程中一個關鍵的步驟轧抗,作為第一個進程,它被賦予了很多極其重要的工作職責渡紫,比如創(chuàng)建Zygote(孵化器)和屬性服務等到推。init進程是由多個源文件共同組成的,這些文件位于源碼目錄system/core/init中惕澎。

2. 引入init進程

為了講解init進程莉测,首先要了解Android系統(tǒng)啟動流程的前幾步,以引入init進程唧喉。

1. 啟動電源以及系統(tǒng)啟動

當電源按下時引導芯片代碼開始從預定義的地方(固化在ROM)開始執(zhí)行捣卤。加載引導程序Bootloader到RAM,然后執(zhí)行八孝。

2. 引導程序Bootloader

引導程序是在Android操作系統(tǒng)開始運行前的一個小程序董朝,它的主要作用是把系統(tǒng)OS拉起來并運行。

3. linux內(nèi)核啟動

內(nèi)核啟動時干跛,設置緩存子姜、被保護存儲器、計劃列表楼入,加載驅(qū)動哥捕。當內(nèi)核完成系統(tǒng)設置,它首先在系統(tǒng)文件中尋找”init.rc”文件嘉熊,并啟動init進程遥赚。

4. init進程啟動

init進程做的工作比較多,主要用來初始化和啟動屬性服務记舆,也用來啟動Zygote進程。

3. init進程的入口函數(shù)

在Linux內(nèi)核加載完成后呼巴,它首先在系統(tǒng)文件中尋找init.rc文件泽腮,并啟動init進程,然后查看init進程的入口函數(shù)main衣赶,代碼如下所示:
system/core/init/init.cpp

int main(int argc, char** argv) {
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        install_reboot_signal_handlers();
    }

    add_environment("PATH", _PATH_DEFPATH);

    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
        boot_clock::time_point start_time = boot_clock::now();

        // 清理 umask
        umask(0);

        // 創(chuàng)建和掛載啟動所需的文件目錄
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // 不要將原始命令行暴露給非特權(quán)進程
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        // 初始化 Kernel的Log诊赊,這樣就可以從外界獲取Kernel的日志
        InitKernelLogging(argv);
        ···
}
···
// 對屬性服務進行初始化
property_init();  //1
···
// 創(chuàng)建epoll句柄
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        PLOG(ERROR) << "epoll_create1 failed";
        exit(1);
    }
    // 用于設置子進程信號處理函數(shù),如果子進程(Zygote)異常退出府瞄,init進程會調(diào)用該函數(shù)中設定的信號處理函數(shù)來進行處理
    signal_handler_init();  //2
    // 導入默認的環(huán)境變量
    property_load_boot_defaults();
    export_oem_lock_status();
    // 啟動屬性服務
    start_property_service();  //3
    set_usb_controller();

    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);

    Parser& parser = Parser::GetInstance();
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());
    parser.AddSectionParser("on", std::make_unique<ActionParser>());
    parser.AddSectionParser("import", std::make_unique<ImportParser>());
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        //解析init.rc配置文件
        parser.ParseConfig("/init.rc");  //4
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }

    if (false) parser.DumpState();

    ActionManager& am = ActionManager::GetInstance();
···
while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1;

        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            // 內(nèi)部遍歷執(zhí)行每個action中攜帶的command對應的執(zhí)行函數(shù)
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            //重啟死去的進程
            restart_processes();  //5

            if (process_needs_restart_at != 0) {
                epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
                if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
            }

            if (am.HasMoreCommands()) epoll_timeout_ms = 0;
        }

        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
        if (nr == -1) {
            PLOG(ERROR) << "epoll_wait failed";
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }

    return 0;
}

init的main函數(shù)做了很多事情碧磅,比較復雜,我們只需關注主要的幾點就可以了遵馆。在開始的時候創(chuàng)建和掛載啟動所需的文件目錄鲸郊,其中掛載了 tmpfs、devpts货邓、proc秆撮、sysfs 和 selinuxfs 共5種文件系統(tǒng),這些都是系統(tǒng)運行時目錄换况,顧名思義职辨,只在系統(tǒng)運行時才會存在盗蟆,系統(tǒng)停止時會消失。

在注釋 1 處調(diào)用 property_init 函數(shù)來對屬性進行初始化舒裤,并在注釋 3 處調(diào)用 start_property_service 函數(shù)啟動屬性服務喳资。在注釋 2 處調(diào)用 signal_handler_init 函數(shù)用于設置子進程信號處理函數(shù),它被定義在 system/core/init/signal_handler.cpp 中腾供,主要用于防止init進程的子進程成為僵尸進程仆邓,為了防止僵尸進程的出現(xiàn),系統(tǒng)會在子進程暫停和終止的時候發(fā)出SIGCHLD信號台腥,而signal_handler_init函數(shù)就是用來接收SIGCHLD信號的(其內(nèi)部只處理進程終止的SIGCHLD信號)宏赘。

假設init進程的子進程Zygote終止了,signal_handler_init函數(shù)內(nèi)部會調(diào)用handle_signal函數(shù)黎侈,經(jīng)過層層的函數(shù)調(diào)用和處理察署,最終會找到Zygote進程并移除所有的Zygote進程的信息,再重啟Zygote服務的啟動腳本(比如 init.zygote64.rc)中帶有onrestart選項的服務峻汉,至于Zygote進程本身會在注釋 5 處被重啟贴汪。這里只是拿Zygote進程舉個例子,其他init進程子進程的原理也是類似的休吠。

注釋 4 處用來解析init.rc文件扳埂,解析init.rc的文件為system/core/init/init_parse.cpp文件,接下來我們查看init.rc里做了什么瘤礁。

僵尸進程與危害

在UNIX/Linux中阳懂,父進程使用fork創(chuàng)建子進程,在子進程終止之后柜思,如果父進程并不知道子進程已經(jīng)終止了岩调,這時子進程雖然已經(jīng)退出了,但是在系統(tǒng)進程表中還為它保留了一定的信息(比如進程號赡盘、退出狀態(tài)号枕、運行時間等),這個子進程就被稱作僵尸進程陨享。系統(tǒng)進程表是一項有限資源葱淳,如果系統(tǒng)進程表被僵尸進程耗盡的話,系統(tǒng)就可能無法創(chuàng)建新的進程了抛姑。

4. 解析 init.rc

init.rc 視一個非常重要的配置文件赞厕,它是由Android初始化語言(Android Init Language)編寫的腳本,這種語言主要包含5種類型語句:Action定硝、Command坑傅、Service、Option 和 Improt喷斋。init.rc 的配置代碼如下所示:
system/core/rootdir/init.rc

on init
    sysclktz 0
    # Mix device-specific information into the entropy pool
    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom
...

on boot
    # basic network init
    ifup lo
    hostname localhost
    domainname localdomain
    # set RLIMIT_NICE to allow priorities from 19 to -20
    setrlimit 13 40 40
...

這里只截取了一部分代碼唁毒,其中#是注釋符號蒜茴。on init和on boot是Action類型語句,它的格式為:

on <trigger> [&& <trigger>]*     //設置觸發(fā)器  
   <command>  
   <command>      //動作觸發(fā)之后要執(zhí)行的命令

為了分析如何創(chuàng)建zygote浆西,我們主要查看Services類型語句粉私,它的格式如下所示:

service <name> <pathname> [ <argument> ]*   //<service的名字><執(zhí)行程序路徑><傳遞參數(shù)>  
   <option>       //option是service的修飾詞,影響什么時候近零、如何啟動services  
   <option>  
   ...

需要注意的是诺核,Android8.0中對init.rc文件進行了拆分,每個服務對應一個rc文件久信。我們要分析的Zygote啟動腳本則在init.zygoteXX.rc中定義窖杀,這里拿64位處理器為例,init.zygote64.rc的代碼如下所示:
system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    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
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

其中service用于通知init進程創(chuàng)建名zygote的進程裙士,這個zygote進程執(zhí)行程序的路徑為/system/bin/app_process64入客,后面的則是要傳給app_process64的參數(shù)。class main指的是zygote的class name為main腿椎,后文會用到它桌硫。

5. 解析service類型語句

init.rc中的Action類型語句和Service類型語句都有相應的類來進行解析,Action類型語句采用ActionParser來進行解析啃炸,Service類型語句采用ServiceParser來進行解析铆隘,這里因為主要分析Zygote,所以只介紹ServiceParser南用。ServiceParser的實現(xiàn)代碼在system/core/init/service.cpp中膀钠,接下來我們來查看ServiceParser是如何解析上面提到的Service類型語句的,會用到兩個函數(shù):一個是ParseSection裹虫,它會解析Service的rc文件肿嘲,比如上文講到的init.zygote64.rc,ParseSetion函數(shù)主要用來搭建Service的架子恒界;另一個是PareLineSection睦刃,用于解析子項砚嘴。代碼如下所示:
system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    if (args.size() < 3) {  //判斷Service是否有name與執(zhí)行程序
        *err = "services must have a name and a program";
        return false;
    }
    const std::string& name = args[1];
    if (!IsValidName(name)) {  //檢查Service的name是否有效
        *err = StringPrintf("invalid service name '%s'", name.c_str());
        return false;
    }
    std::vector<std::string> str_args(args.begin() + 2, args.end());
    service_ = std::make_unique<Service>(name, "default", str_args);  //1
    return true;
}

bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
                                     const std::string& filename, int line,
                                     std::string* err) const {
    return service_ ? service_->HandleLine(args, err) : false;
}

注釋 1 處十酣,根據(jù)參數(shù),構(gòu)造出一個Service對象际长,它的classname為default耸采。在解析完所有數(shù)據(jù)后,會調(diào)用EndSection:

void ServiceParser::EndSection() {
    if (service_) {
        ServiceManager::GetInstance().AddService(std::move(service_));
    }
}

EndSection函數(shù)中會調(diào)用ServiceManager的AddService函數(shù)工育,接著查看AddService做了什么:

void ServiceManager::AddService(std::unique_ptr<Service> service) {
    Service* old_service = FindServiceByName(service->name());
    if (old_service) {
        ERROR("ignored duplicate definition of service '%s'",
              service->name().c_str());
        return;
    }
    services_.emplace_back(std::move(service)); //1
}

注釋 1 處的代碼將service對象加入到services鏈表中虾宇。上面的解析過程總體來講就是根據(jù)參數(shù)創(chuàng)建出service對象,然后根據(jù)選項域的內(nèi)容填充service對象如绸,最后將service對象加入到vector類型的services鏈表中嘱朽。

6. init啟動Zygote

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旭贬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搪泳,更是在濱河造成了極大的恐慌稀轨,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岸军,死亡現(xiàn)場離奇詭異奋刽,居然都是意外死亡,警方通過查閱死者的電腦和手機艰赞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門佣谐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人方妖,你說我怎么就攤上這事狭魂。” “怎么了吁断?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵趁蕊,是天一觀的道長。 經(jīng)常有香客問我仔役,道長掷伙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任又兵,我火速辦了婚禮任柜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沛厨。我一直安慰自己宙地,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布逆皮。 她就那樣靜靜地躺著宅粥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪电谣。 梳的紋絲不亂的頭發(fā)上秽梅,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音剿牺,去河邊找鬼企垦。 笑死,一個胖子當著我的面吹牛晒来,可吹牛的內(nèi)容都是我干的钞诡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼荧降!你這毒婦竟也來了接箫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朵诫,失蹤者是張志新(化名)和其女友劉穎列牺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗窃,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡瞎领,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了随夸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片九默。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宾毒,靈堂內(nèi)的尸體忽然破棺而出驼修,到底是詐尸還是另有隱情,我是刑警寧澤诈铛,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布乙各,位于F島的核電站,受9級特大地震影響幢竹,放射性物質(zhì)發(fā)生泄漏耳峦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一焕毫、第九天 我趴在偏房一處隱蔽的房頂上張望蹲坷。 院中可真熱鬧,春花似錦邑飒、人聲如沸循签。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽县匠。三九已至,卻和暖如春撒轮,著一層夾襖步出監(jiān)牢的瞬間乞旦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工腔召, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杆查,地道東北人扮惦。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓臀蛛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浊仆,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容