Android12源码—— init进程

启动BootLoader

在CPU上电复位完成后,会从一个固定的地址加载一段程序,即BootLoader,不同的CPU可能这个地址不同。BootLoader是一段引导程序,其中最为常见的为U-boot,它一般会先检测用户是否按下某些特别按键,这些特别按键是uboot在编译时预先被约定好的,用于进入调试模式。如果用户没有按这些特别的按键,则uboot会从NAND Flash中装载Linux内核,装载的地址是在编译uboot时预先约定好的。

idle , init, kthreadd 进程

idle 进程 Linux内核启动后,便会创建第一个进程idle。idle进程是Linux中的第一个进程,pid为0,是唯一一个没有通过fork产生的进程,它的优先级非常低,用于CPU没有任务的时候进行空转。

init 进程 init进程由idle进程创建,是Linux系统的第一个用户进程,pid为1,是系统所有用户进程的直接或间接父进程,本篇重点讲的就是它。

kthreadd 进程 kthreadd进程同样由idle进程创建,pid为2,它始终运行在内核空间,负责所有内核线程的调度与管理。

kernel 启动init

kernel_init启动后,完成一些init的初始化操作,然后去系统根目录下依次找ramdisk_execute_commandexecute_command设置的应用程序,如果这两个目录都找不到,就依次去根目录下找 /sbin/init/etc/init/bin/init,/bin/sh 这四个应用程序进行启动,只要这些应用程序有一个启动了,其他就不启动了。

代码位置:kernel-5.10/init/main.c

scss 复制代码
static char *ramdisk_execute_command = "/init";

static int __ref kernel_init(void *unused)
{
    ...// 省略部分代码
	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);
	}

	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).", execute_command, ret);
	}

	if (CONFIG_DEFAULT_INIT[0] != '\0') {
		ret = run_init_process(CONFIG_DEFAULT_INIT);
		if (ret)
			pr_err("Default init %s failed (error %d)\n",CONFIG_DEFAULT_INIT, ret);
		else
			return 0;
	}

	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/admin-guide/init.rst for guidance.");
}

上面代码可以看到ramdisk_execute_command指向了系统根目录下init文件,系统根目录下的init是软连接,实际指向system/bin/init

init 进程流程图

init进程

Android的init进程代码在system/core/init/main.cpp中,以main方法作为入口

scss 复制代码
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
    // 设置当前进程的优先级为 -20,了解Android OOM机制的同学应该大致知道,基本不会被OOM killer(LMK)杀死。
    setpriority(PRIO_PROCESS, 0, -20);
    
    //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);
    }
    
    //如果参数大于1个,即至少2个及以上,则执行下面if块的代码
    //argc 大于1,根据上文提要,至少有2种情况:1. ./init subcontext   2. ./ueventd subcontext
    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
            return SubcontextMain(argc, argv, &function_map);
        }
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
    return FirstStageMain(argc, argv);
}

优先级setpriority

bash 复制代码
/**
*Linux setpriority系统调用用于设置进程,进程组,用户进程的优先级,修改进程的nice值,nice值越小,进程的优先级越高。
*当which为PRIO_PROCESS时,如果参数who为0,则设置当前进程的进程优先级;如果参数who不为0,则设置进程号为who的进程的优先级。
*/
long setpriority(int which,int who,int niceval)

Android 中定义

arduino 复制代码
enum __priority_which
{
#define PRIO_PROCESS 0 //进程
#define PRIO_PGRP 1 //进程组
#define PRIO_USER 2 //用户进程
};

Linux OOM Killer机制 Linux下有一种 OOM KILLER 的机制,它会在系统内存耗尽的情况下,启用自己算法有选择性的杀掉一些进程,这个算法和三个值有关:

bash 复制代码
/proc/PID/oom_score ,OOM 最终得分,值越大越有可能被杀掉
/proc/PID/oom_score_adj ,取值范围为-1000到1000,计算oom_score时会加上该参数
/proc/PID/oom_adj ,取值是-17到+15,该参数主要是为兼容旧版内核

在init过程中,代码设置了init进程和以后fork出来的进程的OOM等级,这里的值为-1000,设置为这个值就可以保证进程永远不会因为OOM被杀死

FirstStageMain

从上面代码中可以看到默认启动不加任何参数时,会执行到最下面第一阶段FirstStageMain函数中, 下面看一下FirstStageMain函数的实现。 FirstStageMain函数代码在system/core/init/first_stage_init.cpp中,

下面先看一段FirstStageMain 函数中前面一部分代码
less 复制代码
int FirstStageMain(int argc, char** argv) {

    //init崩溃时候重启系统:只有userdebug 和 eng //版本的固件中,REBOOT_BOOTLOADER_ON_PANIC才会等于1
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    
    umask(0);
    //清除环境变量
    CHECKCALL(clearenv());
    // 设置环境变量  #define	_PATH_DEFPATH	"/usr/bin:/bin"
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    
    // 挂载目录为 /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

    // 读取内核配置参数 , 可以到root 设备 /proc/cmdline 位置查看一下
    CHECKCALL(chmod("/proc/cmdline", 0440));
    std::string cmdline;
    android::base::ReadFileToString("/proc/cmdline", &cmdline);
    
    // 读取Android 用户空间的配置参数。 可以到root 设备 /proc/bootconfig 位置查看一下
    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)));
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    CHECKCALL(mkdir("/mnt/product", 0755));
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
    CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"))
#undef CHECKCALL

    SetStdioToDevNull(argv);
    InitKernelLogging(argv);

    ...// 省略部分代码
}

从上面函数的内容可以知道上面这段代码主要进行创建目录、设备节点,挂载操作。 下面介绍函数中的几个方法:

umask(0); 这个方法是用来设置创建目录或文件时所应该赋予权限的掩码 Linux中,文件默认最大权限是666,目录最大权限是777,当创建目录时,假设掩码为022,那赋予它的权限为(777 & ~022)= 755 在执行init第一阶段时,先执行umask(0),使创建的目录或文件的默认权限为最高

SetStdioToDevNull(argv); 由于Linux内核打开了/dev/console作为标准输入输出流(stdin/stdout/stderr)的文件描述符,而init进程在用户空间,无权访问/dev/console,后续如果执行printf的话可能会导致错误,所以先调用SetStdioToDevNull函数来将标准输入输出流(stdin/stdout/stderr)用/dev/null文件描述符替换 /dev/null被称为空设备,是一个特殊的设备文件,它会丢弃一切写入其中的数据,读取它会立即得到一个EOF

SetStdioToDevNull函数在system/core/init/util.cpp

scss 复制代码
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";
    }
    // 将标准输入(STDIN_FILENO)、输出(STDOUT_FILENO)、错误(STDERR_FILENO)重定向到 /dev/null
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    if (fd > STDERR_FILENO) close(fd);
}

dup2函数 ,把指定的newfd也指向oldfd指向的文件,也就是说,执行完dup2之后,有newfd和oldfd同时指向同一个文件,共享文件偏移量和文件状态。 dup2 函数介绍:www.cnblogs.com/love-jelly-...

InitKernelLogging(argv); 接着调用InitKernelLogging函数,初始化了一个简单的kernel日志系统

arduino 复制代码
void InitKernelLogging(char** argv) {
    SetFatalRebootTarget();
    android::base::InitLogging(argv, &android::base::KernelLogger, InitAborter);
}
下面在看FirstStageMain函数中后面一部分代码
scss 复制代码
int FirstStageMain(int argc, char** argv) {
    ...// 省略部分代码
    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;
    // 加载kernel模块
    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";
        }
    }
    ...// 省略部分代码
    
    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 (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 ...";
    }

    ...// 省略部分代码
}

从上面这一段代码主要是创建设备,挂载分区 工作. 在FirstStageMain 函数中LoadKernelModules 来完成kernel 模块的加载的。 下面看一下LoadKernelModules 方法的具体实现:

c 复制代码
#define MODULE_BASE_DIR "/lib/modules"
bool LoadKernelModules(bool recovery, bool want_console, int& modules_loaded) {
    
    ....// 省略部分代码
    // 打开kernel模块目录
    std::unique_ptr<DIR, decltype(&closedir)> base_dir(opendir(MODULE_BASE_DIR), closedir);
    if (!base_dir) {
        LOG(INFO) << "Unable to open /lib/modules, skipping module loading.";
        return true;
    }
    // 遍历模块信息 , 添加到module_dirs
    dirent* entry;
    std::vector<std::string> module_dirs;
    while ((entry = readdir(base_dir.get()))) {
        if (entry->d_type != DT_DIR) {
            continue;
        }
        int dir_major, dir_minor;
        if (sscanf(entry->d_name, "%d.%d", &dir_major, &dir_minor) != 2 || dir_major != major ||
            dir_minor != minor) {
            continue;
        }
        module_dirs.emplace_back(entry->d_name);
    }

    std::sort(module_dirs.begin(), module_dirs.end());

    for (const auto& module_dir : module_dirs) {
        std::string dir_path = MODULE_BASE_DIR "/";
        dir_path.append(module_dir);
        // 载入模块
        Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
        bool retval = m.LoadListedModules(!want_console);
        modules_loaded = m.GetModuleCount();
        if (modules_loaded > 0) {
            return retval;
        }
    }
    // 载入模块
    Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(recovery, MODULE_BASE_DIR));
    bool retval = m.LoadListedModules(!want_console);
    modules_loaded = m.GetModuleCount();
    if (modules_loaded > 0) {
        return retval;
    }
    return true;
}

