Android 12 init Subcontext进程工作过程分析

Posted pecuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 12 init Subcontext进程工作过程分析相关的知识,希望对你有一定的参考价值。

文章托管在gitee上 Android Notes , 同步csdn
本文基于android12 分析

概述

在init启动过程中,会启动一个subcontext进程,通常与init有着不一样的 secontext 以及 mount namespace。该进程用来接收来自init的命令,用来执行某些操作,这些操作是在 subcontext 的secontext 和 mount namespace 下进行。通过ps命令看看init及subcontext进程信息

# ps -AZ | grep init                                                                                                            
u:r:init:s0                    root             1     0 10904472  3956 do_epoll_wait       0 S init   # 这个是 init 进程
u:r:vendor_init:s0             root           166     1 10780496  1924 do_sys_poll         0 S init   # 这个是 subcontext 进程

Subcontext 进程启动与初始化

Subcontext 进程是在init启动第二阶段进行启动和初始化的,在SecondStageMain函数中调用InitializeSubcontext去完成相关操作。

SecondStageMain

int SecondStageMain(int argc, char** argv) 
  ...
  const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(); // 内置函数表
  Action::set_function_map(&function_map); // 设置 Action 需要的函数映射表
  ...
  if (!SetupMountNamespaces())  // 初始化 Mount Namespaces
    PLOG(FATAL) << "SetupMountNamespaces failed";
  

  InitializeSubcontext(); // 初始化Subcontext

  ActionManager& am = ActionManager::GetInstance(); // 创建action管理器
  ServiceList& sm = ServiceList::GetInstance(); // 创建服务管理列表

  LoadBootScripts(am, sm); // 加载并解析启动脚本
  ...

SetupMountNamespaces

  • 如果apex可更新并且不是recovery模式,则会创建一个新的mount namespace,被记为default,而原始的则称为 bootstrap
  • 否则,default 和 bootstrap 的mount namespace是同一个
bool SetupMountNamespaces() 
    // Set the propagation type of / as shared so that any mounting event (e.g.
    // /data) is by default visible to all processes. When private mounting is
    // needed for /foo/bar, then we will make /foo/bar as a mount point (by
    // bind-mounting by to itself) and set the propagation type of the mount
    // point to private.
    if (!ChangeMount("/", MS_SHARED | MS_REC)) return false; // 将根 / 挂载为共享

    // /apex is a private mountpoint to give different sets of APEXes for
    // the bootstrap and default mount namespaces. The processes running with
    // the bootstrap namespace get APEXes from the read-only partition.
    if (!(ChangeMount("/apex", MS_PRIVATE))) return false;  // 将/apex 挂载为私有

    // /linkerconfig is a private mountpoint to give a different linker configuration
    // based on the mount namespace. Subdirectory will be bind-mounted based on current mount
    // namespace
    if (!(ChangeMount("/linkerconfig", MS_PRIVATE))) return false; // 将/linkerconfig 挂载为私有
    ...
    // 保存当前的mount namespace 为 bootstrap
    bootstrap_ns_fd.reset(OpenMountNamespace());
    bootstrap_ns_id = GetMountNamespaceId();

    // When APEXes are updatable (e.g. not-flattened), we create separate mount
    // namespaces for processes that are started before and after the APEX is
    // activated by apexd. In the namespace for pre-apexd processes, small
    // number of essential APEXes (e.g. com.android.runtime) are activated.
    // In the namespace for post-apexd processes, all APEXes are activated.
    bool success = true;
    if (IsApexUpdatable() && !IsRecoveryMode())  // 如果apex可更新并且不是recovery模式
        // Creating a new namespace by cloning, saving, and switching back to
        // the original namespace.
        if (unshare(CLONE_NEWNS) == -1)  // 创建新的mount namespace
            PLOG(ERROR) << "Cannot create mount namespace";
            return false;
        
        // 将新的 mount namespace 保存为 default
        default_ns_fd.reset(OpenMountNamespace());
        default_ns_id = GetMountNamespaceId();

        if (setns(bootstrap_ns_fd.get(), CLONE_NEWNS) == -1)  // 将init重新设置回 bootstrap
            PLOG(ERROR) << "Cannot switch back to bootstrap mount namespace";
            return false;
        
     else  // 否则 default 和 bootstrap 同 mount namespace
        // Otherwise, default == bootstrap
        default_ns_fd.reset(OpenMountNamespace());
        default_ns_id = GetMountNamespaceId();
    
#ifdef ACTIVATE_FLATTENED_APEX
    success &= ActivateFlattenedApexesIfPossible();
#endif
    LOG(INFO) << "SetupMountNamespaces done";
    return success;

关于 mount namespace 可参考文章 浅谈Linux Namespace机制(一)

InitializeSubcontext

