Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android init進程分析 基本流程

android init進程分析 基本流程

編輯:關於Android編程

(懶人最近想起我還有csdn好久沒打理了,這個android init躺在我的草稿箱中快5年了,稍微改改發出來吧)

android設備上電,引導程序引導進入boot(通常是uboot),加載initramfs、kernel鏡像,啟動kernel後,進入用戶態程序。第一個用戶空間程序是init, PID固定是1.在android系統上,init的代碼位於/system/core/init下,基本功能有:

管理設備解析並處理啟動腳本init.rc實時維護這個init.rc中的服務

init進程的系統初始化和服務流程,可以簡單的看下init.c中的main函數。這裡簡要分析一下這個函數的主要作用,細節不展開或後繼再展開。

簡要說下main函數的各項操作吧:

    if (!strcmp(basename(argv[0]), ueventd))
        return ueventd_main(argc, argv);

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

以上這段,是main函數入口的第一句,這是判斷是跑那個程序。

/system/core/init 這裡面的一份代碼,編出的二進制可執行程序init,實際是分為三個程序運行的,指示大家共享一份代碼而已。三份分別是:
/init: 實際init可執行程序
/sbin/ueventd:ueventd daemon程序,是init的軟鏈接
/sbin/watchdogd: watchdogd daemon程序,也是init的軟鏈接。

我們都知道,main函數的參數argv的第一個,argv[0]為自身運行目錄路徑和程序名,這裡就根據這個條件來判斷代碼走的路徑。為何這樣做?其實完全可以再另外寫一個ueventd或watchdogd的一套程序,定義main函數啊。其實這裡原因也是很簡單,他們共享了太多東西,直接寫到一起多簡單!就像busybox或toolbox集成那麼多命令工具一樣的道理。

    /* clear the umask */
    umask(0);

清理umask,這個主要是文件權限的問題,設了umask,可以全局mask掉一些文件權限。

        /* 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);

Linux需要的dev,proc, sys等文件系統的加載。

 

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

 

 

 

 

 

 

創建一個/dev/.booting文件。 /dev是內存文件系統,不會保存的,每次開機都要重新創建。這個是指示,目前在booting過程中,具體干什麼用的,介紹ueventd的時候就清楚了。就是加載設備的fimware用的。

    open_devnull_stdio();
    klog_init();
    property_init();

這三句,分別是將 stdin,stdout和stderr先初始化到/dev/__null__,這樣用printf或其他打印的,都輸出不了,也不會引起其它異常(這個階段,其實不能用,會出錯的)。

注意這個/dev/__null__是臨時起的名字,創建node後刪了,不影響系統真正的/dev/null,這裡只需要fd即可,有了fd後,文件名就無用了,有了還會有干擾。

klog_init,是將init進程中的log,打印到內核printk的log buffer中的方法。這對調試init很有幫助,畢竟此時沒有shell,通用的log輸出方法,如printf等還不能工作,借助底層已有的內核調試功能當然是最好的了。

property的初始化也是在這裡,property讀取可以在各個用戶進程中做,但設置的入口必須是到init進程中來。在4.4上,property這塊修改了好多,現在是通過字典樹的方式存儲,可以支持更多的property屬性。

 get_hardware_name(hardware, &revision);

從 /proc/cpuinfo中讀到hardware信息,設置到ro.hardware屬性中,便於後面解析 init.${ro.hardware}.rc使用

    process_kernel_cmdline();

kernel的command參數,解析後也放到property中,供以後的子進程或其他服務等使用。

    union selinux_callback cb;
    cb.func_log = klog_write;
    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);

selinux的初始化和檢查,沒仔細研究selinux,則個檢查過程有時候還挺耗費時間的。

    INFO(property init
);
    if (!is_charger)
        property_load_boot_defaults();

這個是加載並設置properties。這些是預置的property,通常是/system/build.prop和default.prop文件中預置的那些property。

init_parse_config_file(/init.rc);

這是開始解析init.rc文件了。細節和init.rc的格式、寫法不說了,網上到處都是。主要說一下常見問題:
1. 下載代碼的服務器,umask有時候可能會有影響,init.rc文件other和group用戶是不能有寫權限的,編譯的PC的umask最好設置成0022。
2. init.rc文件嚴格來說只是配置文件,不算腳本,循環、條件判斷等等都不支持的,不要想這裡能干太多事情。

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

這是開始處理init.rc中parser好的各個命令了。

action_for_each_trigger是對此類trigger所在呃所有命令,都加入到actions的list中去。對實際代碼或項目上要全局的看一下,所有的*.rc的同一個trigger都一起處理的。

queue_builtin_action這是內建的actions,也是將actions動作加入到actions list中。

這裡需要注意的是,各個trigger的加載順序,先加入的先執行,後加入的後執行,要特別注意,尤其是要修改init.rc文件的時候,不了解這個容易因為前後依賴關系造成問題。

 

都准備好了的話,就到了服務處理階段了,這是一個死循環,主要工作就是:
1. 將init.rc及內建的actions命令,一條一條執行
2. 負責對service的管理。
3. 對signal及進程退出的處理
4. 響應property設置的請求(設置都在init中統一設置,讀取進程可以自己讀共享內存)

    for(;;) {
        int nr, i, timeout = -1;

        execute_one_command();
        restart_processes();
	...

有時候需要優化開機的話,可以測量一下execute_on_command中的命令執行時間,把較長的(比如大於50ms)的打印出來,再想辦法優化一下。

每次循環,執行一條命令,檢查是否有需要重啟的服務..

        nr = poll(ufds, fd_count, timeout);
        if (nr <= 0)
            continue;

        for (i = 0; i < fd_count; i++) {
            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();
                else if (ufds[i].fd == get_signal_fd())
                    handle_signal();
            }
        }

多路polling,當polling到東西,就執行相應的動作。

timeout時間到,執行下輪循環。init.rc中的command沒處理完的話,timeout是0,這樣之前的actions list可以一順下來都執行掉。
注意,init.rc中定義的服務,是在class_start這個command中做的。

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved