我的Android进阶修炼:安卓启动流程之init(1)

易弘阔
2023-12-01

我的Android进阶修炼:安卓启动流程之init(1)

一、前言

希望深入研究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. 是用户空间的1号进程,所有其他进程的父进程
  2. 解析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));
}

四、篇尾

未完待续……

 类似资料: