【Android】【init】解析init程式啟動過程
1.init簡介
init程式是Android系統中使用者空間的第一個程式,作為第一個程式,它被賦予了很多極其重要的工作職責,比如建立zygote(孵化器)和屬性服務等。init程式是由多個原始檔共同組成的,這些檔案位於原始碼目錄system/core/init。本文將基於Android7.0原始碼來分析Init程式。
2.引入init程式
說到init程式,首先要提到Android系統啟動流程的前幾步:
1.啟動電源以及系統啟動
當電源按下時引導晶片程式碼開始從預定義的地方(固化在ROM)開始執行。載入載入程式Bootloader到RAM,然後執行。
2.載入程式Bootloader
載入程式是在Android作業系統開始執行前的一個小程式,它的主要作用是把系統OS拉起來並執行。
3.linux核心啟動
核心啟動時,設定快取、被保護儲存器、計劃列表,載入驅動。當核心完成系統設定,它首先在系統檔案中尋找”init”檔案,然後啟動root程式或者系統的第一個程式。
4.init程式啟動
講到第四步就發現我們這一節要講的init程式了。關於Android系統啟動流程的所有步驟會在本系列的最後一篇做講解。
3.init入口函式
init的入口函式為main,程式碼如下所示。
system/core/init/init.cpp
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); } 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
cpp
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型別語句,它的格式為:
cpp
on <trigger> [&& <trigger>]* //設定觸發器 <command> <command> //動作觸發之後要執行的命令 |
為了分析如何建立zygote,我們主要檢視Services型別語句,它的格式如下所示:
cpp
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
cpp
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
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:
cpp
void ServiceParser::EndSection() { if (service_) { ServiceManager::GetInstance().AddService(std::move(service_)); } } |
接著檢視AddService做了什麼:
cpp
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
cpp
... 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
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
cpp
bool Service::StartIfNotDisabled() { if (!(flags_ & SVC_DISABLED)) { return Start(); } else { flags_ |= SVC_DISABLED_START; } return true; } |
接著檢視Start方法,如下所示。
cpp
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
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
cpp
property_init(); start_property_service(); |
這兩句程式碼用來初始化屬性服務配置並啟動屬性服務。首先我們來學習服務配置的初始化和啟動。
屬性服務初始化與啟動
property_init函式具體實現的程式碼如下所示。
system/core/init/property_service.cpp
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函式的具體程式碼:
cpp
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
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函式對屬性進行修改,程式碼如下所示。
cpp
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函式:
cpp
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程式
相關文章
- Linux的啟動過程及init程式Linux
- Android 9.0 init 啟動流程Android
- OpenHarmony的init程式、init配置與啟動項配置
- (連載)Android 8.0 : 系統啟動流程之init程式(一)Android
- (連載)Android 8.0 : 系統啟動流程之init程式(二)Android
- Android 系統開發_啟動階段篇 — 深入鑽研 initAndroid
- Linux init程式分析Linux
- CentOS的System V init啟動指令碼CentOS指令碼
- 理解 Android 程式啟動之全過程Android
- Android App啟動過程AndroidAPP
- 圖解 Android 系列(二)深入理解 init 與 zygote 程式圖解AndroidGo
- Linux基礎命令---init程式Linux
- Tomcat 7 啟動分析(四)各元件 init、start 方法呼叫Tomcat元件
- Linux下init程式原始碼分析Linux原始碼
- 2.16.10.init程式詳解1
- git init 命令Git
- __init__.py
- clinit和init
- 子程式、孤兒程式,殭屍程式, init程式
- 以太坊啟動過程原始碼解析原始碼
- SpringMVC原始碼解析(1)-啟動過程SpringMVC原始碼
- Android啟動過程剖析-深入淺出Android
- Go init 函式Go函式
- [JVM]<clinit>和<init>JVM
- 2788647047_init_multiprocessing
- Golang init() 函式Golang函式
- 50-cloud-init.yaml 和80-cloud-init.yaml區別CloudYAML
- Android啟動過程-萬字長文(Android14)Android
- Android效能優化之啟動過程(冷啟動和熱啟動)Android優化
- Java虛擬機器啟動過程解析Java虛擬機
- Linux基礎命令—initLinux
- git init命令詳解Git
- vue init webpack報錯VueWeb
- Python: __init__.py 作用Python
- Ubuntu移除cloud init元件UbuntuCloud元件
- 理解 go mod init 命令Go
- npx & yarn create & npm initYarnNPM
- JUC原始碼講解:逐步解析 Thread.init() 原始碼原始碼thread