上篇中講到linux核心啟動與android啟動的銜接是linux核心根據約定,在啟動的最後執行android的init進程。init進程主要工作是首先是解析init.rc等設定檔,之後充當property service。本文收集兩篇文章,能夠很好的理解init的邏輯和init.rc設定檔的文法和使用方法。
1. init程式邏輯
文章引用地址:一篇pdf文檔,來自百度文庫
Android 源碼分析 --
(一) Android 啟動過程
royalxw@gmail.com
1. 源碼檔案路徑:
platform/system/core/init/init.c
0) 這個代碼檔案主要用於實現 Android 系統中 init 進程 (init 進程為 Android 系統中用
戶空間啟動的第一個進程,其作用相當於 Linux 系統中的 init 進程)
NOTE: 如果調用此檔案產生的可執行檔的第一個參數為"ueventd",
那麼此檔案
將實現 Android 系統中的 "ueventd" 進程。
1) 在進行編譯後,此檔案產生的可執行程式名稱為"init",同時會產生一個指向此文
件的軟連結: "ueventd"
int main(int argc, char **argv)
{
2) 基於 C 語言的風格,在函數入口處聲明一些後續會使用的變數。
int fd_count = 0;
struct pollfd ufds[4];
char *tmpdev;
char* debuggable;
char tmp[32];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
3) 如果執行此檔案產生的可執行程式的方式類似於: "ueventd xxx"。也即是基於執行
此 文 件 對 應 的 軟 鏈 接 : /sbin/ueventd 時 會 調 用 "ueventd_main" 函 數 , 進 而 生
成"ueventd" 進程。
4) Android 系統中的 ueventd 進程用於實現使用者態進程與核心進行資料通訊。
5) 在 Android 系統的 init.rc 檔案: platform/system/core/rootdir/init.rc 中通過如下方式
來啟動 ueventd 進程:
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16
start ueventd
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
6)
產生 Android 系統中的一些基本的系統目錄並掛載對應的檔案系統。
/* clear the umask */
umask(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.
*/
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);
/* indicate that booting is in progress to background fw loaders, etc
*/
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
/* 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.
*/
7) 產生 "/dev/__null__" 虛擬設備(類似於 Linux 系統中的 /dev/null 裝置)並將
stdin/stdout/stderr 三個檔案重新導向到 "/dev/__null__"
open_devnull_stdio();
8)
產生 " /dev/__kmsg__" 虛擬設備用於記錄 log。
Klog_init 實現檔案: system/core//libcutils/klog.c
klog_init();
INFO("reading config file\n");
9)
解析 init.rc (platform/system/core/rootdir/init.rc)。
init_parse_config_file("/init.rc");
/* pull the kernel commandline and ramdisk properties file in */
10) 從 "/proc/cmdline" 中讀取核心命令列參數,
對應函數實現路徑: platform/system/core/init/util.c
import_kernel_cmdline(0, import_kernel_nv);
11) 在第 10 步讀取完 /proc/cmdline 中的參數後,修改此檔案的許可權,禁止非授權
使用者操作此檔案。
/* don't expose the raw commandline to nonpriv processes */
chmod("/proc/cmdline", 0440);
12) 從 "/proc/cpuinfo" 中讀取系統的 CPU 硬體資訊。
對應函數實現路徑: platform/system/core/init/util.c
get_hardware_name(hardware, &revision);
13) 基於第 12 步中讀取的硬體資訊來解析特定於硬體的配置資訊。
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);
14) 基 於 "early-init","property_init","keychord_init","console_init" 標 識 , 使 用 "
queue_builtin_action"來過濾上述操作中解析的 init.rc 檔案中的 action,
並將符合
條 件 的 action 添 加 到 對 應 的
action_queue 中 , 然 後 調 用 "
action_for_each_trigger"來運行這些 actions(實際上是 action 在 list 中的分類移
動操作)。
對應函數實現路徑: platform/system/core/init/init_parser.c
基於下面的運行邏輯可以看出,運行"init.rc"中的 action 的順序為:
"early-init" -> "init" -> "early-boot" -> "boot"
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(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action,
"set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0)
{
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
action_for_each_trigger("post-fs-data", action_add_queue_tail);
}
queue_builtin_action(property_service_init_action,
"property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
if (!strcmp(bootmode, "charger"))
{
action_for_each_trigger("charger", action_add_queue_tail);
}
else
{
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
}
/* run all property triggers based on current state of the properties
*/
queue_builtin_action(queue_property_triggers_action,
"queue_propety_triggers");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
15) "init" 進程開始進行"迴圈事件處理"邏輯。
for (;;)
{
int nr, i, timeout = -1;
16) 運行第 14 步操作中所分類整理的 action_queue 中對應的 action。
execute_one_command();
17) 查看當前的服務進程狀態,如果有服務進程退出,重啟對應服務進程。
restart_processes();
18) 基於 property_service 的事件控制代碼填充 poll event 結構體,用於後續 poll 操作。
if (!property_set_fd_init && get_property_set_fd() > 0)
{
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
19) 處理當前 init 進程上接收到的 signal,主要是處理 SIGCHLD。
if (!signal_fd_init && get_signal_fd() > 0)
{
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
20) 基於 keychord service 的事件控制代碼填充 poll event 結構體,用於後續 poll 操作。
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;
}
21) 如果有所監控的子進程退出,此時 init 進程需要負責重新啟動這些退出的服務進
程,因此下面調用 poll 時的 timeout 設定為 0,這樣就不會因為 poll 在無事件激發時而
阻塞導致當前的 init 進程對"重啟服務進程"處理的的延遲,從而可以儘快恢複退出的
服務進程。
if (process_needs_restart)
{
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
22) 如果當前的 action_queue 中有需要處理的 action, 麼下面調用 poll 時的 timeout
那
設定為 0,這樣就不會因為 poll 在無事件激發時而阻塞導致當前的 init 進程對 action
處理的的延遲,從而提高 init 進程對 action 處理的即時性。
if (!action_queue_empty() || cur_action)
timeout = 0;
23) 關於" BOOTCHART"參數的解釋:
/*
* 如果在編譯選項中添加了 BOOTCHART 參數,那麼意味著在系統的啟動
* 過程中需要產生 bootchart(http://www.bootchart.org/),用於後續
* Android 啟動過程中的效能分析並產生系統啟動過程的可視圖表。
* makefile 中的編譯選項如下:
ifeq ($(strip $(INIT_BOOTCHART)),true)
LOCAL_SRC_FILES += bootchart.c
LOCAL_CFLAGS
endif
+= -DBOOTCHART=1
bootchart 的實現檔案: platform/system/core/init/bootchart.c
*
#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
24) 類似於網路伺服器開發中常見的基於"poll"機制來檢測所關注的控制代碼上是否有指定
的事件激發。
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++)
{
25) 如果當前所關注的事件控制代碼上有事件發生,進行對應的事件處理。
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();
}
}
}
return 0;
}
2. init.rc類設定檔解析
文章引用地址:一篇pdf文檔,來自百度文庫
Android 的初始化語言指令碼 init.rc 解析
Android init.rc (Android init language)
Android 初始化語言由四大類聲明組成:行為類(Actions),命令類(Commands),服務類
(Services),選項類(Options)。
1) 初始化語言以行為單位,由以空格間隔的語言符號組成。C 風格的反斜線轉義符可以
用來插入空白到語言符號。雙引號也可以用來防止文本被空格分成多個語言符號。當
反斜線在行末時,作為折行符。
2) 以#開始(前面允許有空格)的行為注釋行。
3) Actions 和 Services 隱含聲明一個新的段落。所有該段落下 Commands 或 Options 的
聲明屬於該段落。第一段落前的 Commands 或 Options 被忽略。
4) Actions 和 Services 擁有獨一無二的命名。在它們之後聲明相同命名的類將被當作
錯誤並忽略。
行為類(Actions)
Actions 是一系列命令的命名。
Actions 擁有一個觸發器(trigger)用來決定 action 何時執行。當一個 action 在符合觸發
條件被執行時,如果它還沒被加入到待執行隊列中的話,則加入到隊列最後。
隊列中的 action 依次執行,action 中的命令也依次執行。Init 在執行命令的中間處理其它
活動(裝置建立/銷毀,property 設定,進程重啟)。
Actions 表現形式為:
on <trigger>
<command>
<command>
<command>
服務類(Services)
Services 是由 init 啟動,在它們退出時重啟(可選)。
Service 表現形式為:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
選項類(Options)
Options 是 Services 的修飾,它們影響 init 何時、如何運行 service.
PDF 檔案使用 "pdfFactory Pro" 試用版本建立 www.fineprint.cn
critical
這是一個裝置關鍵服務(device-critical service) .如果它在 4 分鐘內退出超過 4
次,裝置將重啟並進入復原模式。
disabled
這個服務的層級將不會自動啟動,它必須被依照服務名指定啟動才可以啟動。
setenv <name> <value>
設定已啟動的進程的環境變數<name>的值<value>
socket <name> <type> <perm> [ <user> [ <group> ] ]
建立一個名為/dev/socket/<name>的 unix domin socket,並傳送它的 fd 到已啟動的
進程。<type>必須為"dgram"或"stream".使用者和組預設為 0.
user <username>
在執行服務前改變使用者名稱。當前預設為 root.如果你的進程需要 linux 能力,你不能
使用這個命令。你必須在還是 root 時請求能力,並下降到你需要的 uid.
group <groupname> [ <groupname> ]*
在執行服務前改變組。在第一個組後的組將設為進程附加組(通過 setgroups()).當前
預設為 root.
oneshot
在服務退出後不重啟。
class <name>
為 service 指定一個類別名。同樣類名的所有的服務可以一起啟動或停止。如果沒有
指定類別的服務預設為"default"類。
onrestart
當服務重啟時執行一個命令。
PDF 檔案使用 "pdfFactory Pro" 試用版本建立 www.fineprint.cn
Triggers
Triggers(觸發器)是一個字串,可以用來匹配某種類型的事件並執行一個 action。
boot
這是當 init 開始後執行的第一個觸發器(當/init.conf 被載入)
<name>=<value>
當 property <name>被設為指定的值<value>時觸發。
device-added-<path>
device-removed-<path>
當裝置節點被添加或移除時觸發。
service-exited-<name>
當指定的服務存在時觸發
命令類(Commands)
exec <path> [ <argument> ]*
Fork 並執行一個程式(<path>).這將被 block 直到程式執行完畢。最好避免執行例如
內建命令以外的程式,它可能會導致 init 被阻塞不動。
export <name> <value>
設定全域環境變數<name>的值<value>,當這個命令執行後所有的進程都可以取得。
ifup <interface>
使網路介面<interface>聯機。
import <filename>
解析一個 init 設定檔,擴充當前設定檔。
hostname <name>
設定主機名稱
chmod <octal-mode> <path>
改變檔案存取權限
chown <owner> <group> <path>
改變檔案所屬和組
class_start <serviceclass>
當指定類別的服務沒有運行,啟動該類別所有的服務。
class_stop <serviceclass>
當指定類別的服務正在運行,停止該類別所有的服務。
domainname <name>
設定網域名稱。
insmod <path>
載入該路徑<path>的模組
mkdir <path> [mode] [owner] [group]
在<path>建立一個目錄,可選選項:mod,owner,group.如果沒有指定,目錄以 755 許可權,
owner 為 root,group 為 root 建立.
mount <type> <device> <dir> [ <mountoption> ]*
嘗試 mount <device>到目錄<dir>. <device>可以用 mtd@name格式以命名指定一個 mtd
塊裝置。<mountoption>包含"ro","rw","remount","noatime".
setkey
PDF 檔案使用 "pdfFactory Pro" 試用版本建立 www.fineprint.cn
暫時沒有
setprop <name> <value>
設定系統 property <name>的值<value>.
setrlimit <resource> <cur> <max>
設定 resource 的 rlimit.
start <service>
啟動一個沒有啟動並執行服務。
stop <service>
停止一個正在啟動並執行服務。
symlink <target> <path>
建立一個<path>的符號連結到<target>
sysclktz <mins_west_of_gmt>
設定系統時區(GMT 為 0)
trigger <event>
觸發一個事件。用於調用其它 action。
write <path> <string> [ <string> ]*
開啟<path>的檔案並寫入一個或多個字串。
Properties
Init 會更新一些系統 property 以提供查看它正在幹嘛。
init.action
當前正在執行的 action,如果沒有則為""
PDF 檔案使用 "pdfFactory Pro" 試用版本建立 www.fineprint.cn
init.command
被執行的命令,如果沒有則為""
init.svc.<name>
命名為<name>的服務的狀態("stopped", "running", "restarting")
init.rc 樣本:
# not complete -- just providing some examples of usage
#
on boot
export PATH /sbin:/system/sbin:/system/bin
export LD_LIBRARY_PATH /system/lib
mkdir /dev
mkdir /proc
mkdir /sys
mount tmpfs tmpfs /dev
mkdir /dev/pts
mkdir /dev/socket
mount devpts devpts /dev/pts
mount proc proc /proc
mount sysfs sysfs /sys
write /proc/cpu/alignment 4
ifup lo
hostname localhost
domainname localhost
mount yaffs2 mtd@system /system
mount yaffs2 mtd@userdata /data
import /system/etc/init.conf
class_start default
service adbd /sbin/adbd
user adb
group adb
service usbd /system/bin/usbd –r
user usbd
group usbd
socket usbd 666
service zygote /system/bin/app_process -Xzygote /system/bin –zygote
socket zygote 666
service runtime /system/bin/runtime
user system
group system
on device-added-/dev/compass
start akmd
on device-removed-/dev/compass
stop akmd
service akmd /sbin/akmd
disabled
user akmd
group akmd
調試
預設情況下,init 執行的程式輸出的資訊和錯誤到/dev/null.為了 debug,你可以通過
Android 程式 logwrapper 執行你的程式。這將複位向輸出/錯誤輸出到 Android logging 系
統(通過 logcat 訪問)。
例如
service akmd /system/bin/logwrapper /sbin/akmd
本文完...