android init进程启动流程

 Android系统启动篇

1,《android系统启动流程简介》

2,《android init进程启动流程》

3,《android zygote进程启动流程》

4,《Android SystemServer进程启动流程》

5,《android launcher启动流程》

6,《Android Activity启动过程详解》

Android系统开发准备篇

1,《Android 源码下载和编译》

2,《android 11源码编译和pixel3 刷机》

3,《Android Framework代码IDE加载和调试》

Android系统开发实践篇

1,《android设置默认输入法》

2,《android framework预制APK应用》

3,《Android系统层面限制应用开机自启动详解》

4,《android单独编译framework模块并push》

5,《Android Framework开发系统问题分析》

Android系统开发核心知识储备篇

1,《Android编译系统-envsetup和lunch代码篇》

2,《Android编译系统-概念篇》

3,《android日志系统详解》

4,《Android系统Handler详解》

5,《Android系统Binder详解》

6,《Android中Activity、View和Window关系详解》

7,《android view绘制流程详解》

8,《Android读取系统属性详解》

9,《android 窗口管理机制详解》

10,《初识Android系统》

11,《android中AMS进程通知Zygote进程fork新进程的通信方式》

Android核心功能详解篇

1,《android应用市场点击下载APK安装详解》

2,《Android 手势导航(从下往上滑动进入多任务页面)》

3,《android手势分析(应用界面左往右边滑动退出应用)》

4,《android应用安装流程详解》

5,《android11安装应用触发桌面图标刷新流程》

6,《Android系统多任务Recents详解》

7,《android系统导航栏视图分析》

———————————————————————————————————————————

目录

一,背景知识

二,init进程启动流程

2.1 ueventd_main

2.2 init 进程启动第一阶段

2.3 加载SELinux规则

2.4 init进程启动第二阶段

2.5 第三阶段init.rc

2.6 init.rc 解析过程

2.6.1 LoadBootScripts

2.6.2 执行Action动作

2.6.3 Zygote启动

三,总结


一,背景知识

        android启动流程如下,

        android系统基于Linux系统之上,启动步骤如上。init进程作为用户态第一个进程,从事android framework或 app相关开发工作,分析init进程启动流程很有必要。

二,init进程启动流程

        init 进程入口system/core/init/main.cpp,

using namespace android::init;
/*
 * 1.第一个参数argc表示参数个数,第二个参数是参数列表,也就是具体的参数
 * 2.main函数有四个参数入口,
 *一是参数中有ueventd,进入ueventd_main
 *二是参数中有subcontext,进入InitLogging 和SubcontextMain
 *三是参数中有selinux_setup,进入SetupSelinux
 *四是参数中有second_stage,进入SecondStageMain
 *3.main的执行顺序如下:
   *  (1)ueventd_main    init进程创建子进程ueventd,
   *      并将创建设备节点文件的工作托付给ueventd,ueventd通过两种方式创建设备节点文件
   *  (2)FirstStageMain  启动第一阶段
   *  (3)SetupSelinux     加载selinux规则,并设置selinux日志,完成SELinux相关工作
   *  (4)SecondStageMain  启动第二阶段
 */
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
    //当argv[0]的内容为ueventd时,strcmp的值为0,!strcmp为1
    //1表示true,也就执行ueventd_main,ueventd主要是负责设备节点的创建、权限设定等一些列工作
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
   //当传入的参数个数大于1时,执行下面的几个操作
    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);
        }
      //参数为“selinux_setup”,启动Selinux安全策略
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }
       //参数为“second_stage”,启动init进程第二阶段
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
 // 默认启动init进程第一阶段
    return FirstStageMain(argc, argv);
}


2.1 ueventd_main

代码路径:platform/system/core/init/ueventd.cpp

Android根文件系统的镜像中不存在“/dev”目录,该目录是init进程启动后动态创建的。

因此,建立Android中设备节点文件的重任,也落在了init进程身上。为此,init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。

ueventd通过两种方式创建设备节点文件。

第一种方式对应“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。

