基于Android13的系统启动流程分析(四)之SecondStageMain阶段

这篇具有很好参考价值的文章主要介绍了基于Android13的系统启动流程分析(四)之SecondStageMain阶段。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲SecondStageMain阶段
(若分析有误敬请指教)

一. Android系统启动基本介绍

在基于Android13的系统启动流程分析(三)之FirstStageMain阶段已经讲解过android系统启动的基本介绍了,这里不再单独介绍了

二. SecondStageMain源码分析

我们先看是怎么进入该阶段的,仍然是由用户空间层main.cpp调用,先简单的说一下第二阶段主要是干什么的:

  1. 设置init进程优先级并创建/dev/.booting设备块代表init正在初始化执行中
  2. 初始化属性服务,也就是会读取property_contexts文件内容以及读取build.prop内容通过MMAP映射到全局内存中,也就是对所有进程共享该资源
  3. 启动属性服务并创建socket_service,等待新链接去更新或新增属性值
  4. 挂载/apex,vendor_overlay等其他分区
  5. 检查设备是否被unlock解锁
  6. 持续监控/proc/mounts设备文件,解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等。将解析内容生成实体类追加到要挂载的mounts_中并进行挂载
  7. 将根目录下所有的目录设置为全局共享,例如对/data设置为根目录下的全局共享
  8. 解析init.rc以及其他import了的rc文件,主要解析rc中的:service,on(action),Import,而zygote进程正是从解析rc文件中创建的,然后根据zygote(本质上就是一个socket),通过JNI调用到上层代码,再fork出systemServer.java
  9. 让init进程无限循环,因为主进程不能退出,退出即代表发生异常
  10. 处理sm(ServiceList)中服务超时重启相关(init.rc中的service),若rc中启动的服务启动超时则会让其服务重新启动
...
    if (argc > 1) {
       ...
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);//第二阶段执行
        }
    }
    return FirstStageMain(argc, argv); //第一阶段执行
}

1. int SecondStageMain(int argc, char** argv)分析

位于/system/core/init/init.cpp,直接上代码,代码注释中分步骤来分析

int SecondStageMain(int argc, char** argv) {
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        // 针对产生异常的进程进行信号处理,确保子进程能重启,如果主进程pid=1发生异常则触发crash
        // 已经在上个文章分析过该函数
        InstallRebootSignalHandlers();
    }
 	...
    // 初始化kernel log,所有的kernel log均输出在/dev/kmsg设备节点上
    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";
    ...
    // Init不应该因为依赖于任何其他进程而崩溃,因此我们忽略主进程的信号管道信息
    // 但我们不想忽略子进程的SIGPIPE(信号管道),因此我们为信号处理程序设置了一个no op函数
    // SIGPIPE信号产生的场景举例
    // ① 初始时,C、S连接建立,若某一时刻,C端进程宕机或者被KILL而终止(终止的C端进程将会关闭打开的文件描述符,即向S端发送FIN段),S端收到FIN后,响应ACK
    // ② 假设此时,S端仍然向C端发送数据:当第一次写数据后,S端将会收到RST分节; 当收到RST分节后,第二次写数据后,S端将收到SIGPIPE信号(S端进程被终止)
    {
        struct sigaction action = {.sa_flags = SA_RESTART};
        action.sa_handler = [](int) {};
        // sigaction是一个函数,可以用来查询或设置信号处理方式
        sigaction(SIGPIPE, &action, nullptr);
    }

    //  MIN_OOM_SCORE_ADJUST = -1000;
    //  MAX_OOM_SCORE_ADJUST = 1000;
    //  设置进程的优先级,例如APK优先级是AMS计算出来并下发到/proc/1/oom_score_adj
    //  统一由init进程设置/proc/**/oom_score_adj为-1000优先级
    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();
    }

  	...
    // 创建 /dev/.booting 文件,就是个标记,表示booting进行中
    // is_booting()函数会依靠空文件".booting"来判断是否进程处于初始化中,初始化结束后,这个文件会被删除
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
    
    // 当设备解锁时,允许adb root
    // 如果设备unlocked(解锁了),则会修改selinux规则,放大用户权限,这一点在第一阶段已经完成了
    // 并设置了INIT_FORCE_DEBUGGABLE环境变量,这里只是根据环境变量获取第一阶段的内容
    const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
    bool load_debug_prop = false;
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
        load_debug_prop = "true"s == force_debuggable_env;
    }
    unsetenv("INIT_FORCE_DEBUGGABLE");
    
    // 如果设备未unlock,则卸载关于debug版本的/debug_ramdisk
    // 让属性值读取/ramdisk而不是/debug_ramdisk,因为非unlock,不需要debug ramdisk
    if (!load_debug_prop) {
    	// setup 1
        UmountDebugRamdisk();
    }

    // 初始化属性服务
    // 获取system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,等其他build.prop并加载到properties map结构中
    // 然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值
    // 注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖
    // setup 2
    PropertyInit();

    // Umount second stage resources after property service has read the .prop files.
    // 在属性服务读取.prop文件后,将卸载/second_stage_resources,因为已经用不到了,已经将属性值加载到内存当中了
    UmountSecondStageRes();

    // Umount the debug ramdisk after property service has read the .prop files when it means to.
    // 若是debug版本,已经获取了属性值过后,也将卸载/debug_ramdisk
    if (load_debug_prop) {
        UmountDebugRamdisk();
    }

    // 挂载第二阶段(该阶段)的文件系统,第一阶段已经挂载了很多基本的文件系统了以及重要的分区
    // 挂载/apex:简单点说apex为了解决性能而产生的机制,APK可以通过内置升级,但系统升级可是个大问题
    // setup 3
    MountExtraFilesystems();
	...
    // 之前初始化了属性服务,这里将开始属性服务,其实它就是一个socket
    // 创建socket,处理客户端发来的请求,决定是更新属性值还是新增属性值
    // setup 4
    StartPropertyService(&property_fd);
    ...
    // 根据ro.vndk.version 版本号,将/system/vendor_overlay和/product/vendor_overlay挂载在vendor上
    // 也就是会覆盖vendor分区内容
    // setup 5
    fs_mgr_vendor_overlay_mount_all();
    // 根据ro.oem_unlock_supported属性值来决定是否可以对设备进行unlock(解锁)
    // 若ro.oem_unlock_supported:「1」则代表 设备支持刷写unlock,若不支持该值为0
    // 如果设备支持刷写解锁,ro.boot.verifiedbootstate则会为orange,根据orange状态,把androidboot.flash.locked设置为1
    // 如果设备不支持刷新解锁,ro.boot.verifiedbootstate则会为green,根据orange状态,把androidboot.flash.locked设置为0
    // androidboot.flash.locked在系统启动完成后会形成属性值
    // (或 /firmware/android/flash.locked DT 属性)设置为“1”(如果已锁定)或“0”(如果已解锁)来指示锁定状态。
    export_oem_lock_status();
    // 持续监控/proc/mounts 节点(fopen("/proc/mounts", "re")),主要是解析该文件
    // 解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等(空格分割/dev/block/dm-33 /mnt/pass_through/0/emulated ext4 rw)
    // 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息
    // setup 6
    MountHandler mount_handler(&epoll);
    ...
    // 将根目录下所有的目录设置为全局共享
    // 将根目录/{分区}类型设置为共享,以便默认情况下所有进程都可以看到任何装载事件(例如/data)
    if (!SetupMountNamespaces()) {
        PLOG(FATAL) << "SetupMountNamespaces failed";
    }

	...
	// setup 7
    // 创建ActionManager对象和ServiceList对象
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    // 加载rc文件,保存到action manager和service list中
    // rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
    // 主要解析rc中的:service,on,Import,包含了zygote.rc,路径:/system/bin/app_process64
    // 在文件系统挂载的第一阶段,system/vendor分区已经成功挂载,而其它分区的挂载则通过rc来挂载
    // 后面主要分析一下on early-init和on init和zygote
    LoadBootScripts(am, sm);
	...
	// setup 8
    // 构建action和触发器(on early-init),放到event_queue,等待执行函数
    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.
    // 构建action和触发器(on init),放到event_queue,等待执行函数
    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.
    // 运行所有属性触发器(action),例如 on property
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

    // Restore prio before main loop
    // 设置进程优先级,主进程不能被销毁和退出,循环处理rc中的服务相关
    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())) {
            // 执行队列中的action
            // 队列中依次执行每个action中携带command对应的执行函数
            am.ExecuteOneCommand();
        }
        if (!IsShuttingDown()) {
            // 处理sm(ServiceList)中服务超时重启相关(init.rc中的service)
            auto next_process_action_time = HandleProcessActions();
            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;
            }
        }