modprobe命令 modprobe可载入指定的个别模块,或是载入一组相依的模块。modprobe会根据depmod所产生的相依关系,决定要载入哪些模块。若在载入过程中发生错误,在modprobe会卸载整组的模块。

ForceNormalBoot 函数来完判断是否需要强制重启

c 复制代码
bool ForceNormalBoot(const std::string& cmdline, const std::string& bootconfig) {
    return bootconfig.find("androidboot.force_normal_boot = \"1\"") != std::string::npos ||
           cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
}

在这个方法中会去判断cmdlinebootconfig 配置文件中是否有androidboot.force_normal_boot=1 这个字段,如果返回true表示强制重启,否则不重启, 这个正常启动都是返回false

DoFirstStageMount函数在次完成驱动的挂载

scss 复制代码
bool DoFirstStageMount(bool create_devices) {
    if (IsRecoveryMode()) {
        LOG(INFO) << "First stage mount skipped (recovery mode)";
        return true;
    }

    auto fsm = FirstStageMount::Create();
    if (!fsm.ok()) {
        LOG(ERROR) << "Failed to create FirstStageMount " << fsm.error();
        return false;
    }

    if (create_devices) {
        if (!(*fsm)->DoCreateDevices()) return false;
    }

    return (*fsm)->DoFirstStageMount();
}
剩下FirstStageMain中最后一段代码
scss 复制代码
int FirstStageMain(int argc, char** argv) {
    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);
    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";
}

通过execv函数带参执行init文件,进入SetupSelinux 用exec系列函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID。

这里在末尾直接打log是因为,exec系列函数如果执行正常是不会返回的,所以只要执行到下面就代表exec执行出错了

SetupSelinux

下面看一下SetupSelinux函数的实现。 SetupSelinux函数代码在system/core/init/selinux.cpp

scss 复制代码
int SetupSelinux(char** argv) {
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    boot_clock::time_point start_time = boot_clock::now();

    MountMissingSystemPartitions();
    SelinuxSetupKernelLogging();

    std::string policy;
    ReadPolicy(&policy);

    auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
    if (snapuserd_helper) {
        snapuserd_helper->StartTransition();
    }

    LoadSelinuxPolicy(policy);
    if (snapuserd_helper) {
        snapuserd_helper->FinishTransition();
        snapuserd_helper = nullptr;
    }

    SelinuxSetEnforcement();
    if (selinux_android_restorecon("/system/bin/init", 0) == -1) {
        PLOG(FATAL) << "restorecon failed of /system/bin/init failed";
    }

    setenv(kEnvSelinuxStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(), 1);

    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));

    PLOG(FATAL) << "execv(\"" << path << "\") failed";
    return 1;
}

上面代码启动Selinux安全机制,初始化selinux,加载SELinux规则,配置SELinux相关log输出,并启动第二阶段:SecondStageMain

需要注意一下在函数中调用MountMissingSystemPartitions函数挂载之前没有挂载的系统分区

这里不对SELinux 内容过多介绍,感兴趣可以研究一下

SecondStageMain

下面看一下SecondStageMain函数的实现。 SecondStageMain函数代码在system/core/init/init.cpp

c 复制代码
int SecondStageMain(int argc, char** argv) {
    //init崩溃时候重启系统:只有userdebug 和 eng //版本的固件中,REBOOT_BOOTLOADER_ON_PANIC才会等于1
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    
    ....// 省略部分代码
    // 初始化日志
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";

    ....// 省略部分代码

    // Set init and its forked children's oom_adj.
    //设置init进程和以后fork出来的进程的OOM等级,这里的值为-1000,保证进程不会因为OOM被杀死
    if (auto result =
                WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
        !result.ok()) {
        LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
                   << " to /proc/1/oom_score_adj: " << result.error();
    }

    ....// 省略部分代码
    // Indicate that booting is in progress to background fw loaders, etc.
    //设置一个标记,代表正在启动过程中
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

    ....// 省略部分代码
    
    //初始化系统属性
    PropertyInit();


    // Mount extra filesystems required during second stage init
    //挂载额外的文件系统
    MountExtraFilesystems();

    // Now set up SELinux for second stage.
    //设置SELinux
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();

    //使用epoll,注册信号处理函数,守护进程服务
    Epoll epoll;
    if (auto result = epoll.Open(); !result.ok()) {
        PLOG(FATAL) << result.error();
    }

    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
     //启动系统属性服务
    StartPropertyService(&property_fd);

    ....// 省略部分代码

    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
    MountHandler mount_handler(&epoll);
    SetUsbController();
    SetKernelVersion();

    //设置commands指令所对应的函数map
    const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
    Action::set_function_map(&function_map);

    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }

    InitializeSubcontext();

    //解析init.rc脚本
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

    LoadBootScripts(am, sm);
    
    ....// 省略部分代码
    //构建了一些Action,Trigger等事件对象加入事件队列中
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    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(SetMmapRndBitsAction, "SetMmapRndBits");
    Keychords keychords;
    am.QueueBuiltinAction(
            [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
                for (const auto& svc : ServiceList::GetInstance()) {
                    keychords.Register(svc->keycodes());
                }
                keychords.Start(&epoll, HandleKeychord);
                return {};
            },
            "KeychordInit");

    // Trigger all the boot actions to get us started.
    am.QueueEventTrigger("init");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }

    // Run all property triggers based on current state of the properties.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    // Restore prio before main loop
    setpriority(PRIO_PROCESS, 0, 0);
    while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

        auto shutdown_command = shutdown_state.CheckShutdown();
        if (shutdown_command) {
            LOG(INFO) << "Got shutdown_command '" << *shutdown_command
                      << "' Calling HandlePowerctlMessage()";
            HandlePowerctlMessage(*shutdown_command);
            shutdown_state.set_do_shutdown(false);
        }

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!IsShuttingDown()) {
            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;
            }
        }
        
        //执行从init.rc脚本解析出来的每条指令
        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }

        auto pending_functions = epoll.Wait(epoll_timeout);
        if (!pending_functions.ok()) {
            LOG(ERROR) << pending_functions.error();
        } else if (!pending_functions->empty()) {
            // We always reap children before responding to the other pending functions. This is to
            // prevent a race where other daemons see that a service has exited and ask init to
            // start it again via ctl.start before init has reaped it.
            ReapAnyOutstandingChildren();
            for (const auto& function : *pending_functions) {
                (*function)();
            }
        }
        if (!IsShuttingDown()) {
            HandleControlMessages();
            SetUsbController();
        }
    }

    return 0;
}

上面第二阶段代码主要是解析init.rc文件,按照init.rc 中的指令执行。

解析init.rc脚本

rc文件,是用Android Init Language编写的特殊文件。用这种语法编写的文件,统一用".rc"后缀 它的语法说明可以在aosp源码system/core/init/README.md中找到,这里就简单说明一下语法规则

Actions

Actions是一系列命令的开始,一个Action会有一个触发器,用于确定Action何时执行。当一个与Action的触发器匹配的事件发生时,该动作被添加到待执行队列的尾部 格式如下:

xml 复制代码
on <trigger> [&& <trigger>]* 
    <command> 
    <command> 
    <command>
Triggers(触发器)

触发器作用于Actions,可用于匹配某些类型的事件,并用于导致操作发生

Commands

Commands就是一个个命令的集合了 Action,Triggers, Commands共同组成了一个单元,举个例子:

sql 复制代码
on zygote-start && property:ro.crypto.state=unencrypted 
    # A/B update verifier that marks a successful boot. 
    exec_start update_verifier_nonencrypted 
    start statsd 
    start netd 
    start zygote 
    start zygote_secondary
Services

Services是对一些程序的定义,格式如下:

xml 复制代码
service <name> <pathname> [ <argument> ]*
    <option>
    <option>
    ...

其中:

  • name:定义的服务名
  • pathname:这个程序的路径
  • argument:程序运行的参数
  • option:服务选项,后文将介绍
Options

Options是对Services的修饰,它们影响着服务运行的方式和时间 Services, Options组成了一个单元,举个例子:

perl 复制代码
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    task_profiles ProcessCapacityHigh MaxPerformance
Imports

导入其他的rc文件或目录解析,如果path是一个目录,目录中的每个文件都被解析为一个rc文件。它不是递归的,嵌套的目录将不会被解析。

arduino 复制代码
import <path>

Imports的内容会放到最后解析 上文所述的CommandsOptions等具体命令,可以网上搜索一下,或者自己看system/core/init/README.md Commands的定义可以在system/core/init/builtins.cpp中找到 Options的定义可以在system/core/init/service_parser.cpp中找到

init.rc 介绍

init.rc 在源码目录system/core/rootdir下, 看一下init文件中内容:

kotlin 复制代码
import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc

会看到import /vendor/etc/init/hw/init.${ro.hardware}.rc 这一行,同时发现目录中有init.zygote32.rc,init.zygote64.rc,init.zygote64_32.rc

