1. 程式人生 > >Android開機流程分析 -- init程序

Android開機流程分析 -- init程序

init程序(system\core\init)是Linux Kernal啟動之後,在使用者空間執行的第一個程序。init程序是一個守護程序,它的生命週期貫穿整個Linux核心執行的始終,在Linux系統中的所有程序都是由init程序建立並執行的。因為Android是基於Linux核心的,所以Google為啟動並執行整個Android系統,實現了自己的init程序。
一、init程序職責
init作為所有程序的父程序,被賦予了至關重要的工作職責,那麼它做了哪些事情呢?
1、首先系統核心啟動完成後,到使用者空間啟動init程序,然後再啟動系統執行的其他程序。在系統啟動完成後,init程序作為守護程序執行。
init程序作為守護程序,用來監視其他程序,若某個被監視的程序一旦終結,進入僵死狀態,就會釋放掉程序所佔用的系統資源。若有時候父程序先於子程序死掉,則一般會讓init程序領養,init程序成為其父程序。
2、init程序負責建立系統中的幾個關鍵程序,最重要的是zygote,zygote更是java世界的建立者。那麼,init程序是如何建立zygote的呢?
3、Android系統有很多屬性,於是init提供了一個property service(屬性服務)來管理它們。那麼這個屬性服務是怎麼工作的呢?
二、init分析
(一)、init中main函式分析(system\core\init\init.c)
(1)、先看一下這個變數的定義:strcut pollfd ufds[4];在後面的poll輪訓中用到了該陣列。
ufds是一個指向pollfd的結構體陣列,pollfd的定義如下:

#include <sys/poll.h> int poll(struct pollfd *ufds, unsigned int nfds, int timeout); struct pollfd { int fd; /* 想查詢的檔案描述符. */ short int events; /* 等待的事件*/ short int revents; /* 實際發生了的事件 */ };

ufds 指向 struct pollfd 陣列,pollfd是用於存放需要監控事件的檔案描述符;nfds 指定 pollfd 陣列元素的個數,也就是要監測幾個 pollfd;timeout用於標記poll函式呼叫的阻塞時間,如果timeout為0,表示不阻塞,直接返回;poll函式返回ufds中revents不為0的fd個數;如果超時沒有任何事件發生,返回0;失敗時返回-1。

(2)、啟動ueventd、watchdog程序
 if (!strcmp(basename(argv[0]), "ueventd")) 
return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv);可以看出傳入到mian函式的命令列引數有ueventd和watchdogd,通過比較它們的值,進入到相應的main函式中,啟動uevent和watchdog程序,分別是冷插拔程序和看門狗程序。ueventd伺服程式將解析/ueventd.rc檔案,並建立相應的裝置結點。watchdogd伺服程式是一個看門狗程式,它的任務就是定期向看門狗裝置檔案執行寫操作,以判斷系統是否正常執行。
(3)、將當前umask值設定為0
/* clear the umask */
umask(0);
呼叫umask(0)函式(函式原型:mode_t umask(mode_t mask)),將當前umask值設定為0(實際上是引數值mask & 0777 之後的值),先前預設值為022;引用該函式的主要作用是在建立檔案時設定或遮蔽掉檔案的一些許可權。這裡將mask設定成0,說明後面的檔案建立時的許可權是多少實際許可權就是多少。
(4)、建立檔案系統目錄並掛載檔案系統
 /* Get the basic filesystem setup we need put
         * together in the initramdisk on / and then we'll
         * let the rc file figure out the rest.
         */
	mkdir("/dev", 0755);
	mkdir("/proc", 0755);
	mkdir("/sys", 0755);
	mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
	mkdir("/dev/pts", 0755);
	mkdir("/dev/socket", 0755);
	mount("devpts", "/dev/pts", "devpts", 0, NULL);
	mount("proc", "/proc", "proc", 0, NULL);
	mount("sysfs", "/sys", "sysfs", 0, NULL);
