android init入口函數分析

init入口函數分析

init的入口函數爲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);
    }


    // Clear the umask.
    umask(0);


    add_environment("PATH", _PATH_DEFPATH);

    //判斷是否是第一次開機
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);


    // 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.

    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);
    }


    // 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.

    //調用dup函數把標準輸入,輸出,錯誤輸出都重定位到/dev/__null__

    open_devnull_stdio();

    //創建/dev/__kmsg__並打開/dev/__kmsg__設備節點,用於後面的log信息處理,如寫日誌等就往這個節點裏面寫.

    klog_init();

    //設置實時將LOG打印到節點

    klog_set_level(KLOG_NOTICE_LEVEL);


    NOTICE("init %s started!\n", is_first_stage ? "first stage" : "second stage");


    if (!is_first_stage) {

////關閉/dev/.booting文件的相關權限

        // Indicate that booting is in progress to background fw loaders, etc.
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

//主要工作是申請32k共享內存,其中前1k是屬性區的頭,後面31k可以存247個屬性(受前1k頭的限制)。property_init初始化完property以後,加載/default.prop的屬性定義。
        property_init();



//先初始化DT,因爲DT的屬性集的優先級高於comand line

        process_kernel_dt();

 // properties set in DT always have priority over the command-line ones.

        process_kernel_cmdline();

        // //導出內核變量
        export_kernel_boot_props();
    }


    selinux_initialize(is_first_stage);



    // 恢復下面目錄的安全上下文爲系統原始設置
    NOTICE("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon("/property_contexts");
    restorecon_recursive("/sys");


    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }

//初始化SIGCHLD信號處理的
    signal_handler_init();


    property_load_boot_defaults();
    export_oem_lock_status();//啓動屬性服務
    start_property_service();
#ifdef BOOT_TRACE
    if (boot_trace) {
        ERROR("enable boot systrace...");
        property_set("debug.atrace.tags.enableflags", "0x3ffffe");
    }
#endif


    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");


    ActionManager& am = ActionManager::GetInstance();


    am.QueueEventTrigger("early-init");


    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");


    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");


    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");


    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = property_get("ro.bootmode");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }


    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");


    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.創建文件系統目錄並掛載相關的文件系統 
2.屏蔽標準的輸入輸出 
3.初始化內核log系統 
4.調用property_init初始化屬性相關的資源 
5.完成SELinux相關工作 
6.重新設置屬性 
7.創建epoll句柄 
8.裝載子進程信號處理器 
9.通過property_start_service啓動屬性服務 
10.通過parser.ParseConfig(“/init.rc”)來解析init.rc 
接下來對上述部分步驟,進行詳細解析。


1.創建文件系統目錄並掛載相關的文件系統

//除屏蔽字(file mode creation mask),保證新建的目錄的訪問權限不受屏蔽字影響。

umask(0);
add_environment("PATH", _PATH_DEFPATH); bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
// Get the basic filesystem setup we need put together in the initramdisk
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);}

該部分主要用於創建和掛載啓動所需的文件目錄。 
需要注意的是,在編譯Android系統源碼時,在生成的根文件系統中,並不存在這些目錄,它們是系統運行時的目錄,即當系統終止時,就會消失。

在init初始化過程中,Android分別掛載了tmpfs,devpts,proc,sysfs這4類文件系統。

2.屏蔽標準的輸入輸出

open_devnull_stdio();

前文生成/dev目錄後,init進程將調用open_devnull_stdio函數,屏蔽標準的輸入輸出。 
open_devnull_stdio函數會在/dev目錄下生成null設備節點文件,並將標準輸入、標準輸出、標準錯誤輸出全部重定向到null設備中。

void open_devnull_stdio(void)
{ // Try to avoid the mknod() call if we can. Since SELinux makes // a /dev/null replacement available for free, let's use it. int fd = open("/sys/fs/selinux/null", O_RDWR); if (fd == -1) { // OOPS, /sys/fs/selinux/null isn't available, likely because // /sys/fs/selinux isn't mounted. Fall back to mknod. static const char *name = "/dev/__null__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
            fd = open(name, O_RDWR);
            unlink(name);
        } if (fd == -1) { exit(1);
        }
    }

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2); if (fd > 2) {
        close(fd);
    }
}

open_devnull_stdio函數定義於system/core/init/util.cpp中。