可以通过下面命令查看设备是多少位:

bash 复制代码
$adb shell getprop ro.zygote
zygote64_32

init.rc 解析

上面简单介绍了rc 文件的语法,下面看一下rc 文件是在哪里进行解析的。 在SecondeStageMain函数中执行LoadBootScripts函数来解析rc文件

scss 复制代码
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        // late_import is available only in Q and earlier release. As we don't
        // have system_ext in those versions, skip late_import for system_ext.
        parser.ParseConfig("/system_ext/etc/init");
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }

这个函数会从这些地方寻找rc文件解析,/system/etc/init/hw/init.rc是主rc文件,剩下的目录,如果system分区尚未挂载的话,就把它们加入到late_import_paths中,等到后面mount_all时再加载 主rc文件在编译前的位置为system/core/rootdir/init.rc

简单分析一下:

首先,以ActionManagerServiceList作为参数创建了一个Parser解析器,解析后的结果会存放在ActionManagerServiceList中,这里的两个传进来的参数都是单例模式

arduino 复制代码
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
                                               &service_list, GetSubcontext(), std::nullopt));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

先创建了一个Parser对象,然后往里面添加了ServiceParserActionParser以及ImportParser,这三个类都是继承自ServiceParser,这里的std::make_unique是new了一个对象,并用其原始指针构造出了一个智能指针

接着走到Parser::ParseConfig方法中:

c 复制代码
bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {
        return ParseConfigDir(path);
    }
    return ParseConfigFile(path);
}

判断是否是目录,如果是目录,就把目录中的所有文件加入容器中排序后依次解析 详细解析方式这里不做过多介绍。

执行rc文件中的任务

SecondStageMain中,最后面可以看到有一个死循环,是用来等待事件处理

scss 复制代码
while (true) {
    // By default, sleep until something happens.
    auto epoll_timeout = std::optional<std::chrono::milliseconds>{};

    ....// 省略部分代码
    //执行从init.rc脚本解析出来的每条指令
    if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
        am.ExecuteOneCommand();
    }
    ....// 省略部分代码
    if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
        // If there's more work to do, wake up again immediately.
        if (am.HasMoreCommands()) epoll_timeout = 0ms;
    }

    auto pending_functions = epoll.Wait(epoll_timeout);
    if (!pending_functions.ok()) {
        LOG(ERROR) << pending_functions.error();
    } else if (!pending_functions->empty()) {
        // We always reap children before responding to the other pending functions. This is to
        // prevent a race where other daemons see that a service has exited and ask init to
        // start it again via ctl.start before init has reaped it.
        ReapAnyOutstandingChildren();
        for (const auto& function : *pending_functions) {
            (*function)();
        }
    }
    ....// 省略部分代码
}

其中am.ExecuteOneCommand()方法便是执行从rc文件中解析出来的指令 ExecuteOneCommand函数在system/core/init/action_manager.cpp 文件中

c 复制代码
void ActionManager::ExecuteOneCommand() {
    {
        auto lock = std::lock_guard{event_queue_lock_};
        // Loop through the event queue until we have an action to execute
          //当前正在执行的action队列为空,但等待执行的事件队列不为空
        while (current_executing_actions_.empty() && !event_queue_.empty()) {
            for (const auto& action : actions_) {
                //从等待执行的事件队列头取出一个元素event, 
                //然后调用action的CheckEvent检查此event是否匹配当前action 
                //如果匹配,将这个action加入到正在执行的actions队列的队尾
                if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
                               event_queue_.front())) {
                    current_executing_actions_.emplace(action.get());
                }
            }
            event_queue_.pop();
        }
    }

    // 如果当前没有执行的action 结束
    if (current_executing_actions_.empty()) {
        return;
    }
    //从队列头取一个action(front不会使元素出队)
    auto action = current_executing_actions_.front();
    //如果是第一次执行这个action
    if (current_command_ == 0) {
        std::string trigger_name = action->BuildTriggersString();
        LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
                  << ":" << action->line() << ")";
    }
     //这个current_command_是个成员变量,标志着执行到了哪一行
    action->ExecuteOneCommand(current_command_);

    // If this was the last command in the current action, then remove
    // the action from the executing list.
    // If this action was oneshot, then also remove it from actions_.
    ++current_command_;
    //current_command_等于action的commands数量,说明这个action以及全部执行完了
    if (current_command_ == action->NumCommands()) {
        current_executing_actions_.pop();
         //重置计数器
        current_command_ = 0;
        if (action->oneshot()) {
            auto eraser = [&action](std::unique_ptr<Action>& a) { return a.get() == action; };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser),
                           actions_.end());
        }
    }
}