...

    return 0;
}

2. SecondStageMain(int argc, char** argv)----->setup 1步骤

先贴一下setup 1的代码块,主要分析UmountDebugRamdisk函数

    if (!load_debug_prop) {
    	// setup 1
        UmountDebugRamdisk();
    }
    
static void UmountDebugRamdisk() {
    if (umount("/debug_ramdisk") != 0) {
        PLOG(ERROR) << "Failed to umount /debug_ramdisk";
    }
}    

如果设备未unlock,则卸载关于debug版本的/debug_ramdisk,让属性值读取/ramdisk而不是/debug_ramdisk

3. SecondStageMain(int argc, char** argv)----->setup 2步骤

该步骤主要作用是初始化属性值服务,这里只是一个初始化的动作

    // setup 2
    PropertyInit();
  • 初始化属性服务,获取system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,等其他build.prop并加载到properties map结构中,然后会将该properties结构,通过MMAP映射到全局内存中,供所有进程调用,主要ro是只读,只能够写一次,即初始化时给的一次值,注意build.prop有优先级,product下的优先级最高,因为是map结构,针对key相同的属性值时,会覆盖
  • PropertyInit位于/system/core/init/property_service.cpp
void PropertyInit() {
   ...
    // 建立属性服务设备文件(linux思想,万物皆文件系统)
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    // 创建序列化过后的propertyInfo实体,主要就是读取property_contexts文件
    CreateSerializedPropertyInfo();
    // 这里主要步骤是:通过mmap映射,将文件(/dev/__properties__/{..})映射进内存(初始化属性内存映射文件)
    // 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }
    // 加载/dev/__properties__/property_info,此文件是序列化过的,无法直接查看内容
    if (!property_info_area.LoadDefaultPath()) {
        LOG(FATAL) << "Failed to load serialized property info file";
    }

    // 读取/proc/device-tree/firmware/android/目录下的文件,生成ro.boot.xxx属性值
    // 这三个函数主要就是生成ro.boot.xx属性值,这里不详细研究
    ProcessKernelDt();
    ProcessKernelCmdline();
    ProcessBootconfig();

    // 初始化ro.xx,将ro.boot.xx的属性值复制给ro.xxx
    // { "ro.boot.serialno",   "ro.serialno",   UNSET, },
    // { "ro.boot.mode",       "ro.bootmode",   "unknown", },
    // { "ro.boot.baseband",   "ro.baseband",   "unknown", },
    // { "ro.boot.bootloader", "ro.bootloader", "unknown", },
    // { "ro.boot.hardware",   "ro.hardware",   "unknown", },
    // { "ro.boot.revision",   "ro.revision",   "0", },
    ExportKernelBootProps();

    // 读取{system/vendor/odm/product}/build.prop等...
    // 将build.prop通过MMAP映射到全局内存中,供所有进程访问 
    PropertyLoadBootDefaults();

调用__system_property_area_init通过mmap映射,将文件(/dev/properties/{包含了上下文和propertys_info实体:保存了property_contexts文件内容})映射进内存(初始化属性内存映射文件), 这里将文件映射进内存,用于后续对__prooerties__目录(也可以说是文件,将代码块映射为file类型)进行内存共享

  • CreateSerializedPropertyInfo:

    创建序列化过后的属性值信息(既然序列化了,那肯定是要跨进程通信)
    (1).读取{system_ext,vendor,product,odm,system}_property_contexts属性值安全上下文并赋值给:property_infos
    (2).property_infos属于容器类型,读取不同的property_contexts将会追加到末尾,而不是覆盖原本内容
    (3).将property_infos实体序列化,使其可以跨进程传递消息
    (4).将property_infos实体写入/dev/properties/property_info驱动节点中

  • PropertyLoadBootDefaults
    这里会将属性值全部写入build.prop里,分为:system/build.prop,vendor/build.prop,/odm/build.prop,/product/build.prop,注意是有优先级顺序的,按先后顺序覆盖,获取build.prop分别是直接从指定文件里获取和从指定分区中获取,这两个方式作用都一样,只不过第二种需要区分出分区里是否存在{partition}/{etc}/build.prop,有的分区是不存在{partition}/etc/build.prop这个文件,而是直接存在于{partition}/build.prop

一切都写到了注释里,继续分析比较重要的2个函数CreateSerializedPropertyInfoPropertyLoadBootDefaults

3.1 CreateSerializedPropertyInfo
void CreateSerializedPropertyInfo() {
    auto property_infos = std::vector<PropertyInfoEntry>();
    //判断文件是否存在,并判断文件是否可写(属性服务的安全上下文,之前有提过设备节点,服务,属性值都要遵守selinux规则)
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
        // 加载property_contexts文件,该文件内容都是配置的属性值上下文,属于selinux相关知识
        // 通过ParsePropertyInfoFile解析该文件,得到property_infos
        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {
            return;
        }
        // 如果这里system_ext/vendor/product没有挂载上(例如在恢复的情况下,vendor分区将不会安装),则无法继续加载该上下文,该分区会在第一阶段挂载
        // 从下面的代码可以看出来,property_infos的是容器类型vector<PropertyInfoEntry>()
        // 所以这里并没有优先级也没有以哪个property_contexts为准,而是根据是否存在对应的分区而append加载
        // 也就是在对应后面追加内容,而不是覆盖:property_infos->emplace_back(property_info_entry);
        if (access("/system_ext/etc/selinux/system_ext_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/system_ext/etc/selinux/system_ext_property_contexts",
                                     &property_infos);
        }
        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
                                     &property_infos);
        }
        if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
                                     &property_infos);
        }
        if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
            LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
        }
     // 若/system/etc/selinux/plat_property_contexts无法读取,则else
    } else {
        // 由于system下的安全上下文未创建,则可能是system出现异常未挂载上,或者供应商修改过plat_property_contexts
        // 一般供应商都是复写,而不会直接更改文件名称
        // 若找不到该文件,则加载根目录下的这些属性值安全上下文
        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        LoadPropertyInfoFromFile("/system_ext_property_contexts", &property_infos);
        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
        }
        LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
        LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
    }

    // 序列化property_infos实体,使其可以跨进程传递
    auto serialized_contexts = std::string();
    auto error = std::string();
    if (!BuildTrie(property_infos, "u:object_r:default_prop:s0", "string", &serialized_contexts,
                   &error)) {
        LOG(ERROR) << "Unable to serialize property contexts: " << error;
        return;
    }


    // 将property_infos写入/dev/__properties__/property_info设备文件中
    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}