首先構建dev、proc、sys三個主要目錄(其他目錄從init.rc中讀取建立),再呼叫mount函式對檔案系統掛載。在這裡分別掛載了tmpfs、devpts、proc、sysfs四類檔案系統。
mount函式定義:int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data);source是要掛上的檔案系統,通常是裝置名;target是檔案系統所要掛載的目標目錄;filesystemtype是檔案系統型別,只要有“ext2”、“ext3”、“proc”、“sysfs”、“ntfs”、“tmpfs”等;mountflags是檔案系統的讀寫訪問標誌;data是檔案系統特有的引數。
tmpfs:是一種虛擬記憶體檔案系統,典型的tmpfs檔案系統是完全駐留在ARM中(虛擬記憶體)的,因此讀寫速度遠遠快於快閃記憶體和硬碟檔案系統。並且tmpfs下的內容均為臨時性內容,因此如果將tmpfs解除安裝後,其裡面的內容將不再存在。
devpts:是一種遠端虛擬終端檔案裝置,標準掛載點是/dev/pts。只要pty的主複合裝置/dev/ptmx被開啟,就會在/dev/pts下動態的建立一個新的pty裝置檔案。通過對相應的裝置檔案進行操作,可以達到操作硬體的目的(讀寫)。
proc:是一種虛擬的檔案系統,只存在記憶體中,而不佔用記憶體空間。它可以看作是核心內部資料結構的介面,通過它我們可以獲得系統的資訊,同時也能夠在執行時修改特定的核心引數。通過echo可以修改核心引數,舉例如下:
[email protected]:/ # cat /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
1
[email protected]:/ # echo "0" > /proc/sys/net/ipv4/ip_forward
echo "0" > /proc/sys/net/ipv4/ip_forward
[email protected]:/ # cat /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
0
sysfs:是Linux中設計的一種較新的虛擬檔案系統,與proc類似,除了與proc一樣檢視和設定核心引數之外,還為Linux統一裝置管理模型之用。參考:http://blog.chinaunix.net/uid-27411029-id-3522299.html
注意:在編譯Android系統原始碼時,在生成的根檔案系統中,不存在/dev、/proc、/sys這類目錄,它們是系統執行時的目錄,有init程序在執行時生成,當系統終止時,自動消失。
(5)、遮蔽標準的輸入輸出,即標準的輸入輸出重定向到null裝置(/dev/__null__)。
/* indicate that booting is in progress to background fw loaders, etc */
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));    /* 檢測/dev/.booting檔案是否可讀寫和建立*/

/* We must have some place other than / to create the
 * device nodes for kmsg and null, otherwise we won't
 * be able to remount / read-only later on.
 * Now that tmpfs is mounted on /dev, we can actually
 * talk to the outside world.
 */
open_devnull_stdio(); /*在/dev目錄下生成__null__裝置節點檔案,並將標準輸入,標準輸出,標準錯誤,標準錯誤輸出全部重定向__null__裝置中。*/
klog_init(); /* 初始化核心log系統。生成"/dev/__kmsg__"裝置節點檔案, 作為init的日誌輸出裝置(/dev/kmsg),設定完成後馬上呼叫unlink函式,其他程序就無法開啟這個檔案讀取日誌資訊了。這個裝置會把它收到的任何寫入都作為printk的輸出,printk是核心中執行的向控制檯輸出顯示的函式。*/
property_init();/* 初始化屬性服務所需要的基本空間。首先建立一個/dev/__properties__檔案,然後通過對應的檔案描述對映一塊共享記憶體,大小PA_SIZE(49152),對映的地址和相應的檔案描述符儲存在struct workspace中。 */
(6)、從/proc/cpuinfo中獲取硬體資訊,將處理傳遞給核心的命令列引數。
  get_hardware_name(hardware, &revision);