在上面的代码中调用action->ExecuteOneCommand(current_command_) 执行一个任务,这里需要注意一下这个函数是system/core/init/action.cpp 中的函数,看一下具体实现

c 复制代码
void Action::ExecuteOneCommand(std::size_t command) const {
    // We need a copy here since some Command execution may result in
    // changing commands_ vector by importing .rc files through parser
    Command cmd = commands_[command];
    ExecuteCommand(cmd);
}

void Action::ExecuteCommand(const Command& command) const {
    android::base::Timer t;
    //这一行是具体的执行
    auto result = command.InvokeFunc(subcontext_);
    auto duration = t.duration();

    // Any action longer than 50ms will be warned to user as slow operation
    //失败、超时或者debug版本都需要打印结果
    if (!result.has_value() || duration > 50ms ||
        android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
        std::string trigger_name = BuildTriggersString();
        std::string cmd_str = command.BuildCommandString();

        LOG(INFO) << "Command '" << cmd_str << "' action=" << trigger_name << " (" << filename_
                  << ":" << command.line() << ") took " << duration.count() << "ms and "
                  << (result.ok() ? "succeeded" : "failed: " + result.error().message());
    }
}

上面代码中调用command.InvokeFunc(subcontext_); 函数执行,这个函数实现在 system/core/init/action.cpp 文件中

scss 复制代码
Result<void> Command::InvokeFunc(Subcontext* subcontext) const {
    //从 /vendor 或 /oem 解析出来的rc文件都会走这里 
    //涉及到selinux权限问题,Google为了保证安全 
    //队对厂商定制的rc文件中的命令执行,以及由此启动的服务的权限都会有一定限制
    if (subcontext) {
        if (execute_in_subcontext_) {
            return subcontext->Execute(args_);
        }

        auto expanded_args = subcontext->ExpandArgs(args_);
        if (!expanded_args.ok()) {
            return expanded_args.error();
        }
        return RunBuiltinFunction(func_, *expanded_args, subcontext->context());
    }
    //系统原生的rc文件命令都会走这里
    return RunBuiltinFunction(func_, args_, kInitContext);
}

看到这个地方需要注意一下变量 func_, 这个函数是Command类的一个实现,那么func_这个变量是Command类的一个变量,看一下Command 类结构

c 复制代码
class Command {
  public:
    Command(BuiltinFunction f, bool execute_in_subcontext, std::vector<std::string>&& args,
            int line);

    Result<void> InvokeFunc(Subcontext* subcontext) const;
    std::string BuildCommandString() const;
    Result<void> CheckCommand() const;

    int line() const { return line_; }

  private:
    BuiltinFunction func_;
    bool execute_in_subcontext_;
    std::vector<std::string> args_;
    int line_;
};

通过阅读Command 结构可以知道func_BuiltinFunction, 那么BuiltinFunction 什么, BuiltinFunction定义在system/core/init/builtins.h文件下, 下面看一下

arduino 复制代码
using BuiltinFunction = std::function<Result<void>(const BuiltinArguments&)>;

这是一个函数,记住它,后面会再次提到。

下面看一下系统原生rc文件执行的函数RunBuiltinFunction, 这个函数实现在 system/core/init/action.cpp 文件中

c 复制代码
Result<void> RunBuiltinFunction(const BuiltinFunction& function,
                                const std::vector<std::string>& args, const std::string& context) {
    auto builtin_arguments = BuiltinArguments(context);

    builtin_arguments.args.resize(args.size());
    builtin_arguments.args[0] = args[0];
    for (std::size_t i = 1; i < args.size(); ++i) {
        auto expanded_arg = ExpandProps(args[i]);
        if (!expanded_arg.ok()) {
            return expanded_arg.error();
        }
        builtin_arguments.args[i] = std::move(*expanded_arg);
    }

    return function(builtin_arguments);
}

通过上面代码可知这里的function是一个以BuiltinArguments为参数的std::function函数包装器模板,可以包装函数、函数指针、类成员函数指针或任意类型的函数对象,在Command对象new出来的时候构造函数就指定了这个func_,我们可以看一下Action::AddCommand方法:

c 复制代码
Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
    if (!function_map_) {
        return Error() << "no function map available";
    }

    //从function_map_中进行键值对查找
    auto map_result = function_map_->Find(args);
    if (!map_result.ok()) {
        return Error() << map_result.error();
    }

    commands_.emplace_back(map_result->function, map_result->run_in_subcontext, std::move(args),
                           line);
    return {};
}