以上的步骤都是对property_infos变量进行赋值,数据就是property_contexts文件内容,而property_infos是属于vector<PropertyInfoEntry>()容器类型,而每次加载property_infos时都是调用的property_infos->emplace_back(property_info_entry);在容器后面追加数据,不会覆盖原有的数据

读取property_contexts文件内容,将内容传递给property_infos实体

3.2 PropertyLoadBootDefaults
void PropertyLoadBootDefaults() {
    std::map<std::string, std::string> properties;
    // 如果是恢复模式则加载/prop.default
    if (IsRecoveryMode()) {
        load_properties_from_file("/prop.default", nullptr, &properties);
    }

    // 这里还没执行,只是一个未执行的代码块,从分区里读取build.prop文件
    const auto load_properties_from_partition = [&properties](const std::string& partition,
                                                              int support_legacy_path_until) {
        // 加载{system_ext,product等分区}/etc/build.prop文件
        // 以后代码上获取的属性值就是从该文件中获取的
        auto path = "/" + partition + "/etc/build.prop";
        if (load_properties_from_file(path.c_str(), nullptr, &properties)) {
            return;
        }
        ...
    }
    // 获取第一阶段生成的second_stage_resources/system/etc/ramdisk/build.prop
    // 并追加到properties中(这里是map结构,注意会覆盖内容)
    LoadPropertiesFromSecondStageRes(&properties);
    // 先读取的/system/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
    load_properties_from_file("/system/build.prop", nullptr, &properties);
    // 获取/system_ext分区下的build.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)
    load_properties_from_partition("system_ext", /* support_legacy_path_until */ 30);
     // 继续读取的/vendor/default.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
    load_properties_from_file("/vendor/default.prop", nullptr, &properties);
    // }
    // 继续读取的/vendor/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
    load_properties_from_file("/vendor/build.prop", nullptr, &properties);
    // 继续读取的/vendor_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
    load_properties_from_file("/vendor_dlkm/etc/build.prop", nullptr, &properties);
    // 继续读取的/odm_dlkm/etc/build.prop,然后赋值给properties(这里是map结构,注意会覆盖内容)
    load_properties_from_file("/odm_dlkm/etc/build.prop", nullptr, &properties);
    // 获取/{odm,product}.prop/default.prop,赋值给然后赋值给propertiess(这里是map结构,注意会覆盖内容)
    load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
    load_properties_from_partition("product", /* support_legacy_path_until */ 30);
    // 因为propertiess是map结构,如果key一样则会覆盖内容,所以以上代码的顺序不能调换,优先级最高的是/product下的build.prop
    // /system->/system_ext->/vendor->/omd->/product

    // 如果"/debug_ramdisk/adb_debug.prop"存在,说明设备已经unlock过了,则加载unlock过后的属性值,例如ro.debugger=1,则是开启了调试模式
    if (access(kDebugRamdiskProp, R_OK) == 0) {
        LOG(INFO) << "Loading " << kDebugRamdiskProp;
        load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
    }

    // 将从build.prop,default.prop获取的properties,循环设置属性值
    // 这里是把.prop文件里的属性值通过mmap映射到内存中,使得所有进程可以访问(全局)
    for (const auto& [name, value] : properties) {
        std::string error;
        // 如果是ro则是只读,只能设置一次,再次设置会无效,如果存在相同的key,则会调用update更新
        if (PropertySet(name, value, &error) != PROP_SUCCESS) {
            LOG(ERROR) << "Could not set '" << name << "' to '" << value
                       << "' while loading .prop files" << error;
        }
    }
    ...
    // 设置persist.sys.usb.config属性值来决定是否开启调试模式(adb或none)
    update_sys_usb_config();
}