process_kernel_cmdline(); 
process_kernel_cmdline函式中,先呼叫import_kernel_cmdline函式從/proc/cmdline讀取核心啟動引數,然後呼叫export_kernel_boot_props在屬性系統設定啟動屬性。
如果啟用了SELinux機制,接下來將載入selinux策略,並初始化檔案安全上下文以及屬性安全上下文。selinux是Linux核心提供的一種強制訪問控制安全系統,在這種訪問控制體系的限制下,程序只能訪問那些在他的任務中所需要的檔案。restorecon函式表示所定義的檔案可以恢復原來的檔案標籤。參考:http://www.it165.net/pro/html/201402/9640.html
union selinux_callback cb;
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
selinux_initialize();
/* These directories were necessarily created before initial policy load
* and therefore need their security context restored to the proper value.
* This must happen before /dev is populated by ueventd.
*/
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
(7)、如果當前啟動模式不是充電模式,將/default.prop檔案中載入預設的一些屬性設定。這裡的bootmode是一個靜態全域性變數,用來標示啟動模式,注意這裡的bootmode是在UBoot中設定的。
			is_charger = !strcmp(bootmode, "charger");
      INFO("property init\n");
      property_load_boot_defaults();
8)、解析init.rc配置檔案。對init.rc配置檔案的解析,稍候詳細介紹。
			INFO("reading config file\n");
      init_parse_config_file("/init.rc");
解析完配置檔案init.rc後,會得到一系列的Action(動作)和服務,生成服務列表和動作列表;動作列表和服務列表會以連結串列的形式註冊到service_list與action_list中,service_list和action_list是init程序中宣告的全域性結構體。
(9)、action待執行佇列
首先觸發init.rc中配置的early-init的action,並通過action_add_queue_tail函式將該action放入可執行佇列action_queue隊尾;接著觸發內建action,內建action並沒有在init.rc或init.<hardware>.rc中配置,queue_builtin_action函式第一個引數是函式指標,第二個引數是action的觸發器,即action的名字,函式指標的作用是把該觸發器指定的Action放入可執行佇列隊尾。
如果定義了BOOTCHART巨集,觸發boot chart初始化的Action。boot chart是分析系統啟動過程的工具,並生成系統啟動過程的圖表,以提供一些有價值的資訊,幫助提升系統的啟動速度。
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");
queue_builtin_action(console_init_action, "console_init");

/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);

/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
* wasn't ready immediately after wait_for_coldboot_done
*/
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");

/* Don't mount filesystems or start core system services if in charger mode. */
if (is_charger) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}

/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
(10)、init經過上面的初始化和觸發action的過程,進入一個無限迴圈,執行command,處理事件。
			for(;;) {
int nr, i, timeout = -1;

execute_one_command(); /* 執行當前Action的一個Command檢測 */
restart_processes();  /* 重新啟動異常退出的service。通過遍歷service_list列表,找到flags設定為需要啟動的service,呼叫restart_service_if_needed函式啟動它。*/

if (!property_set_fd_init && get_property_set_fd() > 0) {  /* 監聽來自屬性服務property service的事件,屬性服務是init提供的重要功能。 */
ufds[fd_count].fd = get_property_set_fd();  /* 得到property service的fd,是一個socket。 */
    /* 有PILLIN事件發生時,revents就會設定為POLLIN。*/
ufds[fd_count].events = POLLIN; /* 有資料可讀的事件 */
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
/* 監控signal。主要用於接收子程序異常退出後核心丟擲的SIGCHLD訊號,然後決定回收子程序資源或者重啟子程序,防止子程序成為殭屍程序。 */
if (!signal_fd_init && get_signal_fd() > 0) {     /* get_signal_fd函式用於獲得子程序退出新號, */
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
/* 監聽來自keychord裝置的事件,keychord是組合按鍵裝置 */
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}

/* 死去的服務如果需要重啟,設定等待時間 */if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}

/* 如果有正要處理的Action,則設定timeout為0,表示poll不阻塞 */if (!action_queue_empty() || cur_action)
timeout = 0;

#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif

/* 將ufds傳入poll函式,監控事件的發生 */nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;

for (i = 0; i < fd_count; i++) {    /* 輪詢這幾個Socket */
if (ufds[i].revents & POLLIN) {   /* 可讀事件 */
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd(); /* 處理屬性服務相關事件 */
else if (ufds[i].fd == get_keychord_fd())
handle_keychord(); /* 處理keychord事件 */
else if (ufds[i].fd == get_signal_fd())
handle_signal();/* 處理signal事件 */
}
}
}
這裡以get_property_set_fd為例,簡要說明一下這部分的處理機制。
當start_property_service中建立的Socket上有可讀事件發生時,init中的poll函式監控到可讀事件發生,便開始執行handle_property_set_fd函式,該函式位於system/core/init/Property_service.c中,程式碼如下:
void handle_property_set_fd()
{
    prop_msg msg;
    int s;
    int r;
    int res;
    struct ucred cr;
    struct sockaddr_un addr;
    socklen_t addr_size = sizeof(addr);
    socklen_t cr_size = sizeof(cr);
    char * source_ctx = NULL;
    struct pollfd ufds[1];
    const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */
    int nr;
    
     /* 接收property_set_fd上的連線請求,accept是標準的Socket程式設計函式 */
    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }

    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to receive socket options\n");
        return;
    }

    ufds[0].fd = s;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;
    nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
    if (nr == 0) {
        ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
        close(s);
        return;
    } else if (nr < 0) {
        ERROR("sys_prop: error waiting for uid=%d to send property message. err=%d %s\n", cr.uid, errno, strerror(errno));
        close(s);
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT)); /* recv接收請求資料 */
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %d expected: %zu errno: %d\n",
              r, sizeof(prop_msg), errno);
        close(s);
        return;
    }

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
/* 屬性客戶端設定的訊息碼 */
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);
 
