服务启动流程
本文面向已经读过 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
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
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.cppService::Startsystem/core/init/service_utils.cppSocketDescriptor::CreateFileDescriptor::Create
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
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
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.cppInitializeReadWrite
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
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.cppSetProcessAttributesAndCapsRunService
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
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
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
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 已注册但没有工作”时按以下状态顺序:
- 查看
starting service、路径展开和Cannot find日志,确认失败是否发生在 fork 前; - 检查 socket/file 创建日志,区分 descriptor 缺失和目标程序本身失败;
- 查看 pid、
init.svc.<name>、cgroup 和execv日志,区分父进程已提交与子进程已执行; - 若有 console 或 namespace,检查
setsid_finished、namespace setup 和 mount 结果; - 目标进程退出后沿
Reap()检查 socket 删除、process group 清理、oneshot/disabled/restart 状态; - 最后才验证目标服务的 Binder、socket、HAL 或业务 ready,不用
init.svc=running代替应用层证明。
本文证明了 Android 17 Service::Start 的路径检查、descriptor 预创建、fork/clone 选择、FIFO 同步、cgroup 创建、子进程属性设置、exec 边界、父进程状态提交和 reap 清理;没有证明特定设备的 SELinux allow、可执行文件业务逻辑、cgroup 配置完整性、Binder 注册或服务 ready。