第二种方式对应“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
 

int ueventd_main(int argc, char** argv) {
    /*
     * init sets the umask to 077 for forked processes. We need to
     * create files with exact permissions, without modification by
     * the umask.
     */
    //设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666
    umask(000);
    //初始化内核日志,位于节点/dev/kmsg, 此时logd、logcat进程还没有起来,
    //采用kernel的log系统,打开的设备节点/dev/kmsg, 那么可通过cat /dev/kmsg来获取内核log。
    android::base::InitLogging(argv, &android::base::KernelLogger);

    LOG(INFO) << "ueventd started!";
    //注册selinux相关的用于打印log的回调函数
    SelinuxSetupKernelLogging();
    SelabelInitialize();

    std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;

    // Keep the current product name base configuration so we remain backwards compatible and
    // allow it to override everything.
    // TODO: cleanup platform ueventd.rc to remove vendor specific device node entries (b/34968103)
    auto hardware = android::base::GetProperty("ro.hardware", "");
    //解析xml,根据不同SOC厂商获取不同的hardware rc文件
    auto ueventd_configuration = ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc",
                                              "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});

    uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(
            std::move(ueventd_configuration.dev_permissions),
            std::move(ueventd_configuration.sysfs_permissions),
            std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));
    uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
            std::move(ueventd_configuration.firmware_directories),
            std::move(ueventd_configuration.external_firmware_handlers)));

    if (ueventd_configuration.enable_modalias_handling) {
        std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"};
        uevent_handlers.emplace_back(std::make_unique<ModaliasHandler>(base_paths));
    }
    UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size);

    //冷启动
    if (!android::base::GetBoolProperty(kColdBootDoneProp, false)) {
        ColdBoot cold_boot(uevent_listener, uevent_handlers,
                           ueventd_configuration.enable_parallel_restorecon);
        cold_boot.Run();
    }

    for (auto& uevent_handler : uevent_handlers) {
        uevent_handler->ColdbootDone();
    }

    // We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.
    signal(SIGCHLD, SIG_IGN);
    // Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN
    // for SIGCHLD above.
    while (waitpid(-1, nullptr, WNOHANG) > 0) {
    }
    //监听来自驱动的uevent,进行“热插拔”处理
    uevent_listener.Poll([&uevent_handlers](const Uevent& uevent) {
        for (auto& uevent_handler : uevent_handlers) {
            uevent_handler->HandleUevent(uevent);
        }
        return ListenerAction::kContinue;
    });

    return 0;
}

2.2 init 进程启动第一阶段

代码路径:platform\system\core\init\first_stage_init.cpp

init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略

第一阶段完成以下内容:

/* 01. 创建文件系统目录并挂载相关的文件系统 */

/* 02. 屏蔽标准的输入输出/初始化内核log系统 */
 