以上代码主要做的一个动作:读取各个分区里的build.prop或直接从指定目录下读取build.prop并调用PropertySet设置到全局内存中,让所有进程访问

主要的两个函数:load_properties_from_partitionload_properties_from_file,分别从分区里读取和从指定文件读取,为什么这么做呢?因为有的分区下是没有/etc目录的,无法直接指定文件位置,所以通过调用 "/" + partition + "/etc/build.prop"来读取,若不存在该文件则直接return。
注意这里读取了{system,system_ext,vendor,vendor_dlkm,odm_dlkm,odm,product}/build.prop,由于properties属于map结构,如果key相同是会覆盖原有的值,所以这里是有优先级排序的:

  1. /system/build.prop
  2. /system_ext/{etc}/build.prop
  3. /vendor/default.prop
  4. /vendor/build.prop
  5. /vendor_dlkm/etc/build.prop
  6. /odm_dlkm/etc/build.prop
  7. /odm/{etc}/build.prop
  8. /product/{etc}/build.prop

如果在system中自定义了属性值,又在product自定义了一样的属性值,那么是以product为准

这里只是init初始化过程,读取build.prop并解析出来每一行属性值并调用PropertySet设置到全局内存中

继续分析一下PropertySet函数,该函数比较简单

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    ...
    // 找到该属性值
    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
    if (pi != nullptr) {
        // 如果是以ro开头则代表只读,禁止写入,返回error
        if (StartsWith(name, "ro.")) {
            *error = "Read-only property was already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }
        // 若存在该属性值且非ro 则更新属性值
        __system_property_update(pi, value.c_str(), valuelen);
    } else {
        // 若找不到该属性值则新增
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            *error = "__system_property_add failed";
            return PROP_ERROR_SET_FAILED;
        }
    }

    // 如果是以persist.开头,则会全部写进/data/property/persistent_properties
    // 属于一个缓存机制
    // std::string persistent_property_filename = "/data/property/persistent_properties";
    if (persistent_properties_loaded && StartsWith(name, "persist.")) {
        WritePersistentProperty(name, value);
    }
    ...
    return PROP_SUCCESS;
}

以上代码就是若存在属性值则更新,若不存在则新增。如果是ro开头的属性值则代表只读,只能初始化的时候给默认值,后续不允许修改值,如果是persist开头的则会全部缓存进/data/property/persistent_properties,persist开头的属性值是可改的,如果用户修改过了persist开头的属性值相当于修改了/data/property/persistent_properties里的属性值,那么重启后仍然生效,并不会还原默认值。这样即不影响属性值的原子性(原有的属性值),又给了开发者/用户操作的空间,如果是刷机或恢复出厂则会还原

如果是系统开发者自定义了属性值,但是发现默认定义的时候属性值无法写入,则可能是property_contexts安全上下文影响,可以直接修改这里的代码,__system_property_add强行调用该方法即可

4. SecondStageMain(int argc, char** argv)----->setup 3步骤

setup 4
MountExtraFilesystems();

static void MountExtraFilesystems() {
#define CHECKCALL(x) \
    if ((x) != 0) PLOG(FATAL) << #x " failed.";

    // /apex is used to mount APEXes
    CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));

    // /linkerconfig is used to keep generated linker configuration
    CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                    "mode=0755,uid=0,gid=0"));
#undef CHECKCALL
}

