Skip to content

服务启动流程

追踪 Service::Start 从路径展开、资源预创建、fork/clone、cgroup 同步到子进程属性设置和 execv 的真实调用链。

基于android-17.0.0_r1
AndroidinitServiceforkexeccgroup

服务启动流程 ​

本文面向已经读过 Service对象、Service解析 和 Init主循环 的读者。前文解释了 Service 的字段和所有权;本文回答一个更窄、更可验证的问题:start foo 之后,init 如何把 Service 对象变成一个已经进入 execv 前后同步点的子进程?

本文不展开服务崩溃后的完整 Reap() 重启策略,也不把 init.svc.foo=running 当作业务 ready。主线是 Start() 的正常路径,同时覆盖路径展开失败、资源创建失败、fork 失败、cgroup 创建失败、子进程属性失败和 exec 失败;读完后应能按日志和源码判断失败发生在父进程、子进程还是 exec 之后。

1. 启动总线 ​

Service::Start() 不是一次系统调用,而是两个进程和两条单向 FIFO 协作的协议:父进程创建 cgroup 并通知子进程,子进程完成 console session 后通知父进程。两条 FIFO 分开,是为了避免父子双方同时写同一个管道产生竞态。

图中的 running 通知位于父进程自身的同步完成之后,但父进程提交和子进程继续执行是并发分支。非 console 服务没有“exec 成功回执”;即使是 console 服务,kSetSidFinished 也只证明 setsid() 已完成。因此 running 既不证明 execv 成功,也不代表目标程序已经进入业务初始化。

2. 前置检查 ​

2.1 更新服务 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Start