int FirstStageMain(int argc, char** argv) {
    //init crash时重启引导加载程序
    //这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
    //清空文件权限
    umask(0);
 
    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
 
    //在RAM内存上获取基本的文件系统,剩余的被rc文件所用
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
    CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
 
    // 非特权应用不能使用Andrlid cmdline
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
 
    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
 
    if constexpr (WORLD_WRITABLE_KMSG) {
        CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
    }
 
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
 
 
    //这对于日志包装器是必需的,它在ueventd运行之前被调用
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
 
 
    //在第一阶段挂在tmpfs、mnt/vendor、mount/product分区。其他的分区不需要在第一阶段加载,
    //只需要在第二阶段通过rc文件解析来加载。
    CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=1000"));
    
    //创建可供读写的vendor目录
    CHECKCALL(mkdir("/mnt/vendor", 0755));
    // /mnt/product is used to mount product-specific partitions that can not be
    // part of the product partition, e.g. because they are mounted read-write.
    CHECKCALL(mkdir("/mnt/product", 0755));
 
    // 挂载APEX,这在Android 10.0中特殊引入,用来解决碎片化问题,类似一种组件方式,对Treble的增强,
    // 不写谷歌特殊更新不需要完整升级整个系统版本,只需要像升级APK一样,进行APEX组件升级
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
 
    // /debug_ramdisk is used to preserve additional files from the debug ramdisk
    CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
 
    //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
    SetStdioToDevNull(argv);
    //在/dev目录下挂载好 tmpfs 以及 kmsg 
    //这样就可以初始化 /kernel Log 系统,供用户打印log
    InitKernelLogging(argv);
 
    ...
 
    /* 初始化一些必须的分区
     *主要作用是去解析/proc/device-tree/firmware/android/fstab,
     * 然后得到"/system", "/vendor", "/odm"三个目录的挂载信息
     */
    if (!DoFirstStageMount()) {
        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();
 
    static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
    uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
    setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
 
    //启动init进程,传入参数selinux_steup
    // 执行命令: /system/bin/init selinux_setup
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    execv(path, const_cast<char**>(args));
    PLOG(FATAL) << "execv(\"" << path << "\") failed";
 
    return 1;
}

2.3 加载SELinux规则

        SELinux是「Security-Enhanced Linux」的简称,是美国国家安全局「NSA=The National Security Agency」和SCC(Secure Computing Corporation)开发的 Linux的一个扩张强制访问控制安全模块。

        在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。

        selinux有两种工作模式:

1,permissive,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志,一般eng模式用
2,enforcing,所有操作都会进行权限检查。一般user和user-debug模式用

        不管是security_setenforce还是security_getenforce都是去操作/sys/fs/selinux/enforce 文件, 0表示permissive 1表示enforcing

2.4 init进程启动第二阶段

第二阶段主要内容:

    1,创建进程会话密钥并初始化属性系统
    2,进行SELinux第二阶段
    3,新建epoll并初始化
    4,启动匹配属性的服务端
    5,解析init.rc等文件,建立rc文件的action 、service,启动其他进程

int SecondStageMain(int argc, char** argv) {
    /* 
    *init crash时重启引导加载程序
    *这个函数主要作用将各种信号量,如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统
    */
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }
 
    //把标准输入、标准输出和标准错误重定向到空设备文件"/dev/null"
    SetStdioToDevNull(argv);
    //在/dev目录下挂载好 tmpfs 以及 kmsg 
    //这样就可以初始化 /kernel Log 系统,供用户打印log
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
 
    // 01. 创建进程会话密钥并初始化属性系统
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
 
    //创建 /dev/.booting 文件,就是个标记,表示booting进行中
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
    // 初始化属性系统,并从指定文件读取属性
    property_init();
 
    /*
     * 1.如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
     * 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数,
     */
    process_kernel_dt(); // 处理 DT属性
    process_kernel_cmdline(); // 处理命令行属性
 
    // 处理一些其他的属性
    export_kernel_boot_props();
 
    // Make the time that init started available for bootstat to log.
    property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
    property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
 
    // Set libavb version for Framework-only OTA match in Treble build.
    const char* avb_version = getenv("INIT_AVB_VERSION");
    if (avb_version) property_set("ro.boot.avb_version", avb_version);
 
    // See if need to load debug props to allow adb root, when the device is unlocked.
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
        load_debug_prop = "true"s == force_debuggable_env;
    }
 
    // 基于cmdline设置memcg属性
    bool memcg_enabled = android::base::GetBoolProperty("ro.boot.memcg",false);
    if (memcg_enabled) {
       // root memory control cgroup
       mkdir("/dev/memcg", 0700);
       chown("/dev/memcg",AID_ROOT,AID_SYSTEM);
       mount("none", "/dev/memcg", "cgroup", 0, "memory");
       // app mem cgroups, used by activity manager, lmkd and zygote
       mkdir("/dev/memcg/apps/",0755);
       chown("/dev/memcg/apps/",AID_SYSTEM,AID_SYSTEM);
       mkdir("/dev/memcg/system",0550);
       chown("/dev/memcg/system",AID_SYSTEM,AID_SYSTEM);
    }
 
    // 清空这些环境变量,之前已经存到了系统属性中去了
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");
    unsetenv("INIT_FORCE_DEBUGGABLE");
 
    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
 
    /*
     * 02. 进行SELinux第二阶段并恢复一些文件安全上下文 
     * 恢复相关文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建的,
     * 所以需要重新恢复上下文
     */
    SelinuxRestoreContext();
 
   /*
    * 03. 新建epoll并初始化子进程终止信号处理函数
    *  创建epoll实例,并返回epoll的文件描述符
    */
    Epoll epoll;
    if (auto result = epoll.Open(); !result) {
        PLOG(FATAL) << result.error();
    }
 
    /* 
     *主要是创建handler处理子进程终止信号,注册一个signal到epoll进行监听
     *进行子继承处理
     */
    InstallSignalFdHandler(&epoll);
 
    // 进行默认属性配置相关的工作
    property_load_boot_defaults(load_debug_prop);
    UmountDebugRamdisk();
    fs_mgr_vendor_overlay_mount_all();
    export_oem_lock_status();
 
    /*
     *04. 设置其他系统属性并开启系统属性服务
     */
    StartPropertyService(&epoll);
    MountHandler mount_handler(&epoll);
 
    //为USB存储设置udc Contorller, sys/class/udc
    set_usb_controller();
 
    // 匹配命令和函数之间的对应关系
    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);
 
    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }
 
    // 初始化文件上下文
    subcontexts = InitializeSubcontexts();
 
   /*
     *05 解析init.rc等文件,建立rc文件的action 、service,启动其他进程
     */
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
 
    LoadBootScripts(am, sm);
 
    // Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();
 
    // 当GSI脚本running时,确保GSI状态可用.
    if (android::gsi::IsGsiRunning()) {
        property_set("ro.gsid.image_running", "1");
    } else {
        property_set("ro.gsid.image_running", "0");
    }
 
 
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
 
    // 执行rc文件中触发器为 on early-init 的语句
    am.QueueEventTrigger("early-init");
 
    // 等冷插拔设备初始化完成
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
 
    // 开始查询来自 /dev的 action
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
 
    // 设备组合键的初始化操作
    Keychords keychords;
    am.QueueBuiltinAction(
        [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
            for (const auto& svc : ServiceList::GetInstance()) {
                keychords.Register(svc->keycodes());
            }
            keychords.Start(&epoll, HandleKeychord);
            return Success();
        },
        "KeychordInit");
 
    //在屏幕上显示Android 静态LOGO
    am.QueueBuiltinAction(console_init_action, "console_init");
 
    // 执行rc文件中触发器为on init的语句
    am.QueueEventTrigger("init");
 
    // Starting the BoringSSL self test, for NIAP certification compliance.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
 
    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
 
    // Initialize binder before bringing up other system services
    am.QueueBuiltinAction(InitBinder, "InitBinder");
 
    // 当设备处于充电模式时,不需要mount文件系统或者启动系统服务
    // 充电模式下,将charger假如执行队列,否则把late-init假如执行队列
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
 
    // 基于属性当前状态 运行所有的属性触发器.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
 
    while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
 
        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }
 