可以看到MountExtraFilesystems主要就挂载了/apex和/linkerconfig并归属于tmpfs文件系统(运行在内存的文件系统,运行速度较快),这里主要探讨一下APEX.
应用程序可以通过更新APK来升级,供应商客制化系统后可以通过OTA进行系统升级,而针对google 开发者来修复原生的系统bug该如何更新呢?那就是通过google发布的安全patch和更新manline以及apex来解决

  • 简单点说apex为了解决性能而产生的机制,APK可以通过内置升级,但系统升级可是个大问题
  • apex可以将系统内部的各个功能打包成模块,然后针对这些模块单独升级
  • apk是应用程序的载体,对应用开发者而言,可以apk方式对应用功能进行升级
  • apex是系统功能的载体,对系统开发者(目前看主要是谷歌)而言,可以apex方式对系统功能进行升级
  • 一般是google开发者通过playstore发布,然后供我们下载更新,而对应ODM第三方供应商,则需要通过OTA升级
  • apex相当于对系统功能进行了更细粒度的划分,可以独立升级这些功能,可以把apex看成是一个一个的系统升级包

5. SecondStageMain(int argc, char** argv)----->setup 4步骤

	// setup 4
    StartPropertyService(&property_fd);

在setup2中初始化了property,获取了build.prop和property_contexts并设置为内存中全局共享

void StartPropertyService(int* epoll_socket) {
    // 在init阶段version=1,这里已经升级到2了
    InitPropertySet("ro.property_service.version", "2");

    // 创建sockets,套接字,可以用于网络通信,也可以用于本机内的进程通信
    // socketpair()函数用于创建一对无名的,相互连接的套接字
    // 如果函数创建成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1
    int sockets[2];
    // 参数1:表示协议族AF_UNIX
    // 参数2:表示协议,SOCK_SEQPACKET提供连续可靠的数据包连接
    // SOCK_CLOEXEC:当文件描述符设置了O_CLOEXEC属性后,在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭
    // 而SOCK_DGRAM是基于UDP的
    // 参数3:表示类型,只能为0
    // 参数4:套节字柄,该两个句柄作用相同,均能进行读写双向操作
    if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
        PLOG(FATAL) << "Failed to socketpair() between property_service and init";
    }
    ...
    // PROP_SERVICE_NAME:/dev/socket/property_service,创建socket
    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false, 0666, 0, 0, {});
        result.ok()) {
        property_set_fd = *result;
    } else {
        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
    }

    // 监听socket:/dev/socket/property_service,最大连接:8
    listen(property_set_fd, 8);

    // 开启线程处理socket
    auto new_thread = std::thread{PropertyServiceThread};
    property_service_thread.swap(new_thread);
}

这里就是创建了socket,而属性值服务的本质就是一个socket(/dev/socket/property_service),最大连接为:8,持续等待连接,连接成功后决定是更新还是新增属性值,仍然是调用的PropertySet,继续分析一下PropertyServiceThread,该函数里调用了handle_property_set_fd函数,来看看具体实现

static void handle_property_set_fd() {
    // 设置超时:2s
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    // 可以设置四个参数,所以用的是accept4,而当启用了SOCK_CLOEXEC参数后,进程在调用exec函数族时,文件描述符就会自动关闭,无需手动关闭
    int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
    if (s == -1) {
        return;
    }
    ...
    
    switch (cmd) {
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];
        ...
        prop_name[PROP_NAME_MAX-1] = 0;
        prop_value[PROP_VALUE_MAX-1] = 0;
        ...
        // 当收到客户端发来的请求,去更新或新增属性值时会调用HandlePropertySet去处理
        const auto& cr = socket.cred();
        std::string error;
        uint32_t result =
                HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error);
        if (result != PROP_SUCCESS) {
            LOG(ERROR) << "Unable to set property '" << prop_name << "' from uid:" << cr.uid
                       << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
        }

        break;
      }
 	...
    }
}

可以看到设置了连接超时时间为2000ms,执行 case PROP_MSG_SETPROP,获取属性值的名称和值,调用HandlePropertySet里的PropertySet进行更新属性值或新增。PropertySet已经分析过了

至此属性值相关就分析完毕了,简单的来说就是读取build.prop属性值共享到全局内存中让所有进程获取,然后创建属性值服务(socket),持续监听客户端(哪个进程去调用更新属性值就是当前客户端)发来的请求,最大只能同时受理8个来自客户端的请求,若有客户端请求则去更新属性值或新增属性值

6. SecondStageMain(int argc, char** argv)----->setup 5步骤

	// setup 5
    fs_mgr_vendor_overlay_mount_all();

这里的代码只要是针对如果有vendor_overlay分区,则覆盖/vendor分区,主要看供应商的客制化

const std::vector<const std::string> kVendorOverlaySourceDirs = {
        "/system/vendor_overlay/",
        "/product/vendor_overlay/",
};

bool fs_mgr_vendor_overlay_mount_all() {
	...
    // 获取 "/system/vendor_overlay/","/product/vendor_overlay/"下的所有子目录
    // 将vendor_overlay挂载到vendor上,若存在该覆盖分区则会覆盖之前的vendor分区
    const auto vendor_overlay_dirs = fs_mgr_get_vendor_overlay_dirs(vndk_version);
    if (vendor_overlay_dirs.empty()) return true;
    if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
        LINFO << "vendor overlay: kernel does not support overlayfs";
        return false;
    }

    // Mount each directory in /(system|product)/vendor_overlay/<ver> on /vendor
    // 挂载vendor_overlay到/vendor分区上
    auto ret = true;
    for (const auto& vendor_overlay_dir : vendor_overlay_dirs) {
        if (!fs_mgr_vendor_overlay_mount(vendor_overlay_dir)) {
            ret = false;
        }
    }
    return ret;
}

7. SecondStageMain(int argc, char** argv)----->setup 6步骤

    // setup 6
    MountHandler mount_handler(&epoll);

该函数主要功能:持续监控/proc/mounts 节点(fopen("/proc/mounts", "re")),主要是解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等, 将解析内容生成实体类追加到要挂载的mounts_中,主要就是对mounts文件解析,更新mounts_中的信息,挂载mounts信息里的分区
位置:/system/core/init/mount_handler.cpp

