AndroidT(13) init 进程 -- second stage init 中的 Epoll (三)

1. 概览

在进入 second stage init 讲解之前,先来看看它事件监听及处理的机制 – Epoll 类,它实际上是对 epoll 的封装,使他变得更加适合再 init 中来跟踪事件以及分发触发方法等。整个类只有 4 个方法,在如此小巧的条件下实现了:事件监听的注册、卸载、跟踪以及收集事件的处理方法。这也意味着用户在注册后,在等待到事件后可以直接调用返回的处理方法列表。

int SecondStageMain(int argc, char** argv) {
    if (REBOOT_BOOTLOADER_ON_PANIC) {
        InstallRebootSignalHandlers();
    }

    // No threads should be spin up until signalfd
    // is registered. If the threads are indeed required,
    // each of these threads _should_ make sure SIGCHLD signal
    // is blocked. See b/223076262
    boot_clock::time_point start_time = boot_clock::now();

    trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };

    SetStdioToDevNull(argv);
    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";

    SelinuxSetupKernelLogging();

    // Update $PATH in the case the second stage init is newer than first stage init, where it is
    // first set.
    if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
        PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
    }

    // Init should not crash because of a dependence on any other process, therefore we ignore
    // SIGPIPE and handle EPIPE at the call site directly.  Note that setting a signal to SIG_IGN
    // is inherited across exec, but custom signal handlers are not.  Since we do not want to
    // ignore SIGPIPE for child processes, we set a no-op function for the signal handler instead.
    {
        struct sigaction action = {.sa_flags = SA_RESTART};
        action.sa_handler = [](int) {};
        sigaction(SIGPIPE, &action, nullptr);
    }

    // Set init and its forked children's oom_adj.
    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();
    }

    // Set up a session keyring that all processes will have access to. It
    // will hold things like FBE encryption keys. No process should override
    // its session keyring.
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);

    // Indicate that booting is in progress to background fw loaders, etc.
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

    // 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");
    bool load_debug_prop = false;
    if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
        load_debug_prop = "true"s == force_debuggable_env;
    }
    unsetenv("INIT_FORCE_DEBUGGABLE");

    // Umount the debug ramdisk so property service doesn't read .prop files from there, when it
    // is not meant to.
    if (!load_debug_prop) {
        UmountDebugRamdisk();
    }

    PropertyInit();

    // Umount second stage resources after property service has read the .prop files.
    UmountSecondStageRes();

    // Umount the debug ramdisk after property service has read the .prop files when it means to.
    if (load_debug_prop) {
        UmountDebugRamdisk();
    }

    // Mount extra filesystems required during second stage init
    MountExtraFilesystems();

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

    Epoll epoll;
    if (auto result = epoll.Open(); !result.ok()) {
        PLOG(FATAL) << result.error();
    }

    // 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.
    epoll.SetFirstCallback(ReapAnyOutstandingChildren);

    InstallSignalFdHandler(&epoll);
    InstallInitNotifier(&epoll);
    StartPropertyService(&property_fd);

    // Make the time that init stages started available for bootstat to log.
    RecordStageBoottimes(start_time);

    // Set libavb version for Framework-only OTA match in Treble build.
    if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) {
        SetProperty("ro.boot.avb_version", avb_version);
    }
    unsetenv("INIT_AVB_VERSION");

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

    const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
    Action::set_function_map(&function_map);

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

    InitializeSubcontext();

    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();

    // Make the GSI status available before scripts start running.
    auto is_running = android::gsi::IsGsiRunning() ? "1" : "0";
    SetProperty(gsi::kGsiBootedProp, is_running);
    auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
    SetProperty(gsi::kGsiInstalledProp, is_installed);
    if (android::gsi::IsGsiRunning()) {
        std::string dsu_slot;
        if (android::gsi::GetActiveDsu(&dsu_slot)) {
            SetProperty(gsi::kDsuSlotProp, dsu_slot);
        }
    }

    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
    am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
    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. Do not convert far_future into
        // std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock
        // is 1ns.
        const boot_clock::time_point far_future = boot_clock::time_point::max();
        boot_clock::time_point next_action_time = far_future;

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

        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) {
                next_action_time = boot_clock::now();
            }
        }
        // Since the above code examined pending actions, no new actions must be
        // queued by the code between this line and the Epoll::Wait() call below
        // without calling WakeMainInitThread().
        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) {
                next_action_time = std::min(next_action_time, *next_process_action_time);
            }
        }

        std::optional<std::chrono::milliseconds> epoll_timeout;
        if (next_action_time != far_future) {
            epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                    std::max(next_action_time - boot_clock::now(), 0ns));
        }
        auto epoll_result = epoll.Wait(epoll_timeout);
        if (!epoll_result.ok()) {
            LOG(ERROR) << epoll_result.error();
        }
        if (!IsShuttingDown()) {
            HandleControlMessages();
            SetUsbController();
        }
    }

    return 0;
}
**函数概述:**

`SecondStageMain` 函数是 Android 系统的第二阶段初始化程序,它负责完成系统的启动过程并加载其他服务和应用程序。

**函数参数:**

* `argc`: 命令行参数的数量。
* `argv`: 命令行参数的数组。

**函数流程:**

1. **初始化第二阶段环境:**
    * 设置信号处理程序,以便在收到某些信号时采取相应的操作。
    * 设置进程的优先级。
    * 设置环境变量。
    * 安装文件系统。

2. **加载属性服务:**
    * 加载属性服务,用于管理系统属性。

3. **挂载额外文件系统:**
    * 挂载必要的额外文件系统。

4. **设置 SELinux 上下文:**
    * 初始化 SELinux 并设置 SELinux 上下文。

5. **创建 Epoll 实例:**
    * 创建一个 Epoll 实例,用于监听文件描述符事件。

6. **安装信号处理程序和初始化程序通知程序:**
    * 安装信号处理程序,以便在收到某些信号时采取相应的操作。
    * 安装初始化程序通知程序,以便在收到某些通知时采取相应的操作。

7. **启动属性服务:**
    * 启动属性服务。

8. **记录引导时间:**
    * 记录引导阶段的开始时间。

9. **设置 libavb 版本:**
    * 设置 libavb 版本,用于在 Treble 构建中进行 Framework-only OTA 匹配。

10. **挂载供应商覆盖文件系统:**
    * 挂载供应商覆盖文件系统。

11. **导出 OEM 锁定状态:**
    * 导出 OEM 锁定状态。

12. **设置 USB 控制器:**
    * 设置 USB 控制器。

13. **设置内核版本:**
    * 设置内核版本。

14. **设置内置函数映射:**
    * 设置内置函数映射,用于将内置函数名映射到对应的函数指针。

15. **初始化子上下文:**
    * 初始化子上下文。

16. **初始化操作管理器和服务列表:**
    * 初始化操作管理器和服务列表。

17. **加载引导脚本:**
    * 加载引导脚本。

18. **队列内置操作:**
    * 将一些内置操作加入到队列中,这些操作将在稍后执行。

19. **触发事件:**
    * 触发 "init" 事件,以便执行与该事件关联的操作。

20. **根据引导模式采取不同的操作:**
    * 根据引导模式的不同,采取不同的操作。

21. **运行属性触发器:**
    * 运行属性触发器,以便执行与当前属性状态关联的操作。

22. **进入主循环:**
    * 进入主循环,监听文件描述符事件并执行相应的操作。

**函数返回值:**

返回 0 表示程序执行成功。

2.整体使用骨架

second init 中就是使用 Epoll 来跟踪事件并处理的,下面看下 Epoll 的使用流程。

//system\core\init\init.cpp
SecondStageMain
    //code 1
    Epoll epoll;
    epoll.Open();
    //code 2
    InstallInitNotifier(&epoll); 
        auto clear_eventfd = [] {
            uint64_t counter;
            TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
        };
        epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd)
    //code 3
    while (true) {
        auto pending_functions = epoll.Wait(epoll_timeout);
        for (const auto& function : *pending_functions) {
            (*function)();
        }
    }

2.1 Epoll 的实例化 – code1

Epoll 构造方法使用的是空的并没做什么,再 Open 中也是很简单的申请了一个 epoll 的句柄,并记录在 epoll_fd_类变量中。

Result<void> Epoll::Open() {
    if (epoll_fd_ >= 0) return {};
    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));

    if (epoll_fd_ == -1) {
        return ErrnoError() << "epoll_create1 failed";
    }
    return {};
}

2.2 注册监听对象及处理方法 – code2

Epoll 的监听对象只能是 fd 也就是文件句柄,它的回调方法原型如下

//system\core\init\epoll.h
typedef std::function<void()> Handler;

准备好监听对象和它的处理方法后就可以调用 RegisterHandler 注册到 Epoll 中了,来看看它的定义

Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {
    if (!events) {
        return Error() << "Must specify events";
    }

    auto [it, inserted] = epoll_handlers_.emplace(
            fd, Info{
                        .events = events,
                        .handler = std::move(handler),
                });
    if (!inserted) {
        return Error() << "Cannot specify two epoll handlers for a given FD";
    }
    epoll_event ev = {
            .events = events,
            .data.fd = fd,
    };
    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fd, &ev) == -1) {
        Result<void> result = ErrnoError() << "epoll_ctl failed to add fd";
        epoll_handlers_.erase(fd);
        return result;
    }
    return {};
}
1. **参数检查:**
   * 首先,代码检查传入的events参数是否为0。如果为0,则返回一个错误信息,指出必须指定事件。这是因为epoll事件循环需要知道对文件描述符感兴趣的事件类型,以便在该事件发生时通知处理程序。

2. **插入处理程序:**
   * 接下来,代码使用std::unordered_map::emplace方法将文件描述符和处理程序信息插入到epoll_handlers_映射中。std::unordered_map是一种哈希表,它可以快速查找和插入元素。
   * Info结构体包含了事件类型和处理程序信息。
   * emplace方法可以确保只有当文件描述符不存在于映射中时才进行插入操作。如果文件描述符已经存在,则返回一个std::pair对象,其中first是映射中的元素,second是插入是否成功的标志。
   * 如果插入操作成功,则返回true,否则返回false3. **创建epoll事件:**
   * 在插入处理程序之后,代码创建一个epoll事件结构体ev。
   * ev结构体包含了事件类型和文件描述符信息。
   * 事件类型与Info结构体中存储的事件类型相同。
   * 文件描述符是传入的fd参数。

4. **添加epoll事件:**
   * 接下来,代码使用epoll_ctl系统调用将epoll事件添加到epoll事件循环中。
   * epoll_ctl系统调用有三个参数:epoll文件描述符、操作类型和epoll事件结构体。
   * 操作类型是EPOLL_CTL_ADD,表示要将epoll事件添加到epoll事件循环中。
   * epoll事件结构体是ev。
   * 如果epoll_ctl系统调用成功,则返回0,否则返回-15. **错误处理:**
   * 如果epoll_ctl系统调用失败,则代码返回一个ErrnoError对象。
   * ErrnoError对象包含了错误信息和错误代码。
   * 然后,代码从epoll_handlers_映射中删除该文件描述符,以确保映射中没有不正确的条目。

6. **返回结果:**
   * 最后,代码返回一个Result对象。
   * Result对象可以是Ok或Err。
   * 如果epoll_ctl系统调用成功,则返回Ok()* 如果epoll_ctl系统调用失败,则返回Err(error)**总而言之,这段代码用于在epoll事件循环中注册一个新的文件描述符和对应的处理程序。如果注册成功,则返回Ok(),否则返回Err(error)** 

可见注册分为如下几个步骤
    a) 构建 Info 信息,其中包括它对 fd 中关心的事件类型,比如默认值 EPOLLIN,如果来这个事件就意味着 fd 句柄有数据可以读取了。还有用来处理 fd 数据的回调方法 handler。最后将 Info 和 fd 传入到 map epoll_handlers_中去,供后续使用。
    b) 设置 epoll_event 事件,该数据结构则是使用 epoll 的的标准构造方法,其中记录的 events 则是供 kernel 使用的和上面提到的 info.events值一致。并将 Info 实例作为它的私有数据。
    c) 最后调用 epoll_ctl 将 fd 添加到 epoll_fd_中去,如此系统才会对该 fd 实现监听。

2.3 等待事件并返回处理方法 – code3

可见 Wait 方法会被循环调用不做退出的,这部分的概念和 Looper 还是很相似的,一个线程中只存在一个 Epoll中,并且 Epoll 的回调方法也是该线程执行的。下面就来看看它的实现

Result<int> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout) {
    int timeout_ms = -1;
    if (timeout && timeout->count() < INT_MAX) {
        timeout_ms = timeout->count();
    }
    const auto max_events = epoll_handlers_.size();
    epoll_event ev[max_events];
    auto num_events = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_.get(), ev, max_events, timeout_ms));
    if (num_events == -1) {
        return ErrnoError() << "epoll_wait failed";
    }
    if (num_events > 0 && first_callback_) {
        first_callback_();
    }
    for (int i = 0; i < num_events; ++i) {
        const auto it = epoll_handlers_.find(ev[i].data.fd);
        if (it == epoll_handlers_.end()) {
            continue;
        }
        const Info& info = it->second;
        if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
            (ev[i].events & EPOLLIN) != ev[i].events) {
            // This handler wants to know about exception events, and just got one.
            // Log something informational.
            LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
        }
        info.handler();
        for (auto fd : to_remove_) {
            epoll_handlers_.erase(fd);
        }
        to_remove_.clear();
    }
    return num_events;
}
1. **初始化变量:**
   * 首先,代码初始化一些变量:
     * timeout_ms:epoll_wait系统调用的超时时间,单位是毫秒。如果timeout参数没有提供,或者timeout参数大于INT_MAX,则timeout_ms设置为-1,表示没有超时。
     * max_events:epoll事件的最大数量。这是epoll_handlers_映射的大小。
     * ev:一个epoll_event结构体数组,用于存储epoll事件。
     * num_events:epoll_wait系统调用的返回值,表示发生的事件数量。

2. **调用epoll_wait系统调用:**
   * 接下来,代码使用epoll_wait系统调用等待epoll事件发生。
   * epoll_wait系统调用有四个参数:epoll文件描述符、epoll事件数组、epoll事件的最大数量和超时时间。
   * epoll文件描述符是epoll_fd_。
   * epoll事件数组是ev。
   * epoll事件的最大数量是max_events。
   * 超时时间是timeout_ms。
   * epoll_wait系统调用会阻塞,直到epoll事件发生或超时。

3. **错误处理:**
   * 如果epoll_wait系统调用返回-1,则代码返回一个ErrnoError对象。
   * ErrnoError对象包含了错误信息和错误代码。

4. **处理事件:**
   * 如果epoll_wait系统调用成功,则代码开始处理发生的事件。
   * 首先,如果num_events大于0并且first_callback_不为空,则调用first_callback_函数。
   * first_callback_函数是一个回调函数,它将在epoll事件循环中接收到第一个事件时被调用。
   * 接下来,代码遍历发生的事件,并对每个事件调用相应的处理程序。
   * 处理程序是epoll_handlers_映射中存储的Info结构体中的handler成员。
   * 在调用处理程序之前,代码还会检查该事件是否属于该处理程序感兴趣的事件类型。
   * 如果该事件不属于该处理程序感兴趣的事件类型,则代码会继续处理下一个事件。
   * 在处理完所有发生的事件之后,代码会从epoll_handlers_映射中删除所有需要删除的文件描述符。
   * 需要删除的文件描述符存储在to_remove_列表中。

5. **返回结果:**
   * 最后,代码返回num_events,表示发生的事件数量。

**总而言之,这段代码用于等待epoll事件循环中的事件发生,并处理这些事件。如果等待成功,则返回发生的事件数量,否则返回ErrnoError对象。** 

3. 总结

可见使用了 Epoll 后,只要简单的几个步骤即可,作为框架使用还是相当方便的,其中隐藏了 epoll 的数据构造,用户只要提供 监听对象和对于的触发方法即可。不过值得注意的是,这是单线程的,如果注册太多的监听对象,或者某一个处理方法中耗时过长,还是相当影响其他监听事件的处理的。

Android 系统的第二阶段初始化程序创建一个 Epoll 实例,用于监听文件描述符事件,具体是为了以下几个目的:

  • 处理输入/输出事件: Epoll 实例可以监听文件描述符的输入/输出事件,例如,当用户输入数据或应用程序读取数据时。当发生输入/输出事件时,Epoll 实例会通知相应的应用程序或进程进行处理。
  • 处理信号事件: Epoll 实例也可以监听信号事件,例如,当应用程序收到终止信号时。当发生信号事件时,Epoll 实例会通知相应的应用程序或进程进行处理。
  • 处理定时器事件: Epoll 实例还可以监听定时器事件,例如,当某个定时器到期时。当发生定时器事件时,Epoll 实例会通知相应的应用程序或进程进行处理。

通过使用 Epoll 实例监听文件描述符事件,Android 系统可以及时响应用户的输入、应用程序的请求以及其他事件,从而提高系统的响应速度和性能。

例如,在 Android 系统中,当用户点击屏幕时,系统会将点击事件转换为一个文件描述符事件,然后通过 Epoll 实例通知相应的应用程序或进程进行处理。应用程序或进程收到事件通知后,会根据需要进行相应的操作,例如,打开一个新的窗口或启动一个新的服务。

又例如,在 Android 系统中,定时器服务会使用 Epoll 实例来监听定时器事件。当某个定时器到期时,定时器服务会将定时器事件转换为一个文件描述符事件,然后通过 Epoll 实例通知相应的应用程序或进程进行处理。应用程序或进程收到事件通知后,会根据需要进行相应的操作,例如,执行某个任务或发送一条消息。

总之,Android 系统的第二阶段初始化程序创建一个 Epoll 实例,用于监听文件描述符事件,是为了提高系统的响应速度和性能,并为应用程序提供一种高效的事件处理机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值