//依次执行每个action中携带command对应的执行函数
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                auto next_process_action_time = HandleProcessActions();
 
                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_action_time) {
                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                            *next_process_action_time - boot_clock::now());
                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                }
            }
 
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
 
        // 循环等待事件发生
        if (auto result = epoll.Wait(epoll_timeout); !result) {
            LOG(ERROR) << result.error();
        }
    }
 
    return 0;
}

2.5 第三阶段init.rc

        当属性服务建立完成后,init的自身功能基本就告一段落,接下来需要来启动其他的进程。但是init进程如何启动其它进程呢?其它进程都是一个二进制文件,我们可以直接通过exec的命令方式来启动,例如 ./system/bin/init second_stage,来启动init进程的第二阶段。但是Android系统有那么多的Native进程,如果都通过传exec在代码中一个个的来执行进程,那无疑是一个灾难性的设计。

        在这个基础上Android推出了一个init.rc的机制,即类似通过读取配置文件的方式,来启动不同的进程。接下来我们就来看看init.rc是如何工作的。

2.5.1 init.rc简介

        Linux的重要特征之一就是一切都是以文件的形式存在的,例如,一个设备通常与一个或多个设备文件对应。这些与内核空间交互的文件都在用户空间,所以在Linux内核装载完,需要首先建立这些文件所在的目录。而完成这些工作的程序就是本文要介绍的init。Init是一个命令行程序。其主要工作之一就是建立这些与内核空间交互的文件所在的目录。当Linux内核加载完后,要做的第一件事就是调用init程序,也就是说,init是用户空间执行的第一个程序。 当然init程序的功能不仅仅是加载上面设备文件。 init.rc是一个配置文件,内部由Android初始化语言编写(Android Init Language)编写的脚本。

        init.rc有两个,确切的说是两套,分别位于:

        1,./system/core/rootdir/init.rc

        2,./bootable/recovery/etc/init.rc

        从目录上大致可以猜测,这两个init.rc使用场景不一样,一个是正常启动用到的;一个是刷机用到的,也就是进入recorvery模式。我们这里重点分析的是上面那个,也是init.c关联的那个。

        /init.rc是主要的.rc文件,由init可执行文件在开始执行时加载。它负责系统的初始设置。

        在加载主目录/init.rc后,init立即加载包含在/{system,vendor,odm}/etc/init/目录中的所有文件。

        1,/system/etc/init/ 用于核心系统项,例如 SurfaceFlinger, MediaService和logd。

        2,/vendor/etc/init/ 是针对SoC供应商的项目,如SoC核心功能所需的actions或守护进程。

        3,/odm/etc/init/ 用于设备制造商的项目,如actions或运动传感器或其他外围功能所需的守护进程。

        每个系统如何加载这些不同目录的.rc文件,需要具体看从init.rc开始的import语句。 其中,import /init.${ro.hardware}.rc 较为常见。通过cat proc/cpuinfo可以查看ro.hardware的值=Hardware的值;一般在cpuinfo文件的末尾。