/* 如果訊息名以ctl.開頭,則認為是control message(控制訊息),呼叫check_control_mac_perms函式檢查許可權,如果滿足許可權要求則執行handle_control_message設定訊息。這部分訊息其實控制的是Service的開啟和關閉。 */
        if(memcmp(msg.name,"ctl.",4) == 0) {
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx)) {
                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 { /* 其他型別的訊息,需要通過check_perms檢查許可權,如果滿足許可權要求呼叫property_set函式設定屬性 */            if (check_perms(msg.name, source_ctx)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }
}
當屬性伺服器接收到客戶端請求後,init中的poll函式監控到可讀事件,這個時候它會返回,判斷property fd這個Socket上是否發生了可讀事件,之後呼叫init的handle_property_set_fd方法處理請求。handle_property_set_fd函式主要做了兩部分工作:首先通過accept和recv這兩個標準的Socket程式設計函式接收並取得客戶端訊息,然後根據訊息型別分別呼叫許可權檢測函式和屬性設定函式,設定屬性或啟動相應的服務。
通過對init.c檔案中main函式的分析,可將init工作流程精簡為以下四點:
1).  初始化檔案系統和日誌系統,為之後的執行階段做準備。
2).  解析init.rc和init.<hardware>.rc初始化檔案。
3).  觸發需要執行的Action和Service。
4).  init迴圈監聽事件。init觸發所有Action後,進入一個無限迴圈,執行在可執行佇列中的命令,重啟異常退出的Service,並迴圈處理來自property service、signal、keychord的事件。
下一節接著分析配置檔案的解析

相關推薦

Android開機流程分析 -- init程序

init程序(system\core\init)是Linux Kernal啟動之後,在使用者空間執行的第一個程序。init程序是一個守護程序,它的生命週期貫穿整個Linux核心執行的始終,在Linux系統中的所有程序都是由init程序建立並執行的。因為Android是基於L

Android 8.0 系統啟動流程init程序--第二階段(五)

1、概述     上一篇中講了init程序的第一階段,我們接著講第二階段,主要有以下內容 建立程序會話金鑰並初始化屬性系統 進行SELinux第二階段並恢復一些檔案安全上下文 新建epoll並初始化子程序終止訊號處理函式 設定其他系統屬性並開啟系統屬性服務

Android 8.0 系統啟動流程init程序--第一階段(四)

1、概述     上一篇中講到,Linux系統執行完初始化操作最後會執行根目錄下的init檔案,init是一個可執行程式,它的原始碼在platform/system/core/init/init.cpp。init程序是使用者空間的第一個程序,我們熟悉的app應

Android系統啟動流程——解析init程序啟動過程

