一、前言
希望深入研究Android系统,却也一直找不到合适的方向,所以仿照大神做法,同样,也以1号init进程为主轴,开始修炼吧!
- 基于AOSP Android 9.0:android-9.0.0_r60
- 调试平台:模拟器
二、init进程简介
1.文件位置
Main.cpp (system\core\init) 2594 2022/5/12
2.主要功能
- 是用户空间的1号进程,所有其他进程的父进程
- 解析init.rc文件,按脚本创建、挂载各种目录,启动各种服务
三、init进程源码分析
3.1 main() 源码注解
- system\core\init\Main.cpp
init\Main.cpp编译后的文件名为init,而int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
// 参考:3.1.1
// 设置当前进程的优先级为 -20,了解Android OOM机制的同学应该大致知道,优先级为负值是很高的优先级,基本不会被OOM killer(LMK)杀死。
setpriority(PRIO_PROCESS, 0, -20);
// 参考:3.1.2
// init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//如果参数大于1个,即至少2个及以上,则执行下面if块的代码
//argc 大于1,根据上文提要,至少有2种情况:1. ./init subcontext 2. ./ueventd subcontext
if (argc > 1) {
if (!strcmp(argv[1], "subcontext")) {
// 参考:3.1.3
// 初始化日志系统
android::base::InitLogging(argv, &android::base::KernelLogger);
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
// 参考:3.1.4
// 跳转到SubcontextMain
return SubcontextMain(argc, argv, &function_map);
}
// 参考:3.1.5
// 跳转到SetupSelinux
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
// 参考:3.3
// 本文重点:SecondStageMain,进入到init的第二阶段
if (!strcmp(argv[1], "second_stage")) {
return SecondStageMain(argc, argv);
}
}
// 参考:3.2
// 本文重点:FirstStageMain,进入到init的第一阶段
return FirstStageMain(argc, argv);
}
3.1.1 参考:setpriority
# define PRIO_PROCESS 0 //进程
# define PRIO_PGRP 1 //进程组
# define PRIO_USER 2 //用户进程
/**
*Linux setpriority系统调用用于设置进程,进程组,用户进程的优先级,修改进程的nice值, nice值越小,进程的优先级越高。
*
*当which为PRIO_PROCESS时,如果参数who为0,则设置当前进程的进程优先级;如果参数who不为0,则设置进程号为who的进程的优先级。
*/
long setpriority(int which,int who,int niceval)
3.1.2 参考:ueventd
- ueventd的主要工作,是通过两种方式创建设备节点文件:1.冷插拔(例如各板载设备);2.热插拔(如U盘)
// init\Main.cpp编译后的文件名为init,而ueventd是指向init的一个软连接。
// 当执行软连接./ueventd 的时候,实际执行的是init文件,而从大学C语言学习可知,argv[0]即所执行文件的文件名:ueventd。
// 这是个非常巧妙的写法,当检测到执行的是 ./ueventd 的时候,即跳转到 ueventd_main ()的实现中
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
3.1.3 参考:InitLogging
// Configure logging based on ANDROID_LOG_TAGS environment variable.
// We need to parse a string that looks like
//
// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
//
// The tag (or '*' for the global level) comes first, followed by a colon and a
// letter indicating the minimum priority level we're expected to log. This can
// be used to reveal or conceal logs with specific tags.
#ifdef __ANDROID__
#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger()
#else
#define INIT_LOGGING_DEFAULT_LOGGER StderrLogger
#endif
void InitLogging(char* argv[],
LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER,
AbortFunction&& aborter = DefaultAborter);
#undef INIT_LOGGING_DEFAULT_LOGGER
3.1.4 参考:SubcontextMain
return SubcontextMain(argc, argv, &function_map);
3.1.5 参考:selinux_setup
if (!strcmp(argv[1], "selinux_setup")) {
return SetupSelinux(argv);
}
3.2 FirstStageMain() 源码注解
- 字面意思,一目了然,此为init的第一阶段,那啥是第一阶段该干的活呢?
- init进程第一阶段做的主要工作是挂载所需分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略
int FirstStageMain(int argc, char** argv) {
// 参考: 3.2.1
// init崩溃时候重启系统:只有userdebug 和 eng 版本的固件中, REBOOT_BOOTLOADER_ON_PANIC才会等于1
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
boot_clock::time_point start_time = boot_clock::now();
std::vector<std::pair<std::string, int>> errors;
#define CHECKCALL(x) \
if ((x) != 0) errors.emplace_back(#x " failed", errno);
// Clear the umask.
// 参考: 3.2.2
// 权限掩码清0,创建文件(含目录等特殊文件)时,将使用默认权限
umask(0);
// 清除环境变量设定
CHECKCALL(clearenv());
// 参考:3.2.3 设置环境变量PATH
CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 关键部分:从initramdisk中,获取基础的文件系统配置,然后让rc文件解决剩下的问题
// 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.
// 挂载一个基于内存的分区,挂载目录为 /dev, 并开始在/dev下创建一系列文件,包括设备节点,
CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
CHECKCALL(mkdir("/dev/pts", 0755));
CHECKCALL(mkdir("/dev/socket", 0755));
CHECKCALL(mkdir("/dev/dm-user", 0755));
// 挂载devpts远程虚拟终端文件设备,文件夹里面一般是一些字符设备文件
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
// 参考:3.2.4
// 读取内核的配置参数
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
// Don't expose the raw bootconfig to unprivileged processes.
// 参考:3.2.5
// 读取Android 用户空间的配置参数
chmod("/proc/bootconfig", 0440);
std::string bootconfig;
android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
// 将当前进程添加到 AID_READPROC 进程组,从而拥有读取进程文件系统的权限
gid_t groups[] = {AID_READPROC};
CHECKCALL(setgroups(arraysize(groups), groups));
// 下面继续挂载所需的fs,创建所需的节点和目录
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)));
// These below mounts are done in first stage init so that first stage mount can mount
// subdirectories of /mnt/{vendor,product}/. Other mounts, not required by first stage mount,
// should be done in rc files.
// Mount staging areas for devices managed by vold
// See storage config details at http://source.android.com/devices/storage/
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));
// /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"));
// /second_stage_resources is used to preserve files from first to second
// stage init
CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"))
#undef CHECKCALL
// 参考:3.2.6
// 将标准输入、输出、错误重定向到/dev/null
SetStdioToDevNull(argv);
// 从此处开始,就可以看到打印信息了
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
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!";
auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
if (!old_root_dir) {
PLOG(ERROR) << "Could not opendir(\"/\"), not freeing ramdisk";
}
struct stat old_root_info;
if (stat("/", &old_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0;
boot_clock::time_point module_start_time = boot_clock::now();
int module_count = 0;
if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
module_count)) {
if (want_console != FirstStageConsoleParam::DISABLED) {
LOG(ERROR) << "Failed to load kernel modules, starting console";
} else {
LOG(FATAL) << "Failed to load kernel modules";
}
}
if (module_count > 0) {
auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
boot_clock::now() - module_start_time);
setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
LOG(INFO) << "Loaded " << module_count << " kernel modules took "
<< module_elapse_time.count() << " ms";
}
bool created_devices = false;
if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
if (!IsRecoveryMode()) {
created_devices = DoCreateDevices();
if (!created_devices){
LOG(ERROR) << "Failed to create device nodes early";
}
}
StartConsole(cmdline);
}
if (access(kBootImageRamdiskProp, F_OK) == 0) {
std::string dest = GetRamdiskPropForSecondStage();
std::string dir = android::base::Dirname(dest);
std::error_code ec;
if (!fs::create_directories(dir, ec) && !!ec) {
LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message();
}
if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) {
LOG(FATAL) << "Can't copy " << kBootImageRamdiskProp << " to " << dest << ": "
<< ec.message();
}
LOG(INFO) << "Copied ramdisk prop to " << dest;
}
// If "/force_debuggable" is present, the second-stage init will use a userdebug
// sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
if (access("/force_debuggable", F_OK) == 0) {
std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
!fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
LOG(ERROR) << "Failed to setup debug ramdisk";
} else {
// setenv for second-stage init to read above kDebugRamdisk* files.
setenv("INIT_FORCE_DEBUGGABLE", "true", 1);
}
}
if (ForceNormalBoot(cmdline, bootconfig)) {
mkdir("/first_stage_ramdisk", 0755);
// SwitchRoot() must be called with a mount point as the target, so we bind mount the
// target directory to itself here.
if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
}
SwitchRoot("/first_stage_ramdisk");
}
if (!DoFirstStageMount(!created_devices)) {
LOG(FATAL) << "Failed to mount required partitions early ...";
}
struct stat new_root_info;
if (stat("/", &new_root_info) != 0) {
PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
old_root_dir.reset();
}
if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
}
SetInitAvbVersionInRecovery();
setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
1);
const char* path = "/system/bin/init";
const char* args[] = {path, "selinux_setup", nullptr};
auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
// 以selinux_setup为参数,启动1号进程
// 即:/system/bin/init selinux_setup
execv(path, const_cast<char**>(args));
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(FATAL) << "execv(\"" << path << "\") failed";
return 1;
}
3.2.1 参考:REBOOT_BOOTLOADER_ON_PANIC
通过查找字符串,可知REBOOT_BOOTLOADER_ON_PANIC 在 init 的根目录的MK 文件中定义。
在编译为:userdebug 和 eng 版本的固件中,不会打开该选项。
# ./system/core/init/Android.mk:14
#
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += \
-DALLOW_FIRST_STAGE_CONSOLE=1 \
-DALLOW_LOCAL_PROP_OVERRIDE=1 \
-DALLOW_PERMISSIVE_SELINUX=1 \
-DREBOOT_BOOTLOADER_ON_PANIC=1 \
-DWORLD_WRITABLE_KMSG=1 \
-DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
-DALLOW_FIRST_STAGE_CONSOLE=0 \
-DALLOW_LOCAL_PROP_OVERRIDE=0 \
-DALLOW_PERMISSIVE_SELINUX=0 \
-DREBOOT_BOOTLOADER_ON_PANIC=0 \
-DWORLD_WRITABLE_KMSG=0 \
-DDUMP_ON_UMOUNT_FAILURE=0
endif
- 主要作用是:当 init 进程崩溃时,重启 bootloader,算是为了方便调试吧。
- install_reboot_signal_handlers 函数将各种信号量,如 SIGABRT、SIGBUS 等的行为设置为 SA_RESTART,一旦监听到这些信号即执行重启系统。
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
3.2.2 参考:umask(0)
- 作用:umask设定创建文件时候的权限掩码;
- 定义函数: mode_t umask(mode_t mask);
- 函数说明: 例如,在建立文件时默认文件权限为0666,通常umask值默认为 022,则该文件的真正权限则为0666&~022=0644,也就是rw-r–r–。
// Clear the umask.
// 创建文件时使用默认权限,不再额外设限
umask(0);
3.2.3 参考:_PATH_DEFPATH
- _PATH_DEFPATH 的定义在 Paths.h (bionic\libc\include) 2525 2022/5/16
- 看到下面的定义,是不是开始熟悉init在干什么事情了 :)
/** Default shell search path. */
#define _PATH_DEFPATH "/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin"
3.2.4 参考:/proc/cmdline
- 存放了kernel的启动参数,是由bootloader启动kernel时传入的
- 从 Android 12开始,这里面只存放和kernel相关的参数,和android专用的上层环境相关的部分被挪移到新的变量/proc/bootconfig中
- 对比示例:下面分别使用andorid 12和android 11的模拟器,可见andorid 12的内核启动参数已经减少了很多
Android 12 的 cmdline
emulator64_x86_64_arm64:/ # cat /proc/cmdline
stack_depot_disable=on cgroup_disable=pressure cgroup.memory=nokmem no_timer_check clocksource=pit console=0 cma=288M@0-4G ndns=4 loop.max_part=7 ramoops.mem_address=0xff018000 ramoops.mem_size=0x10000 memmap=0x10000$0xff018000 prin
tk.devkmsg=on bootconfig ndns=4 mac80211_hwsim.radios=0
emulator64_x86_64_arm64:/ #
Android 11 的 cmdline
- 可见如官方原文描述,之前的版本中,在内核参数中混入了许多用户空间参数,例如 androidboot.vbmeta.size
generic_x86:/ # cat /proc/cmdline
no_timer_check clocksource=pit console=0 cma=288M@0-4G ndns=4 mac80211_hwsim.channels=2 loop.max_part=7 ramoops.mem_address=0xff018000 ramoops.mem_size=0x10000 memmap=0x10000$0xff018000 printk.devkmsg=on qemu=1 androidboot.hardware=ranchu androidboot.serialno=EMULATOR31X2X8X0 qemu.gles=1 qemu.settings.system.screen_off_timeout=2147483647 qemu.encrypt=1 qemu.vsync=60 qemu.gltransport=pipe qemu.gltransport.drawFlushInterval=800 qemu.opengles.version=131072 qemu.dalvik.vm.heapsize=512m qemu.camera_protocol_ver=1 qemu.camera_hq_edge_processing=0 androidboot.vbmeta.size=6144 androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.digest=ea5843921b6671f2d851ebf15e7b55f71e73fc36967778bca3be3e3cf4e15f28 androidboot.boot_devices=pci0000:00/0000:00:03.0 qemu.wifi=1 qemu.hwcodec.avcdec=2 qemu.hwcodec.vpxdec=2 android.qemud=1 qemu.avd_name=Pixel_2_API_30 ndns=4 mac80211_hwsim.radios=0
generic_x86:/ #
3.2.5 参考:/proc/bootconfig
官方解释:在 Android 12 中,bootconfig 功能取代了 Android 11 及更低版本中使用的
androidboot.*
内核命令行选项。 bootconfig 功能是一种将配置详细信息从构建和引导加载程序传递到 Android 12 的机制。此功能提供了一种将 Android 用户空间的配置参数与内核的配置参数分开的方法。将冗长的
androidboot.*
内核参数移动到 bootconfig 文件会在内核 cmdline 上创建空间,并使其可用于将来的扩展。说人话,就是读取Android 用户空间的配置参数, 把原来放在/proc/cmdline内的一些专属于android的配置,单独拿出来放到/proc/bootconfig
emulator64_x86_64_arm64:/ # cat /proc/bootconfig
androidboot.qemu = "1"
androidboot.qemu.cpuvulkan.version = "4202496"
androidboot.qemu.settings.system.screen_off_timeout = "2147483647"
androidboot.qemu.vsync = "60"
androidboot.qemu.gltransport.name = "pipe"
androidboot.qemu.gltransport.drawFlushInterval = "800"
androidboot.qemu.adb.pubkey = "QAAAAGmCOckn0styCNC22zGZCLweCnuYNoL//HHIwzDjI5y81wQ87MaYtaJuJWdTujKIaQrxz6Jx8vVeasge40/yNCUiLHW4GCI2krn0mM41JJbGPMewb2TjnMUX+m8ORzqVuGm+kCFVNDFPQd+ID9sL/vihVmfMij1N3obU4F1YuoC7964PuJT7sqVJVIxUwD3AbNRmh
v4zLSQoriStqTI2baeR6lcjeDOs/O1DqtnopB7BetGLqvF+aGC+huEai6VfgLsEqUlSPA1Ce5saF4u0vDypxmm6ax6w2q2D3BwCV5qphRg30Nelxb9pj7NvxSybJdVVkZSydM9zoitv8hJAFudUL1y+JeUeWH5iard/0vWp6iEBYnzmqKMlouIJBdXXkYRo/hQrXs/EDGYCrDEJe/vQdA2b3zbsvVDAxdc7tFpnU
46/c1Jjl5QLh+eReaHXmWASuuyIqD7WdeyBTfBeh1LRyAvqpjo8+qDP9t3MoIEXKjCgi/vi8lo5TFZQZ0FJ7XG1/ddtVzRH2bmAbVKQHR+rrRJFuUPUFSi3pamHjVkqAW7geY07gdD2V5RrxpO2NvbqdUgeGg7qmD6C5c1Pguija6r0/egIgaxUb0wIX0QLpCtqLqMv0Ed4cWg5ixZP4np+hI/u2EWyRgK8wi5Hi
jXwDux2JTMphw4PZeEvzbrLyhH5CgEAAQA= @unknown"
androidboot.qemu.camera_protocol_ver = "1"
androidboot.qemu.camera_hq_edge_processing = "0"
androidboot.qemu.virtiowifi = "1"
androidboot.qemu.hwcodec.avcdec = "2"
androidboot.qemu.hwcodec.vpxdec = "2"
androidboot.qemu.avd_name = "Pixel_2_API_31"
androidboot.hardware = "ranchu"
androidboot.serialno = "EMULATOR31X2X8X0"
androidboot.veritymode = "enforcing"
androidboot.opengles.version = "131072"
androidboot.logcat = "*:V"
androidboot.dalvik.vm.heapsize = "512m"
androidboot.vbmeta.size = "6272"
androidboot.vbmeta.hash_alg = "sha256"
androidboot.vbmeta.digest = "ea7166a14990ff9f4a2c9ed2cc7e869d4cde33137c4531a187242c84f42032a0"
androidboot.boot_devices = "pci0000:00/0000:00:03.0"
emulator64_x86_64_arm64:/ #
3.2.6 参考:SetStdioToDevNull
(1) SetStdioToDevNull
- 将标准输入(STDIN_FILENO)、输出(STDOUT_FILENO)、错误(STDERR_FILENO)重定向到 /dev/null
// The kernel opens /dev/console and uses that fd for stdin/stdout/stderr if there is a serial
// console enabled and no initramfs, otherwise it does not provide any fds for stdin/stdout/stderr.
// SetStdioToDevNull() is used to close these existing fds if they exist and replace them with
// /dev/null regardless.
//
// In the case that these fds are provided by the kernel, the exec of second stage init causes an
// SELinux denial as it does not have access to /dev/console. In the case that they are not
// provided, exec of any further process is potentially dangerous as the first fd's opened by that
// process will take the stdin/stdout/stderr fileno's, which can cause issues if printf(), etc is
// then used by that process.
//
// Lastly, simply calling SetStdioToDevNull() in first stage init is not enough, since first
// stage init still runs in kernel context, future child processes will not have permissions to
// access any fds that it opens, including the one opened below for /dev/null. Therefore,
// SetStdioToDevNull() must be called again in second stage init.
void SetStdioToDevNull(char** argv) {
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/dev/null", O_RDWR); // NOLINT(android-cloexec-open)
if (fd == -1) {
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
errno = saved_errno;
PLOG(FATAL) << "Couldn't open /dev/null";
}
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);
}
(2) dup2
int dup2(int old_fd, int new_fd) {
// If old_fd is equal to new_fd and a valid file descriptor, dup2 returns
// old_fd without closing it. This is not true of dup3, so we have to
// handle this case ourselves.
if (old_fd == new_fd) {
if (fcntl(old_fd, F_GETFD) == -1) {
return -1;
}
return old_fd;
}
return FDTRACK_CREATE(__dup3(old_fd, new_fd, 0));
}
四、篇尾
未完待续……