Android系統啓動流程(一)解析init進程

相關文章
Android系統架構與系統源碼目錄javascript

前言

做爲「Android框架層」這個大系列中的第一個系列,咱們首先要了解的是Android系統啓動流程,在這個流程中會涉及到不少重要的知識點,這個系列咱們就來一一講解它們,這一篇咱們就來學習init進程。html

1.init簡介

init進程是Android系統中用戶空間的第一個進程,做爲第一個進程,它被賦予了不少極其重要的工做職責,好比建立zygote(孵化器)和屬性服務等。init進程是由多個源文件共同組成的,這些文件位於源碼目錄system/core/init。本文將基於Android7.0源碼來分析Init進程。java

2.引入init進程

說到init進程,首先要提到Android系統啓動流程的前幾步:
1.啓動電源以及系統啓動
當電源按下時引導芯片代碼開始從預約義的地方(固化在ROM)開始執行。加載引導程序Bootloader到RAM,而後執行。
2.引導程序Bootloader
引導程序是在Android操做系統開始運行前的一個小程序,它的主要做用是把系統OS拉起來並運行。
3.linux內核啓動
內核啓動時,設置緩存、被保護存儲器、計劃列表,加載驅動。當內核完成系統設置,它首先在系統文件中尋找」init」文件,而後啓動root進程或者系統的第一個進程。
4.init進程啓動linux

講到第四步就發現咱們這一節要講的init進程了。關於Android系統啓動流程的全部步驟會在本系列的最後一篇作講解。android

3.init入口函數

init的入口函數爲main,代碼以下所示。
system/core/init/init.cppios

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);
    }
    umask(0);
    add_environment("PATH", _PATH_DEFPATH);
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
    //建立文件並掛載
    if (is_first_stage) {
        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));
        mount("sysfs", "/sys", "sysfs", 0, NULL);
    }
    open_devnull_stdio();
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);
    NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");
    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
        //初始化屬性相關資源
        property_init();//1
        process_kernel_dt();
        process_kernel_cmdline();
        export_kernel_boot_props();
    }
 ...
    //啓動屬性服務
    start_property_service();//2
    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>());
    //解析init.rc配置文件
    parser.ParseConfig("/init.rc");//3
   ...   
       while (true) {
        if (!waiting_for_exec) {
            am.ExecuteOneCommand();
            restart_processes();
        }
        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }
        if (am.HasMoreCommands()) {
            timeout = 0;
        }
        bootchart_sample(&timeout);
        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;
}複製代碼

init的main方法作了不少事情,咱們只須要關注主要的幾點,在註釋1處調用 property_init來對屬性進行初始化並在註釋2處的 調用start_property_service啓動屬性服務,關於屬性服務,後面會講到。註釋3處 parser.ParseConfig("/init.rc")用來解析init.rc。解析init.rc的文件爲system/core/init/init_parse.cpp文件,接下來咱們查看init.rc裏作了什麼。小程序

4.init.rc

init.rc是一個配置文件,內部由Android初始化語言編寫(Android Init Language)編寫的腳本,它主要包含五種類型語句:
Action、Commands、Services、Options和Import。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>]*     //設置觸發器  
   <command>  
   <command>      //動做觸發以後要執行的命令複製代碼

爲了分析如何建立zygote,咱們主要查看Services類型語句,它的格式以下所示:架構

service <name> <pathname> [ <argument> ]*   //<service的名字><執行程序路徑><傳遞參數>  
   <option>       //option是service的修飾詞,影響何時、如何啓動services  
   <option>  
   ...複製代碼

須要注意的是在Android 7.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 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 /dev/stune/foreground/tasks複製代碼

其中service用於通知init進程建立名zygote的進程,這個zygote進程執行程序的路徑爲/system/bin/app_process64,後面的則是要傳給app_process64的參數。class main指的是zygote的class name爲main,後文會用到它。

5.解析service