2.5.2 init.rc语句说明

        init.rc主要包含五种类型语句:Action,Command,Service,Option,Import

1,Actions:当达到某个触发条件时,执行命令,即响应某事件的过程,以on开头的语句来决定执行相应的service的时机,具体有如下时机:

    on early-init; 在初始化早期阶段触发;
    on init; 在初始化阶段触发;
    on late-init; 在初始化晚期阶段触发;
    on boot/charger: 当系统启动/充电时触发;
    on property:<key>=<value>: 当属性值满足条件时触发;

Actions take the form of:

    on <trigger> [&& <trigger>]*  // 触发条件
       <command>  // 执行命令
       <command>
       <command>

 2,Commands:命令将在所属事件发生时被一个个地执行,rc脚本中定义了许多命令,如

    boot:init程序启动后触发的第一个事件
    class_start <service_class_name>: 启动属于同一个class的所有服务;
    class_stop <service_class_name> : 停止指定类的服务
    start <service_name>: 启动指定的服务,若已启动则跳过;
    stop <service_name>: 停止正在运行的服务
    setprop <name> <value>:设置属性值
    mkdir <path>:创建指定目录
    symlink <target> <sym_link>: 创建连接到<target>的<sym_link>符号链接;
    write <path> <string>: 向文件path中写入字符串;
    exec: fork并执行,会阻塞init进程直到程序完毕;
    exprot <name> <name>:设定环境变量;
    loglevel <level>:设置log级别
    hostname <name> : 设置主机名
    import <filename> :导入一个额外的init配置文件

3,Services:服务Service,以 service开头,由init进程启动,一般运行在init的一个子进程,所以启动service前需要判断对应的可执行文件是否存在。

Services take the form of:

    service <name> <pathname> [ <argument> ]*
       <option>
       <option>
       ...

