内容简介:Android系统基于Linux,init进程是Android系统中用户空间的第一个进程,进程号为1,init源代码在system/core/init目录下。既然init进程是Android系统用户空间的第一个进程,因此担负着非常重要的责任,主要负责以下两件事:init的入口函数是main,代码如下所示:注释1处判断当前进程是不是ueventd。init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。ueventd主要是负责设备节点的创建、权限设定等一系列工作。服务通过使用ue
Android系统基于Linux,init进程是Android系统中用户空间的第一个进程,进程号为1,init源代码在system/core/init目录下。既然init进程是Android系统用户空间的第一个进程,因此担负着非常重要的责任,主要负责以下两件事:
-
解析配置init.rc,然后启动系统各种native进程,比如Zygote进程、SurfaceFlinger进程以及media进程等。
-
初始化并启动属性服务。
init进程的入口函数
init的入口函数是main,代码如下所示:
//路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
//注释1
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
//注释2
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
//注释3
if (REBOOT_BOOTLOADER_ON_PANIC) {
install_reboot_signal_handlers();
}
...
}
复制代码
注释1处判断当前进程是不是ueventd。init进程创建子进程ueventd,并将创建设备节点文件的工作托付给ueventd。ueventd主要是负责设备节点的创建、权限设定等一系列工作。服务通过使用uevent,监控驱动发送的消息,做进一步处理。
ueventd通过两种方式创建设备节点文件。
-
“冷插拔”(Cold Plug),即以预先定义的设备信息为基础,当ueventd启动后,统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件。
-
“热插拔”(Hot Plug),即在系统运行中,当有设备插入USB端口时,ueventd就会接收到这一事件,为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件。
注释2处判断当前进程是不是watchdogd。Android系统在长时间的运行下会面临各种软硬件的问题,为了解决这个问题,Android开发了WatchDog类作为软件看门狗来监控SystemServer中的线程,一旦发现问题,WatchDog会杀死SystemServer进程,SystemServer的父进程Zygote接收到SystemServer的死亡信号后,会杀死自己。Zygote进程死亡的信号传递到init进程后,init进程会杀死Zygote进程所有的子进程并重启Zygote。
注释3处判断是否紧急重启,如果是紧急重启,就安装对应的消息处理器。
//路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
//注释1
add_environment("PATH", _PATH_DEFPATH);
//注释2
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
// 用于记录启动时间
boot_clock::time_point start_time = boot_clock::now();
// 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响
umask(0);
// 挂载tmpfs文件系统
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
// 挂载devpts文件系统
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
// 挂载proc文件系统
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
//8.0新增, 收紧了cmdline目录的权限
chmod("/proc/cmdline", 0440);
// 8.0新增,增加了个用户组
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
// 挂载sysfs文件系统
mount("sysfs", "/sys", "sysfs", 0, NULL);
// 8.0新增
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL)
// 提前创建了kmsg设备节点文件,用于输出log信息
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
...
}
...
}
复制代码
注释1处添加环境变量。注释2处获取本次启动是否是系统启动的第一阶段,如果是第一阶段,进入下面的if语句中,创建并挂载相关的文件系统。
以上创建并挂载的五类文件系统分别如下所示:
-
tmpfs:一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
-
devpts:为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
-
proc:一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
-
sysfs:与proc文件系统类似,也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是 Linux 2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
-
selinuxfs:用于支持SELinux的文件系统,SELinux提供了一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。
//路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
//重定向输入输出/内核Log系统
InitKernelLogging(argv);
LOG(INFO) << "init first stage started!";
//挂在一些分区设备
if (!DoFirstStageMount()) {
LOG(ERROR) << "Failed to mount required partitions early ...";
panic();
}
//注释1
SetInitAvbVersionInRecovery();
//注释2
selinux_initialize(true);
...
}
...
}
复制代码
注释1处初始化安全框架AVB(Android Verified Boot),AVB主要用于防止系统文件本身被篡改,还包含了防止系统回滚的功能,以免有人试图回滚系统并利用以前的漏洞。注释2处调用selinux_initialize启动SELinux。
//路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
}
...
//注释1
property_init();
...
//注释2
signal_handler_init();
...
//注释3
start_property_service();
...
}
复制代码
注释1处通过property_init函数对属性服务进行初始化,注释3通过start_property_service函数启动属性服务。注释2处signal_handler_init设置子进程退出的信号处理函数,当子进程异常退出的时候,init进程会去捕获异常信息,当它捕获到这些异常信息之后,就会调用该函数设置的相应的捕获函数来处理。比如init进程的子进程Zygote死之后,init进程捕获到这些异常信息,就会调用handle_signal()函数去重启Zygote进程。
//路径:/system/core/init/init.cpp
int main(int argc, char** argv) {
...
if (is_first_stage) {
...
}
...
if (bootscript.empty()) {
//注释1
parser.ParseConfig("/init.rc");
...
} else {
...
}
...
while (true) {
...
ServiceManager::GetInstance().IsWaitingForExec())) {
//注释2
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//注释3
restart_processes();
...
}
...
}
return 0;
}
复制代码
注释1处解析init.rc配置文件,在注释2处执行子进程对应的命令,也就是执行init.rc文件里配置的命令。在注释3处重启死掉的service。
解析init.rc
在init.rc中使用的语言称为Android Init Language,翻译过来就是“Android初始化语言”,init语言共有五种类型的表达式,分别如下所示:
-
Action:Action中包含了一系列的Command。
-
Command:init语言中的命令。
-
Service:由init进程启动的服务。
-
Option:对服务的配置选项。
-
Import:引入其他配置文件。
Action表达式的语法如下所示:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
复制代码
这里的trigger是Action执行的触发器,当触发器条件满足时,command会被执行。触发器有如下两类:
-
事件触发器:当指定的事件发生时触发。事件可能由“trigger”命令发出,也可能是init进程通过QueueEventTrigger()函数发出。
-
属性触发器:当指定的属性满足某个值时触发。
Action中的Command是init语言定义的命令,所有支持的命令如下表:
| 命令 | 参数格式 | 说明 |
|---|---|---|
| bootchart_init | - | 启动bootchart |
| chmod | octal-mode path | 改变文件的访问权限 |
| chown | owner group path | 改变文件的拥有者和组 |
| class_start | serviceclass | 启动指定类别的服务 |
| class_stop | serviceclass | 停止并“disable”指定类别的服务 |
| class_reset | serviceclass | 停止指定类别的服务,但是不“disable”它们 |
| copy | src dst | 复制文件 |
| domainname | name | 设置域名 |
| enable | servicename | enable一个被disable的服务 |
| exec | [seclabel[user[group]]] -- command [argument]* | fork一个子进程来执行指定的命令 |
| export | name value | 导出环境变量 |
| hostname | name | 设置host名称 |
| ifup | iterface | 使网卡在线 |
| insmod | path | 安装指定路径的模块 |
| load_all_props | - | 从/system、/vendor等路径载入属性 |
| load_persist_props | - | 载入持久化的属性 |
| loglevel | level | 设置内核的日志级别 |
| mkdir | path[mode][owner][group] | 创建目录 |
| mount_all | fstab[path]*[--option] | 挂载文件系统并且导入指定的.rc文件 |
| mount | typedevicedir[flag]*[options] | 挂载一个文件系统 |
| powerctl | - | 内部实现使用 |
| restart | service | 重启服务 |
| restorecon | path[path]* | 设定文件的安全上下文 |
| restorecon_recursive | path[path]* | restorecon的递归版本 |
| rm | path | 对于指定路径调用unlink(2) |
| rmdir | path | 删除文件夹 |
| setprop | namevalue | 设置属性值 |
| setrlimit | resourcecurmax | 指定资源的rlimit |
| start | service | 启动服务 |
| stop | service | 停止服务 |
| swapon_all | fstab | 在指定文件上调用fs_mgr_swapon_all |
| symlink | targetpath | 创建符合链接 |
| sysclktz | mins_west_of_gmt | 指定系统时钟基准 |
| trigger | event | 触发一个事件 |
| umount | path | ummount指定的文件系统 |
| verity_load_state | - | 内部实现使用 |
| verity_update_state | mount_point | 内部实现使用 |
| wait | path[timeout] | 等待某个文件存在直到超时,若存在则直接返回 |
| write | pathcontent | 写入内容到指定文件 |
Service是init进程启动的可执行程序,Service表达式的语法如下所示:
service <name> <pathname> [ <argument> ]* <option>
<option>
复制代码
Option是对服务的修饰,它们影响着init进程如何以及何时启动服务。所有支持的Option入下所示:
| Option | 参数格式 | 说明 |
|---|---|---|
| critical | - | 标识为系统关键服务,该服务若退出多次将导致系统重启到recovery模式 |
| disabled | - | 不会随着类别自动启动,必须明确start |
| setenv | name value | 为启动的进程设置环境变量 |
| socket | nametypeperm[user[group[seclabel]]] | 创建UNIX Domain Socket |
| user | username | 在执行服务之前切换用户 |
| group | groupname[groupname]* | 在执行服务之前切换组 |
| seclabel | seclabel | 在执行服务之前切换seclabel |
| oneshot | - | 一次性服务,死亡后不用重启 |
| class | name | 指定服务的类别 |
| onrestart | - | 当服务重启时执行命令 |
| writepid | file... | 写入子进程的pid到指定文件 |
import是一个关键字,而不是一个命令,可以在.rc文件中通过这个关键字来加载其他的.rc文件,它的语法如下:
import path 复制代码
path可以是另一个.rc文件,也可以是一个文件夹。如果是文件夹,那么这个文件夹下面的所有文件都会被导入,但是它不会循环加载子目录中的文件。
启动Zygote
init.rc文件有如下配置代码:
...import /init.${ro.zygote}.rc...on nonencrypted
class_start main
class_start late_start...
复制代码
在init.rc文件的开头使用了import类型语句来引入Zygote启动脚本,其中ro.zygote根据不同的内容引入不同的文件,从Android 5.0开始,Android开始支持64位程序,Zygote就有了32位和64位之分,如下图所示:
查看init.zygote64.rc的代码如下所示:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20 user root group root readproc
socket zygote stream 660 root system onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
复制代码
Service用于通知init进程创建名为zygote的进程,这个进程执行程序的路径为/system/bin/app_process64,后面的代码是传递给app_process64的参数,class main指的是Zygote的classname为main。在解析Service类型语句时会将Service对象加入Service链表中。
再回过头看init.rc配置文件:
...import /init.${ro.zygote}.rc...on nonencrypted
class_start main
class_start late_start...
复制代码
class_start是一个command,对应的函数是do_class_start,用于启动classname为main的Service,也就是前面的Zygote,因此class_start main是用来启动Zygote的,do_class_start函数在builtins.cpp中定义,代码如下所示:
//路径:/system/core/init/builtins.cpp
static int do_class_start(const std::vector<std::string>& args) {
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
ServiceManager::GetInstance().
ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
return 0;
}
复制代码
ForEachServiceInClass函数会遍历Service链表,找到classname为main的Zygote,并执行StartIfNotDisabled函数,代码如下所示:
//路径:/system/core/init/service.cpp
bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;
}
复制代码
如果Service没有在其对应的rc文件中设置disabled选项,就会调用Start函数,Start函数如下所示:
//路径:/system/core/init/service.cpp
bool Service::Start() {
...
pid_t pid = -1;
if (namespace_flags_) {
pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
} else {
//注释1
pid = fork();
}
if (pid == 0) {
...
//注释2
if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
}
...
}
...
}
复制代码
在注释1处通过fork函数创建子进程,并返回pid,如果pid为0说明当前代码逻辑在子线程中运行,接着执行注释2处的execve函数,来启动Service子进程,进入Service的main函数中,如果Service是Zygote,执行程序的路径是/system/bin/app_process64,对应的文件是app_main.cpp,也就是会进入app_main.cpp的main函数中。代码如下所示:
int main(int argc, char* const argv[])
{
...
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
//注释1
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
...
if (zygote) {
//注释2
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
}
复制代码
在注释1处判断执行命令时是否带了--zygote,如果携带了,zygote赋值为true,接在注释2处判断如果zygote为true,就会通过runtime.start启动com.android.internal.os.ZygoteInit。
以上所述就是小编给大家介绍的《Android小知识-深入浅出Android系统启动流程(上)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 深入理解 RPC 交互流程
- 深入剖析Vue源码 - 实例挂载,编译流程
- 深入 Java 类加载全流程,值得你收藏
- RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
- 步步深入MySQL:架构->查询执行流程->SQL解析顺序
- Spring源码分析(八)深入了解事务管理的流程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP for the World Wide Web, Second Edition (Visual QuickStart Gu
Larry Ullman / Peachpit Press / 2004-02-02 / USD 29.99
So you know HTML, even JavaScript, but the idea of learning an actual programming language like PHP terrifies you? Well, stop quaking and get going with this easy task-based guide! Aimed at beginning ......一起来看看 《PHP for the World Wide Web, Second Edition (Visual QuickStart Gu》 这本书的介绍吧!