最近主要是在看android關機充電流程,對android啟動有些迷惑,結合網上部落格專家的文章,加一些自己的理解。 1.init簡介 init程序是Android系統中使用者空間的第一個程序,作為第一個程序,它被賦予了很多極其重要的工作職責,比如建立zygote(孵化器

Android啟動流程分析(七) init.rc的解析

############################################# 本文為極度寒冰原創,轉載請註明出處 ############################################# Init.rc的解析過程是筆者認為在andro

Android啟動流程分析(九) 解析init.rc的service

############################################# 本文為極度寒冰原創,轉載請註明出處 ############################################# 在分析完解析init.rc的action之後,剩

Linux開機流程分析

開機流程 多重開機 核心檔案       檢視systemV的runlevel與systemd的target的對應關係。 sys

Android開機流程(一)

參考文章 一、概述 簡單梳理下Andorid啟動流程。 一般作業系統啟動流程如下圖: Android系統啟動流程概覽: system_server服務啟動流程 啟動過程:  Loader -> Kernel -> Native -> F

Android 音效流程分析

private void onPlaySoundEffect(int effectType, int volume) { synchronized (mSoundEffectsLock) { onLoadSoundEffects();

Android開機時間分析

文章來源:http://blog.csdn.net/huangyabin001/article/details/42777703 一、 關於本篇博文 該文件簡單主要描述瞭如何找出開機各個階段耗時情況,以及對開機各個階段的分析方法和如何優化開機時間,減少耗時。便於讀者可以

Android開機log分析

1.  1. android啟動第一階段:啟動android第一個程序init,通過解析init.rc指令碼,生成檔案系統,啟動vold、media、SurfaceFlinger等Nativie服務。在這個階段你可以看到帶“Android”文字靜態logo和帶“android”文字的開機動畫   2.  [

Android layout 流程分析

寫在前面 推薦 部分來源於動腦學院 筆記 一般 layout都在自定義的 ViewGroup中使用 ViewGroup存在的意義 設計它的目的是什麼? 1)作為容器

Android啟動過程分析——init.c(一)

《Android框架揭祕》這本書是基於Android2.2原始碼的,但是手頭上只有Android4.4的原始碼。這兩個版本的啟動過程基本一致,但是在具體的編碼上,還是有一些區別的,下面,對照著這本書,分析一下4.4的init程序。 分析從main開始 首

Android啟動流程分析(十) action的執行和service的啟動

void service_start(struct service *svc, const char *dynamic_args) { /// ****************************** start service 的第一個階段 struct stat s; pid_t

Android啟動過程分析——init.c(二)

Part 4 // ================================================== // Part 4 union selinux_callback cb; cb.func_log = klog_write;

Android O: init程序啟動流程分析(階段三)

本篇部落格我們就來看看init程序啟動時,解析init.rc檔案相關的工作。 一、建立Parser並決定解析檔案 int main(int argc, char** argv) { .............. //定義Action中

Android系統啟動流程(一)解析init程序啟動過程

前言 作為“Android框架層”這個大系列中的第一個系列,我們首先要了解的是Android系統啟動流程,在這個流程中會涉及到很多重要的知識點,這個系列我們就來一一講解它們,這一篇我們就來學習init程序。 1.init簡介 init程序是An

android開機啟動流程簡單分析

android啟動 當載入程式啟動Linux核心後,會載入各種驅動和資料結構,當有了驅動以後,開始啟動Android系統同時會載入使用者級別的第一個程序init(system\core\init\init.cpp)程式碼如下: int main(int ar

Android系統啟動流程——init程序

配置檔案:system/rootdir/init.rc     init程序是一個由核心啟動的使用者級程序。核心自行啟動之後(已經被載入記憶體,開始執行並已初始化所有裝置驅動程式和資料結構等),通過啟動一個使用者級程式init的方式完成引導過程。Init始終是第一個程序,可

Android Init程序原始碼分析

Init 程序原始碼分析 基於Linux核心的android系統,在核心啟動完成後將建立一個Init使用者程序,實現了核心空間到使用者空間的轉變。在Android 啟動過程介紹一文中介紹了Android系統的各個啟動階段,init程序啟動後會讀取init.rc配置檔案,通