// name:服务名称
// pathname:服务路径
// argument:启动服务传递的参数
// option:对服务的约束项

        init生成的子进程,定义在rc文件,其中每一个service在启动时会通过fork方式生成子进程。例如: service servicemanager /system/bin/servicemanager代表的是服务名为servicemanager,服务执行的路径为/system/bin/servicemanager。

4,Options:服务修饰符,影响init进程运行服务的方式和时间。

1,class <class_name>
说明服务属于class_name这个类。缺省值service属于 “default” 类。同一个class下面的服务可以一起启动或停止。
2,disabled
表示当这个服务所在的class启动的时候,服务不会自动启动,
要用start server_name 或 property_set("ctl.start", server_name);才能启动。
3,oneshot
当服务退出后,不会再重新启动,如果没有加这个option,则服务默认退出后又会重新重启
4,user <username>
执行服务之前,先声明服务的用户名,缺省值应该为root用户.
5,group <groupname> [ <groupname> ]*
执行服务之前,先声明服务所属组名,可以一次声明属于多个组。
声明多个组时,除第一个组名外,其他的为服务的补充组名(调用接口 setgroups()).
6,onrestart + command
服务重启的时,会执行onrestart后面的command.
eg:onrestart restart media 重启名为media的服务
7,setenv <name> <value>
在当前服务进程中设置环境变量name的值为value。
注意:setenv定义的环境变量仅在本进程内生效,退出该进程,或者关闭相应的程序运行窗口,该环境变量即无效)
程序中可通过getenv("name")接口获取这个环境变量的值
setenv和export 的区别:
setenv csh ,本进程生效,退出后,变量无效
export bash ,全局生效,一直存在
格式:
export key=value
setenv key value
8,critical
声明为关键服务。如果服务在四分钟内退出了四次,则设备会进入recovery模式
9,socket <name> <type> <perm> [ <user> [ <group> ] ]
创建名为/dev/socket/<name>的unix domain socket ,并把它的句柄fd传给本服务进程
<type> 必须为 "dgram", "stream" or "seqpacket".User and group default to 0 ,也就是root.
10,seclablel 执行服务之前改变安全上下文

5,Imports:引入其他rc配置文件,语法格式为import <path>

2.5.3 常用命令

1).import <filename>          
  导入init.XX.rc、xxx.conf等文件  
   Parse an init config file, extending the current configuration.

2).chmod <octal-mode> <path>  
   Change file access permissions.

3).chown <owner> <group> <path>  
   Change file owner and group.  
    
4).chdir <directory>  
   Change working directory.  
    
5).chroot <directory>   
  改变进程根目录  
    
6).insmod <path>     
  加载XX.ko驱动模块

7).start <service>  
   Start a service running if it is not already running.

8).stop <service>  
   Stop a service from running if it is currently running.

9).class_start <serviceclass>  
   Start all services of the specified class if they are not already running.

10).class_stop <serviceclass>  
   Stop all services of the specified class if they are currently running.  
       
   class_reset <serviceclass>   //重启class下面所有的服务  
       
11).setprop <name> <value>    
   Set system property <name> to <value>.  
   通过getprop命令可以查看当前系统的属性值  
    
12).export <name> <value>         
  设置全局环境变量,这个变量值可以被所有进程访问(全局的,一直存在)  
  在代码中通过value = getenv("name")接口可以获取这个环境变量的值  
    
13).mkdir <path> [mode] [owner] [group]  
   创建目录,后面项缺省值为 mode,owner,group: 0755 root root

14).trigger <event>  
   Trigger an action.  Used to queue an action from another action.  
   例:trigger post-fs-data

15).exec <path> [ <argument> ]*       
   执行<path>指定的Program,并可以带有执行参数。  
   exec在调用进程内部执行一个可执行文件,并会阻塞当前进程,直到运行完成。  
   最好避免和那些builtin commands一样使用exec命令,否则容易造成阻塞 or stuck ( maybe there should be a timeout?)