MountHandler::MountHandler(Epoll* epoll) : epoll_(epoll), fp_(fopen("/proc/mounts", "re"), fclose) {
    if (!fp_) PLOG(FATAL) << "Could not open /proc/mounts";
    auto result = epoll->RegisterHandler(
            fileno(fp_.get()), [this]() { this->MountHandlerFunction(); }, EPOLLERR | EPOLLPRI);
    if (!result.ok()) LOG(FATAL) << result.error();
}

继续看一下MountHandlerFunction函数

void MountHandler::MountHandlerFunction() {
    rewind(fp_.get());
    std::vector<MountHandlerEntry> touched;
    auto untouched = mounts_; //容器类型
    char* buf = nullptr;
    size_t len = 0;
    // 循环读取文件内容中的每一行
    while (getline(&buf, &len, fp_.get()) != -1) {
        auto buf_string = std::string(buf);
        // /proc/mounts文件下存在一系列代码
        // 若读取到/0/emulated则跳过
        if (buf_string.find("/emulated") != std::string::npos) {
            continue;
        }
        // 根据读取的文件内容,来解析分区以及device path,type等
        auto entry = ParseMount(buf_string);
        auto match = untouched.find(entry);
        // 若这一行解析到底了仍然没有匹配的信息,则这一条记录追加到touched中
        // entry:举例--->对文件内容/dev/block/dm-33 /data_mirror/data_ce/null ext4 解析过后的实体
        if (match == untouched.end()) {
            touched.emplace_back(std::move(entry));
        } else {
            // 若找到了匹配的信息则移除
            untouched.erase(match);
        }
    }
    free(buf);
    // 将匹配到的entry进行移除,并记录Mount属性值
    for (auto& entry : untouched) {
        SetMountProperty(entry, false);
        mounts_.erase(entry);
    }
    // 将未匹配到的entry追加到mounts_,并记录Mount属性值
    for (auto& entry : touched) {
        SetMountProperty(entry, true);
        // emplace是更具有性能的 更新或追加
        mounts_.emplace(std::move(entry));
    }
}
  1. 读取/proc/mounts,解析文件中每一行数据,获取挂载点,挂载分区,文件类型,权限等
  2. 若解析到/0/emulated则跳过,不处理
  3. 根据mounts_,当entry(解析后的内容)可以在其中找到则移除
  4. 若entry未在mounts_找到,则追加到mounts_中
  5. 相当于移除旧的entry,将新的entry追加到mounts_

再看看是如何解析文件内容的auto entry = ParseMount(buf_string),解析文件内容

MountHandlerEntry ParseMount(const std::string& line) {
    auto fields = android::base::Split(line, " ");
    while (fields.size() < 3) fields.emplace_back("");
    if (fields[0] == "/dev/root") {
        auto& dm = dm::DeviceMapper::Instance();
        std::string path;
        // 根据名称获取system分区目录路径,若根据名称找不到则直接获取根目录/
        // 若找到根目录则继续找/system,若找到则拿到device path
        // 例如/system就是挂载在/dev/block/dm-1上,那么获取的就是这个玩意
        // /dev/block/dm-3 /vendor ext4 ro,seclabel,relatime 0 0
        if (dm.GetDmDevicePathByName("system", &path) || dm.GetDmDevicePathByName("vroot", &path)) {
            fields[0] = path;
        } else if (android::fs_mgr::Fstab fstab; android::fs_mgr::ReadDefaultFstab(&fstab)) {
            auto entry = GetEntryForMountPoint(&fstab, "/");
            if (entry || (entry = GetEntryForMountPoint(&fstab, "/system"))) {
                fields[0] = entry->blk_device;
            }
        }
    }
    // 获取所有/dev目录下的device
    // readlink 是Linux系统中的一个常用命令,主要用来找出符号链接所指向的位置
    // 也就是找到devcie path:/dev/block/dm-33
    if (android::base::StartsWith(fields[0], "/dev/")) {
        if (std::string link; android::base::Readlink(fields[0], &link)) {
            fields[0] = link;
        }
    }
    // fields0:/dev/block/dm-33(blk_device)
    // fields1:挂载在device上的分区/文件路径:/data_mirror/cur_profiles(mount_point)
    // fields2:该分区的type类型,例如可能是ext4(fs_type)
    return MountHandlerEntry(fields[0], fields[1], fields[2]);
}

让我们再来看看这个文件内容
基于Android13的系统启动流程分析(四)之SecondStageMain阶段可以看出来第一列是分区挂载的位置,第二列是哪个分区,第三列属于分区格式

8. SecondStageMain(int argc, char** argv)----->setup 7步骤

	// setup 7
    // 创建ActionManager对象和ServiceList对象
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    // 解析rc文件
    LoadBootScripts(am, sm);
  • 加载rc文件,保存到action manager和service list中
  • rc文件中:action 使用 ActionParser,而 service 使用 ServiceParser 解析
  • 主要解析rc中的:service,on,Import,包含了zygote.rc,路径:/system/bin/app_process64
  • 在文件系统挂载的第一阶段,system/vendor分区已经成功挂载,而其它分区的挂载则通过rc来挂载
  • rc中action的执行顺序:on early-initon initon late-initon property
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    // 创建解析器,只解析init.rc文件中的service,on,Import类型
    // action 使用 ActionParser,而 service 使用 ServiceParser 解析
    Parser parser = CreateParser(action_manager, service_list);
    // 获取ro.boot.init_rc属性值,此时该属性值应该是空的
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        // 解析/system/core/rootdir/init.rc
        // 这里的路径就是将/system/core/rootdir/init.rc 拷贝到out目录下
        parser.ParseConfig("/system/etc/init/hw/init.rc");
        ...
    } else {
        parser.ParseConfig(bootscript);
    }
}

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;
}

可以看到只需要解析init.rc文件中的service,on,import类型

9. SecondStageMain(int argc, char** argv)----->setup 8步骤

	// setup 8
    // 构建action和触发器(on early-init),放到event_queue,等待执行函数
   ...
    am.QueueEventTrigger("early-init");
    // 构建action和触发器(on init),放到event_queue,等待执行函数
    am.QueueEventTrigger("init");
    // 如果是充电模式则不需要挂载文件系统和不要启动核心服务
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
    // 运行所有属性触发器(action),例如 on property
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

把action加入队列中按顺序依次执行,继续一下rc文件中做了什么动作, rc中action的执行顺序:on early-initon initon late-initon nonencrypted(启动zygote)on property

9.1. on early-init
on early-init
   ...
    mkdir /acct/uid
	# 挂载linkerconfig(动态链接器)
    mount none /linkerconfig/bootstrap /linkerconfig bind rec
    # 启动ueventd(位于/system/bin/ueventd),ueventd是init启动的第一个进程
    start ueventd
    # memory.pressure_level used by lmkd
    chown root system /dev/memcg/memory.pressure_level
    chmod 0040 /dev/memcg/memory.pressure_level
    # app mem cgroups, used by activity manager, lmkd and zygote
    mkdir /dev/memcg/apps/ 0755 system system
    mkdir /dev/memcg/system 0550 system system
    mkdir /dev/net 0755 root root
    symlink ../tun /dev/net/tun
    ...
    # 挂载tracefs,可以通过指定方式到处trace日志,分析CPU和内存相关等问题
    mount tracefs tracefs /sys/kernel/tracing gid=3012

    # create sys dirctory
    # 创建/sys目录并指定权限
    mkdir /dev/sys 0755 system system
    mkdir /dev/sys/fs 0755 system system
    mkdir /dev/sys/block 0755 system system

可以看到针对lmkd(Low Memory Killer Daemon)以及app 创建用户组,创建目录,挂载tracefs:可以通过指定方式到处trace日志,分析CPU和内存相关等问题。
第一个启动的核心服务是:start ueventd,位于/system/bin/ueventd,ueventd是init启动的第一个服务进程

9.2. on init
on init
	...
    chmod 0775 /dev/cpuset/system-background
    chmod 0664 /dev/cpuset/foreground/tasks
    chmod 0664 /dev/cpuset/background/tasks
    chmod 0664 /dev/cpuset/system-background/tasks
    chmod 0664 /dev/cpuset/top-app/tasks
    chmod 0664 /dev/cpuset/restricted/tasks
    chmod 0664 /dev/cpuset/tasks
    chmod 0664 /dev/cpuset/camera-daemon/tasks
    # 挂载bpf
    mount bpf bpf /sys/fs/bpf nodev noexec nosuid
    mkdir /dev/fscklogs 0770 root system    
    ...
    # 允许system组读写电源状态
    chown system system /sys/power/state
    chown system system /sys/power/wakeup_count
    chmod 0660 /sys/power/state
    ...
    # 在运行其他进程之前需要先启动log服务,说明init中启动的服务,第一个启动的进程是ueventd
    start logd
    # 启用 Low Memory Killer Daemon(lmkd)
    # 1.基于Memory的CGroup进行进程的回收;2.作为frameworks与kernel的沟通桥梁传递参数与信息
    # Start lmkd before any other services run so that it can register them
    chown root system /sys/module/lowmemorykiller/parameters/adj
    chmod 0664 /sys/module/lowmemorykiller/parameters/adj
    chown root system /sys/module/lowmemorykiller/parameters/minfree
    chmod 0664 /sys/module/lowmemorykiller/parameters/minfree
    start lmkd

    # Start essential services.
    # 启用ServiceManager,管理各个服务,非常重要
    start servicemanager
    start hwservicemanager
    start vndservicemanager

可以看到第二个启动的核心服务是:start logd,日志系统。
第三个核心服务是:start lmkd,Low Memory Killer Daemon
作用:基于Memory的CGroup进行进程的回收,作为frameworks与kernel的沟通桥梁传递参数与信息。
接着启动了:servicemanagerhwservicemanagervndservicemanager,这些都属于核心服务
若核心服务未启动成功,那么其他服务将无法启动,系统将无法启动,其他服务必须依赖核心服务

9.3. on late-init
# 装载文件系统并启动核心系统服务
on late-init
    trigger early-fs
    # 触发on fs和on post-fs
    trigger fs
    trigger post-fs
    trigger late-fs
    trigger post-fs-data
    trigger load_persist_props_action
    trigger load_bpf_programs
    trigger zygote-start
    trigger firmware_mounts_complete
    trigger early-boot
    trigger boot

可以看到调用顺序为:启动系统on late-init会先执行,然后继续触发on fs,on post-fs ,on late-fs,on zygote-start ,on boot等,在调用on zygote-start后会解析zygote服务并指定class 名称,然后加入服务管理队列,后续等待调用on nonencrypted来启动zygote服务

9.4. on nonencrypted
on nonencrypted
    class_start main
    class_start late_start

目录:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    # class : 给服务指定一个类属
    class main
    priority -20
    # user 在执行此服务之前先切换用户名。当前默认为root.
    user root
    # 切换组名
    group root readproc reserved_disk
    # 在/dev/socket/下创建一个socket,并传递创建的文件描述符fd给服务进程
    # 其中type必须为dgram或stream,seqpacket.用户名和组名默认为0
    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
    writepid /dev/cpuset/foreground/tasks
    # oneshot : 当此服务退出时不会自动重启.
    # disabled:服务不会自动运行,必须显式地通过服务器来启动
    # 据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。
    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal

可以看到通过class_start main来启动主函数main,位于:frameworks/base/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
	...
	if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}

这里启动了zygote,并且携带参数启动了systemServer,关于zygote这里就不再详细分析了

二. 附录

rc文件中的command以及触发器,action等,对应的关系如下:

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}}},
    };

三. 总结

至此第二阶段就分析完毕了,一句话来总结:优先保证init进程的存活率(拉高优先级),处理设备是否unlock,决定是否卸载一些分区以及调整selinux规则和权限,创建并启动属性服务(实质上就是把属性值映射到全局内存中供所有进程访问,然后在创建socket等待进程来连接 实现更新和新增属性值),继续决定是否把vendor_overlay覆盖到/vendor分区中,然后持续监控/proc/mounts,如果有分区信息加入到该文件中则挂载此分区,接着解析rc文件(创建目录,修改权限,挂载分区,启动服务进程等),根据调用顺序启动核心服务(adbd,ueventd等)以及主服务(zygote)和其他服务文章来源地址https://www.toymoban.com/news/detail-423299.html

到了这里,关于基于Android13的系统启动流程分析(四)之SecondStageMain阶段的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android 13 网络 Adb相关流程深入分析研究

    通过代码分析发现Android13 上对 网络adb 进行了限制! Android13原生代码要求:必现连接上某个wifi,才能进行adb ,并且切换wifi或者关闭wifi都是会停止adb。 并且Android13 上 wifi adb 端口号每次是变化的,这个也是很不方便的! 如果要做成Android11 或者之前一样,设备连接WiFi/有线网

    2024年02月09日
    浏览(54)
  • Android SystemServer 启动流程分析

    和你一起终身学 习,这里是程序员Android 经典好文推荐,通过阅读本文,您将收获以下知识点: 一、SystemServer 启动的服务有哪些 二、SystemServer启动总体流程概述 三、SystemServer 如何启动,是谁启动的? 四、 SystemServer 启动入门 main 方法 五、SystemServer Run 方法初始与启动 六、

    2024年02月13日
    浏览(47)
  • Android 13 骁龙相机点击拍照流程分析(二)——点击拍照到存入相册

            本篇是在Android 13 骁龙相机点击拍照流程分析(一)——点击拍照到更新到左下角缩略图文章的基础上进行延申的,前面的预览、点击拍照的过程参考第一篇:Android 13 骁龙相机点击拍照流程分析(一)——点击拍照到更新到左下角缩略图-CSDN博客         从第一篇的

    2024年02月06日
    浏览(51)
  • android framework之Applicataion启动流程分析

    Application启动流程框架分析 启动方式一:通过Launcher启动app 启动方式二:在某一个app里启动第二个app的Activity. 以上两种方式均可触发app进程的启动。但无论哪种方式,最终通过通过调用AMS的startActivity()来启动application的。    根据上图分析, 要启动一个Application,需要涉及五

    2024年02月11日
    浏览(46)
  • Android 13 骁龙相机点击拍照流程分析(一)——点击拍照到更新到左下角缩略图

    由于最近客户定制需要将文件挂载类型修改为sdcardfs,由于修改了文件挂载类型,导致了骁龙相机拍照后不能点击进入相册,故对骁龙相机从点击事件开始进行问题的排查,此处不介绍最终的sdcardfs挂载后的问题解决方案 拍照的流程大概分为几个阶段:打开相机进行预览、点

    2024年02月04日
    浏览(53)
  • Android系统启动流程概览

    Boot Rom —— Bootloader —— Linux Kernel —— init进程 —— Zygote进程(dalvik/ art)—— systemServer —— Apps init 进程是Linux系统中,用户空间启动的第一个进程。 创建并挂载一些文件目录 启动属性服务 解析 init.rc 配置文件,启动 Zygote 进程 挂载 seLinux 文件目录,创建seLinux,加载

    2024年02月06日
    浏览(43)
  • Android系统启动流程 源码解析

    本文链接:https://blog.csdn.net/feather_wch/article/details/132518105 有道云脑图:https://note.youdao.com/s/GZ9d8vzO 1、整体流程 Boot Room BootLoader idle kthread init init ServiceManager zygote zygote SystemServer app 1、kernel/common/init/main.c 2、andorid.mk-android.bp编译 3、init是用户空间鼻祖 属于C、C++ Framework 1.1 启动源

    2024年02月11日
    浏览(51)
  • 笔记:Android 9系统启动流程

    当电源键按下时,引导芯片代码(汇编指令)会从预定的地方(固化在ROM)开始执行,将引导程序 BootLoader 加载到 RAM中,然后执行 BootLoader 是在 Android 操作系统开始前的一个小程序,主要作用是把系统OS拉起来并运行 位置: bootablebootloader 当 Linux系统被 BootLoader 程序拉起,

    2024年02月14日
    浏览(39)
  • Android T 远程动画显示流程其二——系统侧动画启动流程

    接着上篇文章分析 Android T 远程动画显示流程其一 下面,我们以从桌面点击一个应用启动的场景来分析远程动画的流程,窗口添加的流程见Android T WMS窗口相关流程 这里我们从AppTransitionController.handleAppTransitionReady方法开始跟踪代码流程 代码路径:framework/services/core/java/com/and

    2024年03月28日
    浏览(45)
  • Android13音频子系统分析(一)---整体架构

             目录 一、应用API层 二、Java框架层 三、Native核心层 3.1 AudioFlinger模块 3.2 AudioPolicyService模块 四、HAL层         本文基于AOSP13源码进行分析解读。所以与各个SoC平台厂商提供的运行在真实设备上的源码会有细微差异,但核心原理区别不大。         音频子系

    2024年02月07日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包