可以看到,是通过rc文件中的字符串去一个function_map_常量中查找得到的,而这个function_map_是在哪赋值的呢,答案是在SecondStageMain函数中

arduino 复制代码
int SecondStageMain(int argc, char** argv) {
    ....// 省略无关代码
    //设置commands指令所对应的函数map
    const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
    Action::set_function_map(&function_map);
    ....// 省略无关代码
}

通过GetBuiltinFunctionMap函数获取commads 指令对应的map, 下面看一下GetBuiltinFunctionMap 的实现,GetBuiltinFunctionMapsystem/core/init/builtins.cpp文件中

c 复制代码
const BuiltinFunctionMap& GetBuiltinFunctionMap() {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const BuiltinFunctionMap builtin_functions = {
        {"bootchart",               {1,     1,    {false,  do_bootchart}}},
        {"chmod",                   {2,     2,    {true,   do_chmod}}},
        {"chown",                   {2,     3,    {true,   do_chown}}},
        {"class_reset",             {1,     1,    {false,  do_class_reset}}},
        {"class_reset_post_data",   {1,     1,    {false,  do_class_reset_post_data}}},
        {"class_restart",           {1,     1,    {false,  do_class_restart}}},
        {"class_start",             {1,     1,    {false,  do_class_start}}},
        {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},
        {"class_stop",              {1,     1,    {false,  do_class_stop}}},
        {"copy",                    {2,     2,    {true,   do_copy}}},
        {"copy_per_line",           {2,     2,    {true,   do_copy_per_line}}},
        {"domainname",              {1,     1,    {true,   do_domainname}}},
        {"enable",                  {1,     1,    {false,  do_enable}}},
        {"exec",                    {1,     kMax, {false,  do_exec}}},
        {"exec_background",         {1,     kMax, {false,  do_exec_background}}},
        {"exec_start",              {1,     1,    {false,  do_exec_start}}},
        {"export",                  {2,     2,    {false,  do_export}}},
        {"hostname",                {1,     1,    {true,   do_hostname}}},
        {"ifup",                    {1,     1,    {true,   do_ifup}}},
        {"init_user0",              {0,     0,    {false,  do_init_user0}}},
        {"insmod",                  {1,     kMax, {true,   do_insmod}}},
        {"installkey",              {1,     1,    {false,  do_installkey}}},
        {"interface_restart",       {1,     1,    {false,  do_interface_restart}}},
        {"interface_start",         {1,     1,    {false,  do_interface_start}}},
        {"interface_stop",          {1,     1,    {false,  do_interface_stop}}},
        {"load_exports",            {1,     1,    {false,  do_load_exports}}},
        {"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},
        {"load_system_props",       {0,     0,    {false,  do_load_system_props}}},
        {"loglevel",                {1,     1,    {false,  do_loglevel}}},
        {"mark_post_data",          {0,     0,    {false,  do_mark_post_data}}},
        {"mkdir",                   {1,     6,    {true,   do_mkdir}}},
        // TODO: Do mount operations in vendor_init.
        // mount_all is currently too complex to run in vendor_init as it queues action triggers,
        // imports rc scripts, etc.  It should be simplified and run in vendor_init context.
        // mount and umount are run in the same context as mount_all for symmetry.
        {"mount_all",               {0,     kMax, {false,  do_mount_all}}},
        {"mount",                   {3,     kMax, {false,  do_mount}}},
        {"perform_apex_config",     {0,     0,    {false,  do_perform_apex_config}}},
        {"umount",                  {1,     1,    {false,  do_umount}}},
        {"umount_all",              {0,     1,    {false,  do_umount_all}}},
        {"update_linker_config",    {0,     0,    {false,  do_update_linker_config}}},
        {"readahead",               {1,     2,    {true,   do_readahead}}},
        {"remount_userdata",        {0,     0,    {false,  do_remount_userdata}}},
        {"restart",                 {1,     1,    {false,  do_restart}}},
        {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
        {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
        {"rm",                      {1,     1,    {true,   do_rm}}},
        {"rmdir",                   {1,     1,    {true,   do_rmdir}}},
        {"setprop",                 {2,     2,    {true,   do_setprop}}},
        {"setrlimit",               {3,     3,    {false,  do_setrlimit}}},
        {"start",                   {1,     1,    {false,  do_start}}},
        {"stop",                    {1,     1,    {false,  do_stop}}},
        {"swapon_all",              {0,     1,    {false,  do_swapon_all}}},
        {"enter_default_mount_ns",  {0,     0,    {false,  do_enter_default_mount_ns}}},
        {"symlink",                 {2,     2,    {true,   do_symlink}}},
        {"sysclktz",                {1,     1,    {false,  do_sysclktz}}},
        {"trigger",                 {1,     1,    {false,  do_trigger}}},
        {"verity_update_state",     {0,     0,    {false,  do_verity_update_state}}},
        {"wait",                    {1,     2,    {true,   do_wait}}},
        {"wait_for_prop",           {2,     2,    {false,  do_wait_for_prop}}},
        {"write",                   {2,     2,    {true,   do_write}}},
    };
    // clang-format on
    return builtin_functions;
}

简单分析一下上面这段函数映射,如:rc 文件中 start 字段会对应执行 do_start 函数

启动服务

以下面一段rc脚本为例,我们看一下一个服务是怎么启动的

sql 复制代码
on zygote-start 
    start zygote

首先这是一个action,当init进程在死循环中执行到ActionManager::ExecuteOneCommand方法时,检查到这个action刚好符合event_queue_队首的EventTrigger,便会执行这个action下面的commandscommands怎么执行在上面已经分析过了,我们去system/core/init/builtins.cpp里的map中找key-value对应关系,发现start对应着do_start函数:

scss 复制代码
static Result<void> do_start(const BuiltinArguments& args) {
    // 拿到需要启动的服务
    Service* svc = ServiceList::GetInstance().FindService(args[1]);
    if (!svc) return Error() << "service " << args[1] << " not found";
    // 启动服务
    if (auto result = svc->Start(); !result.ok()) {
        return ErrorIgnoreEnoent() << "Could not start service: " << result.error();
    }
    return {};
}

ServiceList通过args[1]即定义的服务名去寻找之前解析好的service,并执行system/core/init/service.cpp中的Service::Start方法:

c 复制代码
Result<void> Service::Start() {
    ....// 省略无关代码
    pid_t pid = -1;
    //通过namespaces_.flags判断使用哪种方式创建进程
    if (namespaces_.flags) {
        pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
    } else {
        pid = fork();
    }

    if (pid == 0) {
        //设置权限掩码
        umask(077);
        ....// 省略无关代码
        //内部调用execv函数启动文件
        if (!ExpandArgsAndExecv(args_, sigstop_)) {
            PLOG(ERROR) << "cannot execv('" << args_[0]
                        << "'). See the 'Debugging init' section of init's README.md for tips";
        }

        _exit(127);
    }

    if (pid < 0) {
        pid_ = 0;
        return ErrnoError() << "Failed to fork";
    }

    ....// 省略无关代码
    return {};
}


static bool ExpandArgsAndExecv(const std::vector<std::string>& args, bool sigstop) {
    std::vector<std::string> expanded_args;
    std::vector<char*> c_strings;

    expanded_args.resize(args.size());
    //将要执行的文件路径先加入容器
    c_strings.push_back(const_cast<char*>(args[0].data()));
    for (std::size_t i = 1; i < args.size(); ++i) {
        auto expanded_arg = ExpandProps(args[i]);
        if (!expanded_arg.ok()) {
            LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error();
        }
        expanded_args[i] = *expanded_arg;
        c_strings.push_back(expanded_args[i].data());
    }
    c_strings.push_back(nullptr);

    if (sigstop) {
        kill(getpid(), SIGSTOP);
    }
    //调用execv函数,带参执行文件
    return execv(c_strings[0], c_strings.data()) == 0;
}

这里先fork(或clone)出了一个子进程,再在这个子进程中调用execv函数执行文件, 一个服务就启动了

fork、clone、vfork

这三个API的内部实际都是调用一个内核内部函数do_fork,只是填写的参数不同而已。 vfork,其实就是fork的部分过程,用以简化并提高效率。而fork与clone是区别的。fork是进程资源的完全复制,包括进程的PCB、线程的系统堆栈、进程的用户空间、进程打开的设备等。而在clone中其实只有前两项是被复制了的,后两项都与父进程共享。

在四项资源的复制中,用户空间是相对庞大的,如果完全复制则效率会很低。在Linux中采用的"写时复制"技术,也就是说,fork执行时并不真正复制用户空间的所有页面,而只是复制页面表。这样,无论父进程还是子进程,当发生用户空间的写操作时,都会引发"写复制"操作,而另行分配一块可用的用户空间,使其完全独立。这是一种提高效率的非常有效的方法。

而对于clone来说,它们连这些页面表都是与父进程共享,故而是真正意义上的共享,因此对共享数据的保护必须有上层应用来保证。

相关推荐
断剑重铸之日24 分钟前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安27 分钟前
Android Library Maven 发布完整流程指南
android
岁月玲珑32 分钟前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟5 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡6 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi006 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil8 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你8 小时前
Android View的绘制原理详解
android
移动开发者1号11 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号11 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin