android init进程启动流程

2024-05-03 18:44

本文主要是介绍android init进程启动流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


 

Android系统完整的启动流程

android 系统架构图

init进程的启动流程

init进程启动服务的顺序

bool Service::Start() {// Starting a service removes it from the disabled or reset state and// immediately takes it out of the restarting state if it was in there.flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));// Running processes require no additional work --- if they're in the// process of exiting, we've ensured that they will immediately restart// on exit, unless they are ONESHOT.if (flags_ & SVC_RUNNING) {return false;}bool needs_console = (flags_ & SVC_CONSOLE);if (needs_console) {if (console_.empty()) {console_ = default_console;}// Make sure that open call succeeds to ensure a console driver is// properly registered for the device nodeint console_fd = open(console_.c_str(), O_RDWR | O_CLOEXEC);if (console_fd < 0) {PLOG(ERROR) << "service '" << name_ << "' couldn't open console '" << console_ << "'";flags_ |= SVC_DISABLED;return false;}close(console_fd);}struct stat sb;if (stat(args_[0].c_str(), &sb) == -1) {PLOG(ERROR) << "cannot find '" << args_[0] << "', disabling '" << name_ << "'";flags_ |= SVC_DISABLED;return false;}std::string scon;if (!seclabel_.empty()) {scon = seclabel_;} else {scon = ComputeContextFromExecutable(name_, args_[0]);if (scon == "") {return false;}}LOG(INFO) << "starting service '" << name_ << "'...";pid_t pid = -1;if (namespace_flags_) {pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);} else {pid = fork();}if (pid == 0) {umask(077);if (namespace_flags_ & CLONE_NEWPID) {// This will fork again to run an init process inside the PID// namespace.SetUpPidNamespace(name_);}for (const auto& ei : envvars_) {add_environment(ei.name.c_str(), ei.value.c_str());}std::for_each(descriptors_.begin(), descriptors_.end(),std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon));// See if there were "writepid" instructions to write to files under /dev/cpuset/.auto cpuset_predicate = [](const std::string& path) {return StartsWith(path, "/dev/cpuset/");};auto iter = std::find_if(writepid_files_.begin(), writepid_files_.end(), cpuset_predicate);if (iter == writepid_files_.end()) {// There were no "writepid" instructions for cpusets, check if the system default// cpuset is specified to be used for the process.std::string default_cpuset = GetProperty("ro.cpuset.default", "");if (!default_cpuset.empty()) {// Make sure the cpuset name starts and ends with '/'.// A single '/' means the 'root' cpuset.if (default_cpuset.front() != '/') {default_cpuset.insert(0, 1, '/');}if (default_cpuset.back() != '/') {default_cpuset.push_back('/');}writepid_files_.push_back(StringPrintf("/dev/cpuset%stasks", default_cpuset.c_str()));}}std::string pid_str = std::to_string(getpid());for (const auto& file : writepid_files_) {if (!WriteStringToFile(pid_str, file)) {PLOG(ERROR) << "couldn't write " << pid_str << " to " << file;}}if (ioprio_class_ != IoSchedClass_NONE) {if (android_set_ioprio(getpid(), ioprio_class_, ioprio_pri_)) {PLOG(ERROR) << "failed to set pid " << getpid()<< " ioprio=" << ioprio_class_ << "," << ioprio_pri_;}}if (needs_console) {setsid();OpenConsole();} else {ZapStdio();}// As requested, set our gid, supplemental gids, uid, context, and// priority. Aborts on failure.SetProcessAttributes();if (!ExpandArgsAndExecve(args_)) {PLOG(ERROR) << "cannot execve('" << args_[0] << "')";}_exit(127);}

这段代码最后调用_exit(127)的原因:

当一个进程调用fork()创建一个子进程时,子进程会继承父进程的内存映像,包括代码段、数据段、堆栈等。子进程会在fork()调用的位置开始执行,并且会复制父进程的文件描述符。在fork()之后,子进程通常会调用execve()来加载一个新的程序映像,取代原来的父进程映像。如果execve()执行成功,子进程会开始执行新程序的代码,而原来的父进程则继续执行下去。

在这段代码中,当子进程执行if (!ExpandArgsAndExecve(args_))时,它试图调用execve()加载一个新程序,如果加载失败(例如,找不到要执行的程序),那么子进程将无法继续执行。这时候,子进程就没有必要再继续执行下去,因为它无法完成它的任务。所以,为了避免子进程继续运行下去造成不必要的资源浪费,程序员选择在这种情况下使用_exit(127)来终止子进程。

为什么是_exit(127)呢?这是一个惯例,Unix系统的shell约定,当一个命令找不到时,会返回状态码127。这种约定让父进程能够根据子进程的返回状态码来判断子进程的执行情况。所以,在这里使用_exit(127)是为了向父进程传达“找不到要执行的程序”的信息。

如果调用execve()成功了,意味着新程序已经被加载并且开始执行了,那么当前进程的代码段、数据段等都已经被替换成了新程序的内容,所以当前进程不再执行原来的代码,而是转而执行新程序的代码。因此,_exit(127)之后的代码将不会被执行到,包括_exit(127)本身。

在这段代码中,_exit(127)被视为一种防御措施,用于处理execve()调用失败的情况。一旦execve()成功执行,子进程就不会继续执行后续的代码,而是会执行新程序的代码

举例:

#include <iostream>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == -1) {// 错误处理std::cerr << "fork() failed" << std::endl;return 1;} else if (pid == 0) {// 在子进程中std::cout << "Child process: fork() returned " << pid << std::endl;} else {// 在父进程中std::cout << "Parent process: fork() returned " << pid << std::endl;}return 0;
}

执行以后输出结果:

默认情况下fork以后,

在Linux中,默认情况下,父进程不会一直等待子进程执行完毕才结束自己。父进程和子进程是并发执行的,父进程会继续执行自己的任务,不会阻塞等待子进程的执行。

当父进程结束时,它的子进程可能还在运行,这时子进程的状态会被转移给 init 进程(进程号为1),这样子进程就不会成为孤儿进程。

但是,如果父进程希望等待子进程结束,可以使用 wait()waitpid() 系统调用来实现。这些调用可以使父进程阻塞,直到它的一个或多个子进程结束。

所以,要使父进程在子进程执行完毕后再结束自己,需要显式地调用 wait()waitpid() 函数。

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>int main() {std::cout << "Parent process: Starting..." << std::endl;pid_t pid = fork();if (pid == -1) {std::cerr << "fork() failed" << std::endl;return 1;} else if (pid == 0) {// 子进程std::cout << "Child process: Starting..." << std::endl;sleep(3); // 模拟子进程执行一些任务std::cout << "Child process: Finished" << std::endl;return 0;} else {// 父进程std::cout << "Parent process: Waiting for child process to finish..." << std::endl;int status;waitpid(pid, &status, 0); // 等待子进程结束if (WIFEXITED(status)) {std::cout << "Parent process: Child process exited with status: " << WEXITSTATUS(status) << std::endl;} else {std::cerr << "Parent process: Child process terminated abnormally" << std::endl;}std::cout << "Parent process: Finished" << std::endl;}return 0;
}

这篇关于android init进程启动流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/957396

相关文章

Ubuntu 24.04启用root图形登录的操作流程

《Ubuntu24.04启用root图形登录的操作流程》Ubuntu默认禁用root账户的图形与SSH登录,这是为了安全,但在某些场景你可能需要直接用root登录GNOME桌面,本文以Ubuntu2... 目录一、前言二、准备工作三、设置 root 密码四、启用图形界面 root 登录1. 修改 GDM 配

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局