這裏需要說明的是,dup2函數的作用是用來複制一個文件的描述符,通常用來重定向進程的stdin、stdout和stderr。它的函數原形是:

int dup2(int oldfd, int targetfd)

該函數執行後,targetfd將變成oldfd的複製品。

因此上述過程其實就是:創建出null設備後,將0、1、2綁定到null設備上。因此init進程調用open_devnull_stdio函數後,通過標準的輸入輸出無法輸出信息。

4.初始化屬性域

if (!is_first_stage) { ....... property_init(); ....... }

調用property_init初始化屬性域。在Android平臺中,爲了讓運行中的所有進程共享系統運行時所需要的各種設置值,系統開闢了屬性存儲區域,並提供了訪問該區域的API。

需要強調的是,在init進程中有部分代碼塊以is_first_stage標誌進行區分,決定是否需要進行初始化,而is_first_stage的值,由init進程main函數的入口參數決定。 其原因在於,在引入selinux機制後,有些操作必須要在內核態才能完成; 
但init進程作爲android的第一個進程,又是運行在用戶態的。 
於是,最終設計爲用is_first_stage進行區分init進程的運行狀態。init進程在運行的過程中,會完成從內核態到用戶態的切換。

void property_init() {

if (__system_property_area_init())

{

ERROR("Failed to initialize property area\n"); exit(1);

}

}

property_init函數定義於system/core/init/property_service.cpp中,如上面代碼所示,最終調用_system_property_area_init函數初始化屬性域。

5.完成SELinux相關工作

// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.selinux_initialize(is_first_stage);

init進程進程調用selinux_initialize啓動SELinux。從註釋來看,init進程的運行確實是區分用戶態和內核態的。



static void selinux_initialize(bool in_kernel_domain)

{

Timer t;

selinux_callback cb; //用於打印log的回調函數

cb.func_log = selinux_klog_callback;

selinux_set_callback(SELINUX_CB_LOG, cb); //用於檢查權限的回調函數

cb.func_audit = audit_callback;

selinux_set_callback(SELINUX_CB_AUDIT, cb);

if (in_kernel_domain)

{

//內核態處理流程

INFO("Loading SELinux policy...\n");

//用於加載sepolicy文件。該函數最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略配置文件,後續的MAC才能開展起來。

if (selinux_android_load_policy() < 0) {

ERROR("failed to load policy: %s\n",

strerror(errno));

security_failure();

}

//內核中讀取的信息

bool kernel_enforcing = (security_getenforce() == 1);

//命令行中得到的數據

bool is_enforcing = selinux_is_enforcing();

if (kernel_enforcing != is_enforcing) { //用於設置selinux的工作模式。selinux有兩種工作模式: //1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日誌 //2、”enforcing”,所有操作都會進行權限檢查。在一般的終端中,應該工作於enforing模式 if(security_setenforce(is_enforcing)) {

........ //將重啓進入recovery mode security_failure();

}

} if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { security_failure(); } NOTICE("(Initializing SELinux %s took %.2fs.)\n", is_enforcing ? "enforcing" : "non-enforcing", t.duration()); } else { selinux_init_all_handles(); }}

6.重新設置屬性

// If we're in the kernel domain, re-exec init to transition to the init domain now that the SELinux policy has been loaded. if (is_first_stage) { //按selinux policy要求,重新設置init文件屬性 if (restorecon("/init") == -1) {
        ERROR("restorecon failed: %s\n", strerror(errno));
        security_failure();
    } char* path = argv[0]; char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; //這裏就是前面所說的,啓動用戶態的init進程,即second-stage if (execv(path, args) == -1) {
        ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
        security_failure();
    }
} // 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. INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");

上述文件節點在加載Sepolicy之前已經被創建了,因此在加載完Sepolicy後,需要重新設置相關的屬性。

9.啓動配置屬性的服務端

start_property_service();

init進程在共享內存區域中,創建並初始化屬性域。其它進程可以訪問屬性域中的值,但更改屬性值僅能在init進程中進行。這就是init進程調用start_property_service的原因。其它進程修改屬性值時,要預先向init進程提交值變更申請,然後init進程處理該申請,並修改屬性值。在訪問和修改屬性時,init進程都可以進行權限控制。

