Android init 进程的启动(Android 10)
1 系统启动流程简析
在分析 Init 进程的启动流程前,我们先来看一下Android系统开机启动流程,经历了哪些关键的步骤,Init 进程时从哪里开始被启动的。
1.1 启动电源
当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。
1.2 引导程序Bootloader
引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
1.3 Linux Kernel启动
内核kernel启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
1.4 Init进程启动
Init 进程作为 Android 系统中用户空间的第一个进程,它在整个Android系统中扮演了重要的角色,比如负责启动servicemanager(binder 服务管家)、创建 Zygote 进程(Java 进程的鼻祖)、启动属性服务、初始化 Selinux等。
1.5.1 servicemanager 启动(binder 管家)
1.5.2 Zygote 进程启动
1.6 SystemServer 进程启动
1.7 应用进程启动
2 Init 进程
Init 进程时Linux系统中用户空间的第一个进程,pid为1,在kernel启动后,会调用 /system/core/init/main.cpp的main()方法,由此启动 init 进程。
2.1 main.cpp#main()
首次进入main()函数会先调用FirstStageMain函数, 在FirstStageMain函数中,完成相关文件目录、分区的挂载后,会再通过 execv(path, const_cast<char**>(args)); 的方式,携带指定参数再次执行到这个main函数,进入selinux_setup、second_stage、subcontext等阶段。
//system/core/init/main.cpp
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
// 参数长度大于1, 在FirstStageMain函数中通过,会通过 execv(path, const_cast<char**>(args)); 的方式,携带置顶参数再次执行到这个main函数
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
// 初始化 kernel log
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap function_map;
// 4
return SubcontextMain(argc, argv, &function_map);
}
// 2.初始化 selinux
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// 3.进入 SecondStageMain
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 进入 1.FirstStageMain
return FirstStageMain(argc, argv);
}
2.2 first_stage_init.cpp#FirstStageMain()
//system/core/init/first_stage_init.cpp
int FirstStageMain(int argc, char** argv) {
// 。。。
// 创建、挂载系统相关目录并赋予权限
CHECKCALL(clearenv());
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 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.
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
if constexpr (WORLD_WRITABLE_KMSG) {
CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
}
CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
// This is needed for log wrapper, which gets called before ueventd runs.
CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
// 以下挂载操作在 first stage init 完成,以便可以挂载 /mnt/{vendor,product}/ 的子目录,
// 其他挂载则可以放在rc文件中完成
// 下面的这些挂载是在第一阶段初始化中完成的,以便first stage 可以挂载 /mnt/{vendor,product}/ 的子目录。
// first stage 不需要的其他挂载,应该在 RC 文件中完成
// 挂载的设备的暂存区域由 vold 管理
CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=1000"));
// /mnt/vendor is used to mount vendor-specific partitions that can not be
// part of the vendor partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/vendor", 0755));
// /mnt/product is used to mount product-specific partitions that can not be
// part of the product partition, e.g. because they are mounted read-write.
CHECKCALL(mkdir("/mnt/product", 0755));
// /apex is used to mount APEXes
CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
// /debug_ramdisk is used to preserve additional files from the debug ramdisk
CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"));
#undef CHECKCALL
SetStdioToDevNull(argv);
// 现在 tmpfs 挂载在 /dev 上,系统中存在 /dev/kmsg,我们现在可以与外界交流了...
// 初始化内核日志
InitKernelLogging(argv);
if (!errors.empty()) {
for (const auto& [error_string, error_errno] : errors) {
LOG(ERROR) << error_string << " " << strerror(error_errno);
}
LOG(FATAL) << "Init encountered errors starting first stage, aborting";
}
LOG(INFO) << "init first stage started!";
// 完成分区挂载工作
if (!DoFirstStageMount()) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
//
SetInitAvbVersionInRecovery();
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
// 一旦第一阶段 init 完成,它就会使用 “selinux_setup” 参数执行 /system/bin/init
// 这个步骤是选择性地编译 SELinux 并将其加载到系统上的地方。
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
execv(path, const_cast<char**>(args));
return 1;
}
DoFirstStageMount()
完成分区挂载工作
// system/core/init/first_stage_mount.cpp
bool FirstStageMount::DoFirstStageMount() {
if (!IsDmLinearEnabled() && fstab_.empty()) {
// Nothing to mount.
LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
return true;
}
if (!InitDevices()) return false;
if (!CreateLogicalPartitions()) return false;
if (!MountPartitions()) return false;
return true;
}
2.3 selinux.cpp#SetupSelinux()
执行 /system/bin/init 脚本,传递 selinux_setup作为参数
// system/core/init/selinux.cpp
int SetupSelinux(char** argv) {
InitKernelLogging(argv);
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
// 设置 SELinux,加载 SELinux 策略
SelinuxSetupKernelLogging();
SelinuxInitialize();
//使用 “second_stage” 参数执行 /system/bin/init,此时进入 second_stage
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));
return 1;
}
2.4 init.cpp#SecondStageMain()
// system/core/init/init.cpp
int SecondStageMain(int argc, char** argv) {
SetStdioToDevNull(argv);
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
// 设置init进程和它fork出来的子进程的oom_adj, init 进程的pid为1,/proc/{pid}/oom_score_adj
if (auto result = WriteFile("/proc/1/oom_score_adj", "-1000"); !result) {
LOG(ERROR) << "Unable to write -1000 to /proc/1/oom_score_adj: " << result.error();
}
// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
GlobalSeccomp();
// 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_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);
// See if need to load debug props to allow adb root, when the device is unlocked.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
// Clean up our environment.
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
unsetenv("INIT_FORCE_DEBUGGABLE");
// Now set up SELinux for second stage.
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();
Epoll epoll;
if (auto result = epoll.Open(); !result) {
PLOG(FATAL) << result.error();
}
InstallSignalFdHandler(&epoll);
property_load_boot_defaults(load_debug_prop);
UmountDebugRamdisk();
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
// 启动属性服务 PropertyService
StartPropertyService(&epoll);
MountHandler mount_handler(&epoll);
set_usb_controller();
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
subcontexts = InitializeSubcontexts();
// 开始实例化 ActionManager、ServiceList,用于处理 rc文件中的关键字操作
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 加载rc启动脚本
LoadBootScripts(am, sm);
// 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) DumpState();
// Make the GSI status available before scripts start running.
if (android::gsi::IsGsiRunning()) {
property_set("ro.gsid.image_running", "1");
} else {
property_set("ro.gsid.image_running", "0");
}
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
// 执行 rc 文件中触发器为 on early-init 的语句
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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return Success();
},
"KeychordInit");
am.QueueBuiltinAction(console_init_action, "console_init");
// 执行 rc 文件中触发器为 on init 的语句
am.QueueEventTrigger("init");
// Starting the BoringSSL self test, for NIAP certification compliance.
am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
// 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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
// 在启动其他系统服务之前初始化binder程序
am.QueueBuiltinAction(InitBinder, "InitBinder");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
// 执行 rc 文件中触发器为 on charger 的语句
am.QueueEventTrigger("charger");
} else {
// 执行 rc 文件中触发器为 on late-init 的语句
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) {
// By default, sleep until something happens.
auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
if (do_shutdown && !shutting_down) {
do_shutdown = false;
if (HandlePowerctlMessage(shutdown_command)) {
shutting_down = true;
}
}
if (!(waiting_for_prop || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || Service::is_exec_service_running())) {
if (!shutting_down) {
auto next_process_action_time = HandleProcessActions();
// If there's a process that needs restarting, wake up in time for that.
if (next_process_action_time) {
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
*next_process_action_time - boot_clock::now());
if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
}
}
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout = 0ms;
}
// 等待事件发生
if (auto result = epoll.Wait(epoll_timeout); !result) {
LOG(ERROR) << result.error();
}
}
return 0;
}
SelabelInitialize()
StartPropertyService()
LoadBootScripts()
QueueEventTrigger
epoll.Wait
通过上面的流程,我们可以得知 init 进程的主要工作可以分为4个阶段,主要完成了以下几件事:
- 创建一些文件夹并挂载设备、分区
- 设置 SElinux 机制
- 初始化和启动属性服务
- 解析init.rc配置文件(触发脚本,如启动zygote进程)
- 响应rc文件中配置的相关事件
3 init .rc 文件
# system/core/rootdir/init.rc
# inport 引入其他rc文件
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Set the security context of /adb_keys if present.
restorecon /adb_keys
# Set the security context of /postinstall if present.
restorecon /postinstall
mkdir /acct/uid
# 省略。。。
start ueventd
exec_start apexd-bootstrap
on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /system/etc/prop.default /dev/urandom
# Start logd before any other services run to ensure we capture all of their logs.
start logd
# Start essential services.
# 启动 servicemanager、hwservicemanager、vndservicemanager
start servicemanager
start hwservicemanager
start vndservicemanager
# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
on load_persist_props_action
load_persist_props
start logd
start logd-reinit
# Indicate to fw loaders that the relevant mounts are up.
on firmware_mounts_complete
rm /dev/.booting
# 挂载文件系统和启动核心服务
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-data
trigger load_persist_props_action
trigger zygote-start
trigger firmware_mounts_complete
trigger early-boot
trigger boot
on early-fs
start vold
on post-fs
# 省略
# 建议从后 fs 数据中放置不必要的数据/初始化
# 在设备的 init.rc 中启动zygote以解锁zygote启动。
on zygote-start && property:ro.crypto.state=unencrypted
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=unsupported
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
# A/B 升级验证,标记是否成功开机
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
# IPsec SA default expiration length
write /proc/sys/net/core/xfrm_acq_expires 3600
# Memory management.
write /proc/sys/vm/overcommit_memory 1
write /proc/sys/vm/min_free_order_shift 4
chown root system /sys/module/lowmemorykiller/parameters/adj
chmod 0664 /sys/module/lowmemorykiller/parameters/adj
chown root system /sys/module/lowmemorykiller/parameters/minfree
chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
# System server manages zram writeback
chown root system /sys/block/zram0/idle
chmod 0664 /sys/block/zram0/idle
chown root system /sys/block/zram0/writeback
chmod 0664 /sys/block/zram0/writeback
# Tweak background writeout
write /proc/sys/vm/dirty_expire_centisecs 200
write /proc/sys/vm/dirty_background_ratio 5
chown system system /sys/class/leds/vibrator/trigger
chown system system /sys/class/leds/vibrator/activate
chown system system /sys/class/leds/vibrator/brightness
chown system system /sys/class/leds/vibrator/duration
chown system system /sys/class/leds/vibrator/state
chown system system /sys/class/timed_output/vibrator/enable
chown system system /sys/class/leds/keyboard-backlight/brightness
chown system system /sys/class/leds/lcd-backlight/brightness
chown system system /sys/class/leds/button-backlight/brightness
chown system system /sys/class/leds/jogball-backlight/brightness
chown system system /sys/class/leds/red/brightness
chown system system /sys/class/leds/green/brightness
chown system system /sys/class/leds/blue/brightness
chown system system /sys/class/leds/red/device/grpfreq
chown system system /sys/class/leds/red/device/grppwm
chown system system /sys/class/leds/red/device/blink
chown system system /sys/module/sco/parameters/disable_esco
chown system system /sys/kernel/ipv4/tcp_wmem_min
chown system system /sys/kernel/ipv4/tcp_wmem_def
chown system system /sys/kernel/ipv4/tcp_wmem_max
chown system system /sys/kernel/ipv4/tcp_rmem_min
chown system system /sys/kernel/ipv4/tcp_rmem_def
chown system system /sys/kernel/ipv4/tcp_rmem_max
chown root radio /proc/cmdline
# Define default initial receive window size in segments.
setprop net.tcp.default_init_rwnd 60
# Start standard binderized HAL daemons
class_start hal
class_start core
on nonencrypted
class_start main
class_start late_start
on property:sys.init_log_level=*
loglevel ${sys.init_log_level}
on charger
class_start charger
on property:vold.decrypt=trigger_load_persist_props
load_persist_props
start logd
start logd-reinit
on property:vold.decrypt=trigger_post_fs_data
trigger post-fs-data
trigger zygote-start
on property:vold.decrypt=trigger_restart_min_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start main
on property:vold.decrypt=trigger_restart_framework
# A/B update verifier that marks a successful boot.
exec_start update_verifier
class_start_post_data hal
class_start_post_data core
class_start main
class_start late_start
setprop service.bootanim.exit 0
start bootanim
on property:vold.decrypt=trigger_shutdown_framework
class_reset late_start
class_reset main
class_reset_post_data core
class_reset_post_data hal
on property:sys.boot_completed=1
bootchart stop
# Setup per_boot directory so other .rc could start to use it on boot_completed
exec - system system -- /bin/rm -rf /data/per_boot
mkdir /data/per_boot 0700 system system
# system server cannot write to /proc/sys files,
# and chown/chmod does not work for /proc/sys/ entries.
# So proxy writes through init.
on property:sys.sysctl.extra_free_kbytes=*
write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
# "tcp_default_init_rwnd" Is too long!
on property:sys.sysctl.tcp_def_init_rwnd=*
write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}
on property:security.perf_harden=0
write /proc/sys/kernel/perf_event_paranoid 1
write /proc/sys/kernel/perf_event_max_sample_rate ${debug.perf_event_max_sample_rate:-100000}
write /proc/sys/kernel/perf_cpu_time_max_percent ${debug.perf_cpu_time_max_percent:-25}
write /proc/sys/kernel/perf_event_mlock_kb ${debug.perf_event_mlock_kb:-516}
on property:security.perf_harden=1
write /proc/sys/kernel/perf_event_paranoid 3
# on shutdown
service ueventd /system/bin/ueventd
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
service console /system/bin/sh
class core
console
disabled
user shell
group shell log readproc
seclabel u:r:shell:s0
setenv HOSTNAME 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
# Give reads to anyone for the window trace folder on debug builds.
chmod 0775 /data/misc/wmtrace
start console
service flash_recovery /system/bin/install-recovery.sh
class main
oneshot
4 rc文件语法解析
总结
init 进程(pid=1)是 Linux 系统中用户空间的第一个进程,主要工作如下:
- 创建一块共享的内存空间,用于属性服务器;
- 解析各个 rc 文件,并启动相应属性服务进程;
- 初始化 epoll,依次设置 signal、property、keychord 这 3 个 fd 可读时相对应的回调函数;
- 进入无限循环状态,执行如下流程:
- 检查 action_queue 列表是否为空,若不为空则执行相应的 action;
- 检查是否需要重启的进程,若有则将其重新启动;
- 进入 epoll_wait 等待状态,直到系统属性变化事件(property_set 改变属性值),或者收到子进程的信号 SIGCHLD,再或者 keychord 键盘输入事件,则会退出等待状态,执行相应的回调函数。
可见 init 进程在开机之后的核心工作就是响应 property 变化事件和回收僵尸进程。当某个进程调用 property_set 来改变一个系统属性值时,系统会通过 socket 向 init 进程发送一个 property 变化的事件通知,那么 property fd 会变成可读,init 进程采用 epoll 机制监听该 fd 则会触发回调 handle_property_set_fd()方法。
回收僵尸进程,在 Linux 内核中,如父进程不等待子进程的结束直接退出,会导致 子进程在结束后变成僵尸进程,占用系统资源。为此,init 进程专门安装了 SIGCHLD 信号接收器,当某些子进程退出时发现其父进程已经退出,则会向 init 进程发送 SIGCHLD 信号,init 进程调用回调方法 handle_signal()来回收僵尸子 进程。
6 参考链接
Android 开机解析
Android 系统启动流程
init.rc 文件语法解析
Android framework 精编内核解析