linux啓動第一個應用程序init
linux的運行順序爲uboot傳遞參數到內核,內核的第一個c編寫的函數爲start_kernel(),start_kernel來啓動內核,最後到到rest_init()函數處完成內核啓動過程。
rest_init()中啓動第一個應用程序init,init程序的進程號爲1,Linux使用了init進程來對組成Linux的服務和應用程序進行初始化。
init進程還負責了:
Linux運行時:init進程會負責收取孤兒進程。如果某個進程創建子進程之後,在子進程終止之前終止,則子進程成爲孤兒進程。在Linux中所有的進程必須屬於單棵進程樹,所以孤立進程必須被收取。一旦進程成爲孤兒,它會立即成爲init進程的子進程。這是爲了保持進程樹的完整性。
系統關閉:init負責殺死所有其它的進程,卸載所有的文件系統並停止處理器的工作,以及任何其它被配置成要做的工作。
等等很多使命
完成linux初始化的rest_init()函數中會調用kernel_init,kernel_init函數代碼如下:
其中“/init”的來源是在kernel_init_freeable函數中賦值到ramdisk_execute_command變量中的。
try_to_run_init_process一旦執行就不會再返回到此函數中了,而是作爲linux 1號進程長期存活,直至關機時它關掉其他所有進行最後纔會退出。
android系統中linux 1號進程init的源碼在system\core\init\init.cpp(注:linux kernel源碼中沒有init進程的代碼,而是在根文件系統之中,嵌入式中一般使用了busybox中含有的init.c)
詳細的函數調用過程見下圖:
![]()
init.rc文件結構介紹
init.rc文件基本組成單位是section, section分爲三種類型,分別由三個關鍵字(所謂關鍵字即每一行的第一列)來區分,這三個關鍵字是 on、service、import。
1、on類型的section表示一系列命令的組合。當on後面的action被觸發時執行這個section後面的所有命令。如am.QueueEventTrigger("charger")。on後面也可以是properties的變化再觸發執行相關命令,如on property:sys.boot_from_charger_mode=1
2、service類型的section表示一個可執行程序
3、import類型的section表示引入另外一個.rc文件
init.rc實例:
在原生linux中我們發現init進程是讀取一個inittab的腳本而不是android中的init.rc。那這個腳本是幹啥的呢?
inttab實例
inittab爲linux初始化文件系統時init初始化程序用到的配置文件。這個文件負責設置init初始化程序初始化腳本?在哪裏;每個運行級初始化時運行的命令;?開機、關機、重啓對應的命令;各運行級登陸時所運行的命令
property是android在標準linux基礎之上新增的一種系統全局可訪問的屬性機制。和linux的環境變量機制相似,可看做是android系統的環境變量。
在android上層可以同時使用這兩種機制
linux的運行順序爲uboot傳遞參數到內核,內核的第一個c編寫的函數爲start_kernel(),start_kernel來啓動內核,最後到到rest_init()函數處完成內核啓動過程。
rest_init()中啓動第一個應用程序init,init程序的進程號爲1,Linux使用了init進程來對組成Linux的服務和應用程序進行初始化。
init進程還負責了:
Linux運行時:init進程會負責收取孤兒進程。如果某個進程創建子進程之後,在子進程終止之前終止,則子進程成爲孤兒進程。在Linux中所有的進程必須屬於單棵進程樹,所以孤立進程必須被收取。一旦進程成爲孤兒,它會立即成爲init進程的子進程。這是爲了保持進程樹的完整性。
系統關閉:init負責殺死所有其它的進程,卸載所有的文件系統並停止處理器的工作,以及任何其它被配置成要做的工作。
等等很多使命
完成linux初始化的rest_init()函數中會調用kernel_init,kernel_init函數代碼如下:
kernel\init\main.c
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_readonly();
system_state = SYSTEM_RUNNING;
numa_default_policy();
flush_delayed_fput();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d). Attempting defaults...\n",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
其中最重要的函數就是try_to_run_init_process,run_init_process切換到用戶空間,運行/init 第一個可執行程序其中“/init”的來源是在kernel_init_freeable函數中賦值到ramdisk_execute_command變量中的。
try_to_run_init_process一旦執行就不會再返回到此函數中了,而是作爲linux 1號進程長期存活,直至關機時它關掉其他所有進行最後纔會退出。
android系統中linux 1號進程init的源碼在system\core\init\init.cpp(注:linux kernel源碼中沒有init進程的代碼,而是在根文件系統之中,嵌入式中一般使用了busybox中含有的init.c)
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);
}
if (REBOOT_BOOTLOADER_ON_PANIC) {
install_reboot_signal_handlers();
}
add_environment("PATH", _PATH_DEFPATH);
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {////////////////////////////////////////////////////////////////////初始化第一階段
boot_clock::time_point start_time = boot_clock::now();
// 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.
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));
// Don't expose the raw commandline to unprivileged processes.
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
if (!DoFirstStageMount()) {
LOG(ERROR) << "Failed to mount required partitions early ...";
panic();
}
SetInitAvbVersionInRecovery();
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true);///////////////////////////////////////////////seLinux初始化
// We're in the kernel domain, so re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (restorecon("/init") == -1) {
PLOG(ERROR) << "restorecon failed";
security_failure();
}
setenv("INIT_SECOND_STAGE", "true", 1);
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);
char* path = argv[0];
char* args[] = { path, nullptr };
execv(path, args);
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(ERROR) << "execv(\"" << path << "\") failed";
security_failure();
}
// At this point we're in the second stage of init.
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";////////////////////////////////////////初始化第二階段
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1);
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline();
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
// Make the time that init started available for bootstat to log.
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
// Set libavb version for Framework-only OTA match in Treble build.
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
// Clean up our environment.
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
// Now set up SELinux for second stage.
selinux_initialize(false);
selinux_restore_context();
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
signal_handler_init();
property_load_boot_defaults();
export_oem_lock_status();
start_property_service();
set_usb_controller();
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>());
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");/////////////////////////////////讀取啓動腳本,將命令加入執行隊列
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) parser.DumpState();
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(set_kptr_restrict_action, "set_kptr_restrict");
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");////////////////////////////////觸發init.rc中的init動作,在init.rc中可以找到 “on init” section 其中的命令就會執行,後文相同
// 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 = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");///////////////////////////啓動模式爲charger則啓動關機充電,不啓動android
} else if ((strncmp(bootmode.c_str(), "ffbm-00", 7) == 0)
|| (strncmp(bootmode.c_str(), "ffbm-01", 7) == 0)) {
am.QueueEventTrigger("ffbm");//////////////////////////////啓動模式爲ffbm則進入ffbm模式
} else {
am.QueueEventTrigger("late-init");/////////////////////////其它的啓動模式都繼續之後的初始化流程,從而啓動android
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");///////////////根據當前所有properties的狀態執行相應命令
while (true) {
// By default, sleep until something happens.
int epoll_timeout_ms = -1;
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand();/////////////////////////////////////////執行加入隊列中的命令
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
restart_processes();
// If there's a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) {
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout_ms = 0;
}
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
從上面的註釋可以看到在init進程中掛載了相關文件系統;讀取並解析了init.rc腳本;根據不同的bootmode來選擇進入的模式,如果沒有特別指定模式則開啓android初始化流程。詳細的函數調用過程見下圖:
init.rc文件結構介紹
init.rc文件基本組成單位是section, section分爲三種類型,分別由三個關鍵字(所謂關鍵字即每一行的第一列)來區分,這三個關鍵字是 on、service、import。
1、on類型的section表示一系列命令的組合。當on後面的action被觸發時執行這個section後面的所有命令。如am.QueueEventTrigger("charger")。on後面也可以是properties的變化再觸發執行相關命令,如on property:sys.boot_from_charger_mode=1
2、service類型的section表示一個可執行程序
3、import類型的section表示引入另外一個.rc文件
init.rc實例:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.zygote}.rc
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
# Set the security context of /postinstall if present.
restorecon /postinstall
start ueventd
on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /default.prop /dev/urandom
on property:security.perf_harden=1
write /proc/sys/kernel/perf_event_paranoid 3
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
init.rc與inittab的關係在原生linux中我們發現init進程是讀取一個inittab的腳本而不是android中的init.rc。那這個腳本是幹啥的呢?
inttab實例
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
tty2::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
inittab的作用:inittab爲linux初始化文件系統時init初始化程序用到的配置文件。這個文件負責設置init初始化程序初始化腳本?在哪裏;每個運行級初始化時運行的命令;?開機、關機、重啓對應的命令;各運行級登陸時所運行的命令
android簡化了這部分處理直接使用init.rc腳本了。
android property和linux 環境變量property是android在標準linux基礎之上新增的一種系統全局可訪問的屬性機制。和linux的環境變量機制相似,可看做是android系統的環境變量。
在android上層可以同時使用這兩種機制
shell中查看linux中所有環境變量
env
getprop