void start_property_service() {
    //創建了一個非阻塞socket
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0666, 0, 0, NULL); if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno)); exit(1);
    }
    //調用listen函數監聽property_set_fd, 於是該socket變成一個server
    listen(property_set_fd, 8);
    //監聽server socket上是否有數據到來
    register_epoll_handler(property_set_fd,  handle_property_set_fd);
}

我們知道,在create_socket函數返回套接字property_set_fd時,property_set_fd是一個主動連接的套接字。此時,系統假設用戶會對這個套接字調用connect函數,期待它主動與其它進程連接。

由於在服務器編程中,用戶希望這個套接字可以接受外來的連接請求,也就是被動等待用戶來連接,於是需要調用listen函數使用主動連接套接字變爲被連接套接字,使得一個進程可以接受其它進程的請求,從而成爲一個服務器進程。

因此,調用listen後,init進程成爲一個服務進程,其它進程可以通過property_set_fd連接init進程,提交設置系統屬性的申請。

listen函數的第二個參數,涉及到一些網絡的細節。

在進程處理一個連接請求的時候,可能還存在其它的連接請求。因爲TCP連接是一個過程,所以可能存在一種半連接的狀態。有時由於同時嘗試連接的用戶過多,使得服務器進程無法快速地完成連接請求。

因此,內核會在自己的進程空間裏維護一個隊列,以跟蹤那些已完成連接但服務器進程還沒有接手處理的用戶,或正在進行的連接的用戶。這樣的一個隊列不可能任意大,所以必須有一個上限。listen的第二個參數就是告訴內核使用這個數值作爲上限。因此,init進程作爲系統屬性設置的服務器,最多可以同時爲8個試圖設置屬性的用戶提供服務。

在啓動配置屬性服務的最後,調用函數register_epoll_handler。該函數將利用之前創建出的epoll句柄監聽property_set_fd。當property_set_fd中有數據到來時,init進程將利用handle_property_set_fd函數進行處理。

static void handle_property_set_fd() { .......... if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) { return;
    } ........ r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT)); ......... switch(msg.cmd) { ......... } ......... }
  • 1

handle_propery_set_fd函數實際上是調用accept函數監聽連接請求,接收property_set_fd中到來的數據,然後利用recv函數接受到來的數據,最後根據到來數據的類型,進行設置系統屬性等相關操作,在此不做深入分析。

介紹一下系統屬性改變的一些用途。 
在init.rc中定義了一些與屬性相關的觸發器。當某個條件相關的屬性被改變時,與該條件相關的觸發器就會被觸發。舉例來說,如下面代碼所示,debuggable屬性變爲1時,將執行啓動console進程等操作。

on property:ro.debuggable=1 # Give writes to anyone for the trace folder on debug builds.
    # The folder is used to store method traces. chmod 0773 /data/misc/trace start console

總結一下,其它進程修改系統屬性時,大致的流程如下圖所示:其它的進程像init進程發送請求後,由init進程檢查權限後,修改共享內存區。 
這裏寫圖片描述


static void selinux_initialize(bool in_kernel_domain) { Timer t; selinux_callback cb; //用於打印log的回調函數 cb.func_log = selinux_klog_callback; selinux_set_callback(SELINUX_CB_LOG, cb); //用於檢查權限的回調函數 cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); if (in_kernel_domain) { //內核態處理流程 INFO("Loading SELinux policy...\n"); //用於加載sepolicy文件。該函數最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略配置文件,後續的MAC才能開展起來。 if (selinux_android_load_policy() < 0) { ERROR("failed to load policy: %s\n", strerror(errno)); security_failure(); } //內核中讀取的信息 bool kernel_enforcing = (security_getenforce() == 1); //命令行中得到的數據 bool is_enforcing = selinux_is_enforcing(); if (kernel_enforcing != is_enforcing) { //用於設置selinux的工作模式。selinux有兩種工作模式: //1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反權限的話,會記錄日誌 //2、”enforcing”,所有操作都會進行權限檢查。在一般的終端中,應該工作於enforing模式 if(security_setenforce(is_enforcing)) { ........ //將重啓進入recovery mode security_failure(); } } if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { security_failure(); } NOTICE("(Initializing SELinux %s took %.2fs.)\n", is_enforcing ? "enforcing" : "non-enforcing", t.duration()); } else { selinux_init_all_handles(); }}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章