16).ifup <interface>  
   启动某个网络接口,使其为up状态,通过netcfg可以查看,ifup eth0  等价于 netcfg eth0 up 功能一样  
    
17).hostname <name>  
   设置设备的主机名,一般默认设置为localhost,可以在终端通过hostname new_name进行修改

18).domainname <name>  
   设置网络域名localdomain

19).mount <type> <device> <dir> [ <mountoption> ]* 
    把device挂接到dir目录下面,文件系统类型为type。  
   <mountoption>s include "ro", "rw", "remount", "noatime", “nosuid”......,具体可查看[linux](http://lib.csdn.net/base/linux "Linux知识库")的mount命令说明  
    
20).setkey  
   TBD  == to be determined 暂时没有使用

21).setrlimit <resource> <cur> <max>  
  设置本服务进程的资源上限值。(使用例子??)

22).symlink <target> <path>     
   path 链接到 ---》target ;创建符号链接

23).sysclktz <mins_west_of_gmt>    
  设置系统时区(0 if system clock ticks in GMT)

24).wait <path> [ <timeout> ]  
 轮询查找给定的文件path是否存在,如果找到或者超时则返回默认超时为5秒。(使用实例???)

25).write <path> <string> [ <string> ]*  
   打开一个文件,利用write命令写入一个或多个字符串

2.6 init.rc 解析过程

2.6.1 LoadBootScripts

代码路径:platform\system\core\init\init.cpp

说明:如果没有特殊配置ro.boot.init_rc,则解析./init.rc

把/system/etc/init,/product/etc/init,/product_services/etc/init,/odm/etc/init,

/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件

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("/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/product_services/etc/init")) {
            late_import_paths.emplace_back("/product_services/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

代码路径:platform\system\core\init\init.cpp

说明:创建Parser解析对象,例如service、on、import对象

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;
 
    parser.AddSectionParser(
            "service", std::make_unique<ServiceParser>(&service_list, subcontexts, std::nullopt));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
 
    return parser;
}

2.6.2 执行Action动作

        按顺序把相关Action加入触发器队列,按顺序为 early-init -> init -> late-init. 然后在循环中,执行所有触发器队列中Action带Command的执行函数。

am.QueueEventTrigger("early-init");
am.QueueEventTrigger("init");
am.QueueEventTrigger("late-init");
...
while (true) {
if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
}

2.6.3 Zygote启动

        通过属性ro.zygote来控制不同版本的zygote进程启动。在init.rc的import段我们看到如下代码:

import /init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件

 init.rc位于/system/core/rootdir下。在这个路径下还包括四个关于zygote的rc文件。

分别是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬件决定调用哪个文件。

这里拿64位处理器为例,init.zygote64.rc的代码如下所示:
 

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main		# class是一个option,指定zygote服务的类型为main
    	    priority -20
            user root
   	    group root readproc reserved_disk
    	    socket zygote stream 660 root system  # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
   	    socket usap_pool_primary stream 660 root system
            onrestart write /sys/android_power/request_state wake # onrestart是一个option,说明在zygote重启时需要执行的command
    	    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 解析:

service zygote :init.zygote64.rc 中定义了一个zygote服务。 init进程就是通过这个service名称来创建zygote进程

/system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server解析:

zygote这个服务,通过执行进行/system/bin/app_process64 并传入4个参数进行运行:

    参数1:-Xzygote 该参数将作为虚拟机启动时所需的参数
    参数2:/system/bin 代表虚拟机程序所在目录
    参数3:--zygote 指明以ZygoteInit.java类中的main函数作为虚拟机执行入口
    参数4:--start-system-server 告诉Zygote进程启动systemServer进程

三,总结

        init进程第一阶段做的主要工作是挂载分区,创建设备节点和一些关键目录,初始化日志输出系统,启用SELinux安全策略。

        init进程第二阶段主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些功能落实。

        init进行第三阶段主要是解析init.rc 来启动其他进程,进入无限循环,进行子进程实时监控。

猜你喜欢

转载自blog.csdn.net/allen_xu_2012_new/article/details/130701425