cpp
Result<void> Service::Start() {
    auto reboot_on_failure = make_scope_guard([this] {
        if (on_failure_reboot_target_) trigger_shutdown(*on_failure_reboot_target_);
    });

    if (is_updatable() && !IsDefaultMountNamespaceReady()) {
        ServiceList::GetInstance().DelayService(*this);
        return Error() << "Cannot start an updatable service '" << name_
                       << "' before configs from APEXes are all loaded. Queued for execution.";
    }

    bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
    ResetFlagsForStart();

    if (flags_ & SVC_RUNNING) {
        if ((flags_ & SVC_ONESHOT) && disabled) flags_ |= SVC_RESTART;
        reboot_on_failure.Disable();
        return {};
    }

updatable Service 在默认 mount namespace 未准备好时不会盲目启动,而是交给 ServiceList::DelayService();已经运行的服务再次 start 不是错误,oneshot 停止中的服务则设置 SVC_RESTART,等待回收路径处理。

2.2 路径与上下文 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Start

cpp
InterprocessFifo cgroups_activated, setsid_finished;
OR_RETURN(cgroups_activated.Initialize());
OR_RETURN(setsid_finished.Initialize());

if (Result<void> result = CheckConsole(); !result.ok()) return result;

auto expanded_path = ExpandProps(args_[0]);
if (!expanded_path.ok()) {
    flags_ |= SVC_DISABLED;
    return ErrnoError() << "Cannot expand path: " << expanded_path.error();
}
args_[0] = *expanded_path;

struct stat sb;
if (stat(args_[0].c_str(), &sb) == -1) {
    flags_ |= SVC_DISABLED;
    return ErrnoError() << "Cannot find '" << args_[0] << "'";
}

std::string scon;
if (!seclabel_.empty()) {
    scon = seclabel_;
} else {
    auto result = ComputeContextFromExecutable(args_[0]);
    if (!result.ok()) return result.error();
    scon = *result;
}

路径属性展开、文件存在性和 SELinux 执行上下文都发生在 fork 前。路径不存在会把 Service 标为 disabled;显式 seclabel 跳过从可执行文件计算 context,但不会跳过子进程后续 setexeccon。

3. 资源预创建 ​

3.1 描述符 ​

相关源码:

  • system/core/init/service.cpp
  • Service::Start
  • system/core/init/service_utils.cpp
  • SocketDescriptor::Create
  • FileDescriptor::Create
cpp
std::vector<Descriptor> descriptors;
for (const auto& socket : sockets_) {
    if (auto result = socket.Create(scon); result.ok()) {
        descriptors.emplace_back(std::move(*result));
    } else {
        LOG(INFO) << "Could not create socket '" << socket.name << "': " << result.error();
    }
}

for (const auto& file : files_) {
    if (auto result = file.Create(); result.ok()) {
        descriptors.emplace_back(std::move(*result));
    } else {
        LOG(INFO) << "Could not open file '" << file.name << "': " << result.error();
    }
}

socket/file 创建失败只记录日志,当前 Start() 仍可继续 fork;因此 rc 中声明 socket 不是 socket 必然存在的证明。成功返回的 Descriptor 仍由父进程持有,fork 后由子进程发布环境变量。

3.2 发布环境 ​

源码文件:system/core/init/service_utils.cpp

相关函数/类型:Descriptor::Publish

cpp
void Descriptor::Publish() const {
    auto published_name = name_;
    for (auto& c : published_name) c = isalnum(c) ? c : '_';

    int fd = fd_.get();
    auto fd_flags = fcntl(fd, F_GETFD);
    fd_flags &= ~FD_CLOEXEC;
    if (fcntl(fd, F_SETFD, fd_flags) != 0) {
        PLOG(ERROR) << "Failed to remove CLOEXEC from '" << published_name << "'";
    }

    std::string val = std::to_string(fd);
    setenv(published_name.c_str(), val.c_str(), 1);
}

FD 默认带 CLOEXEC,只有在子进程 exec 前显式清除,目标程序才能继承;环境变量值是 fd 数字而不是路径。SocketDescriptor::Create() 返回 ANDROID_SOCKET_<name>,FileDescriptor::Create() 返回 ANDROID_FILE_<path> 形式的变量。

4. 分叉同步 ​

4.1 fork与clone ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Start

cpp
pid_t pid = -1;
if (namespaces_.flags) {
    pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
} else {
    pid = fork();
}

if (pid == 0) {
    umask(077);
    cgroups_activated.CloseWriteFd();
    setsid_finished.CloseReadFd();
    RunService(descriptors, std::move(cgroups_activated), std::move(setsid_finished));
    _exit(127);
} else {
    cgroups_activated.CloseReadFd();
    setsid_finished.CloseWriteFd();
}

if (pid < 0) {
    pid_ = 0;
    return ErrnoError() << "Failed to fork";
}

有 namespace flags 时使用 clone,否则使用 fork;两者都只把 SIGCHLD 作为退出通知。父子进程各自关闭 FIFO 不拥有的一端,避免 EOF 和同步方向被错误解释。

4.2 FIFO语义 ​

相关源码:

  • system/core/init/interprocess_fifo.cpp
  • Initialize
  • Read
  • Write
cpp
Result<void> InterprocessFifo::Initialize() noexcept {
    if (fds_[0] >= 0) return Error() << "already initialized";
    if (pipe(fds_.data()) < 0) return ErrnoError() << "pipe()";
    return {};
}

Result<void> InterprocessFifo::Write(uint8_t byte) noexcept {
    ssize_t written = write(fds_[1], &byte, 1);
    if (written < 0) return ErrnoError() << "write()";
    if (written == 0) return Error() << "write() EOF";
    return {};
}

cgroups_activated 是父到子的通知,setsid_finished 是子到父的通知。两条通道各只传一个状态字节,协议简单但有明确 owner;任何一端关闭或读到错误都应被视为启动同步失败。

5. 子进程准备 ​

5.1 资源等待 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::RunService

cpp
void Service::RunService(const std::vector<Descriptor>& descriptors,
                         InterprocessFifo cgroups_activated,
                         InterprocessFifo setsid_finished) {
    if (auto result = EnterNamespaces(namespaces_, name_, mount_namespace_); !result.ok()) {
        LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
    }

    for (const auto& [key, value] : once_environment_vars_) setenv(key.c_str(), value.c_str(), 1);
    for (const auto& [key, value] : environment_vars_) setenv(key.c_str(), value.c_str(), 1);
    for (const auto& descriptor : descriptors) descriptor.Publish();

    if (auto result = WritePidToFiles(&writepid_files_); !result.ok()) {
        LOG(ERROR) << "failed to write pid to files: " << result.error();
    }

    Result<uint8_t> byte = cgroups_activated.Read();
    if (!byte.ok()) {
        LOG(ERROR) << name_ << ": failed to read from notification channel: "
                   << byte.error();
    }
    cgroups_activated.Close();
    if (*byte != kCgroupsActivated) {
        LOG(FATAL) << "Service '" << name_ << "' failed to start due to a fatal error";
        _exit(EXIT_FAILURE);
    }

子进程先进入 namespace、设置环境和发布 descriptor,再等待父进程完成 process group 创建。task profile 和 uid/gid 等属性在这个等待之后设置;所以子进程不会在 cgroup 尚未建立时提前 exec。

5.2 属性与exec ​

相关源码:

  • system/core/init/service.cpp
  • SetProcessAttributesAndCaps
  • RunService
cpp
if (task_profiles_.size() > 0) {
    bool succeeded = SelinuxGetVendorAndroidVersion() < __ANDROID_API_U__
                             ? SetTaskProfiles(getpid(), task_profiles_)
                             : SetProcessProfiles(getuid(), getpid(), task_profiles_);
    if (!succeeded) LOG(ERROR) << "failed to set task profiles";
}

SetProcessAttributesAndCaps(std::move(setsid_finished));

if (!ExpandArgsAndExecv(args_, sigstop_)) {
    PLOG(ERROR) << "cannot execv('" << args_[0] << "')";
}

task profile 失败只记录错误;SetProcessAttributesAndCaps() 对关键属性失败会 LOG(FATAL);execv 返回则说明 exec 失败,子进程随后以 127 退出。不要把所有子进程准备失败都归为同一种“服务启动失败”。

6. 父进程提交 ​

6.1 cgroup与状态 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Start

cpp
time_started_ = boot_clock::now();
pid_ = pid;
flags_ |= SVC_RUNNING;
start_order_ = next_start_order_++;
process_cgroup_empty_ = false;

if (CgroupsAvailable()) {
    bool use_memcg = swappiness_ != -1 || soft_limit_in_bytes_ != -1 ||
                     limit_in_bytes_ != -1 || limit_percent_ != -1 || !limit_property_.empty();
    errno = -createProcessGroup(uid(), pid_, use_memcg);
    if (errno != 0) {
        cgroups_activated.Write(kActivatingCgroupsFailed);
        return Error() << "createProcessGroup failed for service '" << name_ << "'";
    }
    if (use_memcg) ConfigureMemcg();
}

if (Result<void> result = cgroups_activated.Write(kCgroupsActivated); !result.ok()) {
    return Error() << "Sending cgroups activated notification failed: " << result.error();
}

父进程先登记 pid 和 running 状态,再创建 cgroup 并通知子进程。cgroup 创建失败会向子进程发送失败字节并返回错误;这时 Service 可能已经有 child pid,后续回收仍由 SIGCHLD 路径负责,不能只看 Start() 返回值做资源判断。

6.2 console同步 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Start

cpp
if (!RequiresConsole(proc_attr_)) {
    if (setpgid(pid, pid) < 0) {
        if (errno != EACCES && errno != ESRCH) {
            PLOG(ERROR) << "setpgid() from parent failed";
        }
    }
} else {
    if (Result<uint8_t> result = setsid_finished.Read();
        !result.ok() || *result != kSetSidFinished) {
        return Error() << "Waiting for setsid() failed";
    }
}

setsid_finished.Close();
NotifyStateChange("running");
reboot_on_failure.Disable();
return {};

普通服务父进程负责确保 process group;console 服务则等待子进程完成 setsid() 后才发布 running。NotifyStateChange 是最后一个启动提交点之一,不是 exec 成功回调。

7. 失败清理 ​

7.1 子进程失败 ​

如果 namespace、cgroup 通知、属性设置或 exec 失败,子进程会 fatal 或以 127 退出。父进程仍会通过 SIGCHLD 回收;Service::Reap() 会清理非持久 socket、进程组和状态 flags。已经创建的 socket 可能在回收前短暂存在。

7.2 资源残留 ​

源码文件:system/core/init/service.cpp

相关函数/类型:Service::Reap

cpp
for (const auto& socket : sockets_) {
    if (socket.persist) continue;
    auto path = ANDROID_SOCKET_DIR "/" + socket.name;
    unlink(path.c_str());
}

if (flags_ & SVC_EXEC) UnSetExec();
if (flags_ & SVC_TEMPORARY) return;

pid_ = 0;
flags_ &= (~SVC_RUNNING);
start_order_ = 0;
was_last_exit_ok_ = siginfo.si_code == CLD_EXITED && siginfo.si_status == 0;

普通 socket 默认在 reap 时删除,persist socket 除外;temporary Service 在清理 socket 和 exec 状态后直接返回,不进入普通 Service 的 pid/flags 重启流程。清理不是事务回滚,目标程序已经完成的文件写入、属性设置或外部 IPC 不会恢复。

8. 启动测试 ​

8.1 正常启动 ​

init_test.cpp::StartConsole 在 root、非 user build 条件下解析一个带 console、uid/gid、seclabel 的 Service,调用 Start(),断言 pid 大于零和 session id 存在,最后 Stop。它证明 console 启动协议和基本进程创建,不证明任意服务的 SELinux policy 或业务 ready。

8.2 cgroup停止 ​

service_test.cpp::ServiceStopTest 启动真实 /system/bin/yes,可选地把进程迁移到 root cgroup 并删除原 pid cgroup,再调用 StopServicesAndLogViolations。断言服务能被停止,证明 cleanup 不依赖原始 uid/pid cgroup 目录仍在;测试需要 root,不证明启动失败后的所有设备 cgroup 实现。

8.3 namespace ​

service_test.cpp::enter_namespace_net_preserves_mounts 为服务配置 network namespace,启动后比较服务 mount 表与 root mount 表,证明切换 network namespace 时 init 重新处理 /sys 相关挂载;不证明任意 namespace 组合都安全。

9. 失败定位 ​

排查“service 已注册但没有工作”时按以下状态顺序:

  1. 查看 starting service、路径展开和 Cannot find 日志,确认失败是否发生在 fork 前;
  2. 检查 socket/file 创建日志,区分 descriptor 缺失和目标程序本身失败;
  3. 查看 pid、init.svc.<name>、cgroup 和 execv 日志,区分父进程已提交与子进程已执行;
  4. 若有 console 或 namespace,检查 setsid_finished、namespace setup 和 mount 结果;
  5. 目标进程退出后沿 Reap() 检查 socket 删除、process group 清理、oneshot/disabled/restart 状态;
  6. 最后才验证目标服务的 Binder、socket、HAL 或业务 ready,不用 init.svc=running 代替应用层证明。

本文证明了 Android 17 Service::Start 的路径检查、descriptor 预创建、fork/clone 选择、FIFO 同步、cgroup 创建、子进程属性设置、exec 边界、父进程状态提交和 reap 清理;没有证明特定设备的 SELinux allow、可执行文件业务逻辑、cgroup 配置完整性、Binder 注册或服务 ready。