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

相关推荐
model200540 分钟前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏6891 小时前
Android广播
android·java·开发语言
与衫2 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
500了8 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵9 小时前
Android Debug Bridge(ADB)完全指南
android·adb
小雨cc5566ru14 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng15 小时前
android 原生加载pdf
android·pdf
hhzz15 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒16 小时前
XSS基础
android·web安全
勿问东西18 小时前
【Android】设备操作
android