创建subcontext进程,用于执行init交给其的一些任务(比如后面执行的某些Command),通过socket与init进行通信

  • 在Android P 开始才创建 subcontext
  • 使用的secontext是 kVendorContext, 即 u:r:vendor_init:s0
/// @system/core/init/subcontext.cpp
void InitializeSubcontext() 
    if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_P__)  // android s api 31 > android p api 28
        subcontext.reset( // 创建Subcontext, path_prefixes为"/vendor", "/odm"。kVendorContext u:r:vendor_init:s0
                new Subcontext(std::vector<std::string>"/vendor", "/odm", kVendorContext));
    


/// @system/core/init/subcontext.h
Subcontext(std::vector<std::string> path_prefixes, std::string context, bool host = false)
    : path_prefixes_(std::move(path_prefixes)), context_(std::move(context)), pid_(0) 
    if (!host)  /// host默认为false
        Fork(); // 创建subcontext进程
    

Subcontext::Fork

  • 创建Socket pair,用于subcontext与init进行通信
  • fork() 创建子进程 subcontext
  • subcontext通过setexeccon设置安全上下文,通过setns 设置为默认mount space
  • 通过execv执行/system/bin/init 进入subcontext业务逻辑
/// @system/core/init/subcontext.cpp
void Subcontext::Fork() 
    unique_fd subcontext_socket;
    // 创建 socket对,用于与init进行通信
    if (!Socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, &socket_, &subcontext_socket)) 
        LOG(FATAL) << "Could not create socket pair to communicate to subcontext";
        return;
    

    auto result = fork(); // 创建子进程 subcontext

    if (result == -1) 
        LOG(FATAL) << "Could not fork subcontext";
     else if (result == 0)  // subcontext 进程
        socket_.reset(); // 关闭init的 fd

        // We explicitly do not use O_CLOEXEC here, such that we can reference this FD by number
        // in the subcontext process after we exec.
        int child_fd = dup(subcontext_socket);  // NOLINT(android-cloexec-dup)
        if (child_fd < 0) 
            PLOG(FATAL) << "Could not dup child_fd";
        

        // We don't switch contexts if we're running the unit tests.  We don't use std::optional,
        // since we still need a real context string to pass to the builtin functions.
        if (context_ != kTestContext) 
            if (setexeccon(context_.c_str()) < 0)  // 设置安全上下文 Set exec security context for the next execve
                PLOG(FATAL) << "Could not set execcon for '" << context_ << "'";
            
        
#if defined(__ANDROID__)
        // subcontext init runs in "default" mount namespace
        // so that it can access /apex/*
        if (auto result = SwitchToMountNamespaceIfNeeded(NS_DEFAULT); !result.ok()) // 切换到 default mount namespace
            LOG(FATAL) << "Could not switch to \\"default\\" mount namespace: " << result.error();
        
#endif
        auto init_path = GetExecutablePath(); // /system/bin/init
        auto child_fd_string = std::to_string(child_fd);
        const char* args[] = init_path.c_str(), "subcontext", context_.c_str(), // 注意此处传入的参数 subcontext
                              child_fd_string.c_str(), nullptr;
        execv(init_path.data(), const_cast<char**>(args)); // execv 执行init

        PLOG(FATAL) << "Could not execv subcontext init";
     else  // init 进程
        subcontext_socket.reset();  // 关闭subcontext的 fd
        pid_ = result;
        LOG(INFO) << "Forked subcontext for '" << context_ << "' with pid " << pid_;
    

SwitchToMountNamespaceIfNeeded

/// @system/core/init/mount_namespace.cpp
Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace) 
    if (IsRecoveryMode() || !IsApexUpdatable()) 
        // we don't have multiple namespaces in recovery mode or if apex is not updatable
        return ;
    
    const auto& ns_id = target_mount_namespace == NS_BOOTSTRAP ? bootstrap_ns_id : default_ns_id;
    const auto& ns_fd = target_mount_namespace == NS_BOOTSTRAP ? bootstrap_ns_fd : default_ns_fd;
    const auto& ns_name = target_mount_namespace == NS_BOOTSTRAP ? "bootstrap" : "default";
    // 读取link /proc/self/ns/mnt
    // lrwxrwxrwx 1 root root 0 2022-12-16 15:30 /proc/164/ns/mnt -> mnt:[4026532713]
    if (ns_id != GetMountNamespaceId() && ns_fd.get() != -1) 
        if (setns(ns_fd.get(), CLONE_NEWNS) == -1)  // 调用 setns 设置mount namespace
            return ErrnoError() << "Failed to switch to " << ns_name << " mount namespace.";
        
    
    return ;

subcontext入口在init main方法中,通过参数可以知道启动的是哪个,对于subcontext则会调用SubcontextMain函数

