android init進程分析 基本流程
(懶人最近想起我還有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中做的。