接下來咱們來解析service,會用到兩個函數,一個是ParseSection,它會解析service的rc文件,好比上文講到的init.zygote64.rc,ParseSection函數主要用來搭建service的架子。另外一個是ParseLineSection,用於解析子項。代碼以下所示。
system/core/init/service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args,
                                 std::string* err) {
    if (args.size() < 3) {
        *err = "services must have a name and a program";
        return false;
    }
    const std::string& name = args[1];
    if (!IsValidName(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處,根據參數,構造出一個service對象,它的classname爲"default"。當解析完畢時會調用EndSection:

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

接着查看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鏈表中。上面的解析過程整體來說就是根據參數建立出service對象,而後根據選項域的內容填充service對象,最後將service對象加入到vector類型的services鏈表中。,

6.init啓動zygote

講完了解析service,接下來該講init是如何啓動service,在這裏咱們主要講解啓動zygote這個service。在zygote的啓動腳本中咱們得知zygote的class name爲main。在init.rc有以下配置代碼:
system/core/rootdir/init.rc

...
on nonencrypted    
    # A/B update verifier that marks a successful boot.  
    exec - root -- /system/bin/update_verifier nonencrypted  
    class_start main         
    class_start late_start 
...複製代碼

其中class_start是一個COMMAND,對應的函數爲do_class_start。咱們知道main指的就是zygote,所以class_start main用來啓動zygote。do_class_start函數在builtins.cpp中定義,以下所示。

system/core/init/builtins.cpp

static int do_class_start(const std::vector<std::string>& args) {
        /* Starting a class does not start services * which are explicitly disabled. They must * be started individually. */
    ServiceManager::GetInstance().
        ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
    return 0;
}複製代碼

來查看StartIfNotDisabled作了什麼:
system/core/init/service.cpp

bool Service::StartIfNotDisabled() {
    if (!(flags_ & SVC_DISABLED)) {
        return Start();
    } else {
        flags_ |= SVC_DISABLED_START;
    }
    return true;
}複製代碼

接着查看Start方法,以下所示。

bool Service::Start() {
    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
    time_started_ = 0;
    if (flags_ & SVC_RUNNING) {//若是Service已經運行,則不啓動
        return false;
    }
    bool needs_console = (flags_ & SVC_CONSOLE);
    if (needs_console && !have_console) {
        ERROR("service '%s' requires console\n", name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }
  //判斷須要啓動的Service的對應的執行文件是否存在,不存在則不啓動該Service
    struct stat sb;
    if (stat(args_[0].c_str(), &sb) == -1) {
        ERROR("cannot find '%s' (%s), disabling '%s'\n",
              args_[0].c_str(), strerror(errno), name_.c_str());
        flags_ |= SVC_DISABLED;
        return false;
    }

...
    pid_t pid = fork();//1.fork函數建立子進程
    if (pid == 0) {//運行在子進程中
        umask(077);
        for (const auto& ei : envvars_) {
            add_environment(ei.name.c_str(), ei.value.c_str());
        }
        for (const auto& si : sockets_) {
            int socket_type = ((si.type == "stream" ? SOCK_STREAM :
                                (si.type == "dgram" ? SOCK_DGRAM :
                                 SOCK_SEQPACKET)));
            const char* socketcon =
                !si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();

            int s = create_socket(si.name.c_str(), socket_type, si.perm,
                                  si.uid, si.gid, socketcon);
            if (s >= 0) {
                PublishSocket(si.name, s);
            }
        }
...
        //2.經過execve執行程序
        if (execve(args_[0].c_str(), (char**) &strs[0], (char**) ENV) < 0) {
            ERROR("cannot execve('%s'): %s\n", args_[0].c_str(), strerror(errno));
        }

        _exit(127);
    }
...
    return true;
}複製代碼

經過註釋1和2的代碼,咱們得知在Start方法中調用fork函數來建立子進程,並在子進程中調用execve執行system/bin/app_process,這樣就會進入framework/cmds/app_process/app_main.cpp的main函數,以下所示。
frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//1
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}複製代碼

從註釋1處的代碼能夠得知調用runtime(AppRuntime)的start來啓動zygote。

7.屬性服務

Windows平臺上有一個註冊表管理器,註冊表的內容採用鍵值對的形式來記錄用戶、軟件的一些使用信息。即便系統或者軟件重啓,它仍是可以根據以前在註冊表中的記錄,進行相應的初始化工做。Android也提供了一個相似的機制,叫作屬性服務。
在本文的開始,咱們提到在init.cpp代碼中和屬性服務相關的代碼有:
system/core/init/init.cpp

property_init();
  start_property_service();複製代碼

這兩句代碼用來初始化屬性服務配置並啓動屬性服務。首先咱們來學習服務配置的初始化和啓動。

屬性服務初始化與啓動
property_init函數具體實現的代碼以下所示。
system/core/init/property_service.cpp

void property_init() {
    if (__system_property_area_init()) {
        ERROR("Failed to initialize property area\n");
        exit(1);
    }
}複製代碼

__system_property_area_init函數用來初始化屬性內存區域。接下來查看start_property_service函數的具體代碼:

void start_property_service() {
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);//1
    if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }
    listen(property_set_fd, 8);//2
    register_epoll_handler(property_set_fd, handle_property_set_fd);//3
}複製代碼

註釋1處用來建立非阻塞的socket。註釋2處調用listen函數對property_set_fd進行監聽,這樣建立的socket就成爲了server,也就是屬性服務;listen函數的第二個參數設置8意味着屬性服務最多能夠同時爲8個試圖設置屬性的用戶提供服務。註釋3處的代碼將property_set_fd放入了epoll句柄中,用epoll來監聽property_set_fd:當property_set_fd中有數據到來時,init進程將用handle_property_set_fd函數進行處理。
在linux新的內核中,epoll用來替換select,epoll最大的好處在於它不會隨着監聽fd數目的增加而下降效率。由於內核中的select實現是採用輪詢來處理的,輪詢的fd數目越多,天然耗時越多。

屬性服務處理請求
從上文咱們得知,屬性服務接收到客戶端的請求時,會調用handle_property_set_fd函數進行處理:
system/core/init/property_service.cpp

static void handle_property_set_fd()
{  
...

        if(memcmp(msg.name,"ctl.",4) == 0) {
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx, &cr)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else {
            //檢查客戶端進程權限
            if (check_mac_perms(msg.name, source_ctx, &cr)) {//1
                property_set((char*) msg.name, (char*) msg.value);//2
            } else {
                ERROR("sys_prop: permission denied uid:%d name:%s\n",
                      cr.uid, msg.name);
            }
            close(s);
        }
        freecon(source_ctx);
        break;
    default:
        close(s);
        break;
    }
}複製代碼

註釋1處的代碼用來檢查客戶端進程權限,在註釋2處則調用property_set函數對屬性進行修改,代碼以下所示。

int property_set(const char* name, const char* value) {
    int rc = property_set_impl(name, value);
    if (rc == -1) {
        ERROR("property_set(\"%s\", \"%s\") failed\n", name, value);
    }
    return rc;
}複製代碼

property_set函數主要調用了property_set_impl函數:

static int property_set_impl(const char* name, const char* value) {
    size_t namelen = strlen(name);
    size_t valuelen = strlen(value);
    if (!is_legal_property_name(name, namelen)) return -1;
    if (valuelen >= PROP_VALUE_MAX) return -1;
    if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) {
        if (selinux_reload_policy() != 0) {
            ERROR("Failed to reload policy\n");
        }
    } else if (strcmp("selinux.restorecon_recursive", name) == 0 && valuelen > 0) {
        if (restorecon_recursive(value) != 0) {
            ERROR("Failed to restorecon_recursive %s\n", value);
        }
    }
    //從屬性存儲空間查找該屬性
    prop_info* pi = (prop_info*) __system_property_find(name);
    //若是屬性存在
    if(pi != 0) {
       //若是屬性以"ro."開頭,則表示是隻讀,不能修改,直接返回
        if(!strncmp(name, "ro.", 3)) return -1;
       //更新屬性值
        __system_property_update(pi, value, valuelen);
    } else {
       //若是屬性不存在則添加該屬性
        int rc = __system_property_add(name, namelen, value, valuelen);
        if (rc < 0) {
            return rc;
        }
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
      //以net.開頭的屬性名稱更新後,須要將屬性名稱寫入net.change中 
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {
        /* * Don't write properties to disk until after we have read all default properties * to prevent them from being overwritten by default values. */
        write_persistent_property(name, value);
    }
    property_changed(name, value);
    return 0;
}複製代碼

property_set_impl函數主要用來對屬性進行修改,並對以ro、net和persist開頭的屬性進行相應的處理。到這裏,屬性服務處理請求的源碼就講到這。

8.init進程總結
講到這,總結起來init進程主要作了三件事:
1.建立文件並掛載
2.初始化和啓動屬性服務
3.解析init.rc配置文件並啓動zygote進程

參考資料:
《深刻理解Android系統》
《深刻理解Android卷I》
Android的init過程詳解(一)
Android啓動過程深刻解析
Android7.0解析Init.rc文件
Android 7.0 init.rc的一點改變
Android7.0 init進程源碼分析
Android情景分析之屬性服務


歡迎關注個人微信公衆號,第一時間得到博客更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。

相關文章
相關標籤/搜索