/// @system/core/init/main.cpp
int main(int argc, char** argv) 
    ...
    if (argc > 1) 
        if (!strcmp(argv[1], "subcontext"))  // 此处。 subcontext 子进程入口
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);  // 调用 SubcontextMain
        

        if (!strcmp(argv[1], "selinux_setup")) // selinux初始化阶段
            return SetupSelinux(argv);
        

        if (!strcmp(argv[1], "second_stage")) // 启动第二阶段
            return SecondStageMain(argc, argv);
        
    

    return FirstStageMain(argc, argv); // 启动第一阶段

SubcontextMain

/// @system/core/init/subcontext.cpp
int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map) 
    if (argc < 4) LOG(FATAL) << "Fewer than 4 args specified to subcontext (" << argc << ")";

    auto context = std::string(argv[2]); // 默认是 kVendorContext,u:r:vendor_init:s0
    auto init_fd = std::atoi(argv[3]); // socket fd

    SelabelInitialize(); // selable 初始化
    // 设置关机命令lambda
    trigger_shutdown = [](const std::string& command)  shutdown_command = command; ;
    // 创建 SubcontextProcess 对象
    auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
    // Restore prio before main loop
    setpriority(PRIO_PROCESS, 0, 0);
    subcontext_process.MainLoop(); // 调用其 MainLoop 函数
    return 0;

SubcontextProcess::MainLoop

进入主循环,等待init消息并处理相关请求。

  • poll等待读事件的发生,通常是init发送相关命令请求
  • ReadMessage(init_fd_) 读取来自 init 的消息
  • subcontext_command.ParseFromString 解析init消息
  • 根据Command类别执行不同的操作
    • kExecuteCommand 执行指定的命令
    • kExpandArgsCommand 展开给定的参数。
  • SendMessage回复init执行结果
/// @system/core/init/subcontext.cpp
void SubcontextProcess::MainLoop() 
    pollfd ufd[1];
    ufd[0].events = POLLIN;
    ufd[0].fd = init_fd_; // init socket fd,监听相关事件

    while (true) 
        ufd[0].revents = 0;
        int nr = TEMP_FAILURE_RETRY(poll(ufd, arraysize(ufd), -1)); // poll等待事件发生
        if (nr == 0) continue;
        if (nr < 0) 
            PLOG(FATAL) << "poll() of subcontext socket failed, continuing";
        

        auto init_message = ReadMessage(init_fd_); // 读取来自 init 的消息
        if (!init_message.ok()) 
            if (init_message.error().code() == 0) 
                // If the init file descriptor was closed, let's exit quietly. If
                // this was accidental, init will restart us. If init died, this
                // avoids calling abort(3) unnecessarily.
                return;
            
            LOG(FATAL) << "Could not read message from init: " << init_message.error();
        

        auto subcontext_command = SubcontextCommand();
        if (!subcontext_command.ParseFromString(*init_message))  // 解析init消息
            LOG(FATAL) << "Unable to parse message from init";
        

        auto reply = SubcontextReply();
        switch (subcontext_command.command_case()) 
            case SubcontextCommand::kExecuteCommand:  // 执行命令
                RunCommand(subcontext_command.execute_command(), &reply);
                break;
            
            case SubcontextCommand::kExpandArgsCommand:  // 展开参数
                ExpandArgs(subcontext_command.expand_args_command(), &reply);
                break;
            
            default:
                LOG(FATAL) << "Unknown message type from init: "
                           << subcontext_command.command_case();
        

        if (!shutdown_command.empty())  // shutdown命令不为空,回写shutdown消息
            reply.set_trigger_shutdown(shutdown_command);
            shutdown_command.clear();
        
        // 发送回复消息到 init
        if (auto result = SendMessage(init_fd_, reply); !result.ok()) 
            LOG(FATAL) << "Failed to send message to init: " << result.error();
        
    

ReadMessage

从socket 中读取消息, 存储到 string

/// @system/core/init/proto_utils.h
inline Result<std::string> ReadMessage(int socket) 
    char buffer[kBufferSize] = ;
    auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
    if (result == 0) 
        return Error();
     else if (result < 0) 
        return ErrnoError();
    
    return std::string(buffer, result);

SendMessage

将消息序列化为string,然后通过socket发送

template <typename T>
Result<void> SendMessage(int socket, const T& message) 
    std::

以上是关于Android 12 init Subcontext进程工作过程分析的主要内容,如果未能解决你的问题,请参考以下文章

Android 12 init Subcontext进程工作过程分析

Android 12 init Subcontext进程工作过程分析

Android 12 init 启动流程分析

Android 12 init 启动流程分析

Android 12 init rc脚本解析和事件执行流程

Android 12 init rc脚本解析和事件执行流程