目的
看过一些blog和相关的书籍,大多数文章在介绍init进程时,参考的代码比较久远,同时不同文章行文的重点不太一样,因此决定自己试着来分析一下,并作相应的记录。
背景
当linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,它的生命周期贯穿整个linux 内核运行的始终, linux中所有其它的进程的共同始祖均为init进程。
Android系统是运作在linux内核上的,为了启动并运行整个android系统,google实现了android系统的init进程。
Android init进程的入口文件在system/core/init/init.cpp中。在main函数中,按顺序主要进行了以下工作:
1、创建文件系统目录并挂载相关的文件系统
//清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响。
umask(0);
add_environment("PATH", _PATH_DEFPATH);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
// Get the basic filesystem setup we need put together in the initramdisk
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
如上所示,该部分主要用于创建和挂载启动所需的文件目录。
需要注意的是,在编译Android系统源码时,在生成的根文件系统中,并不存在这些目录,它们是系统运行时的目录,即当系统终止时,就会消失。
在init初始化过程中,Android分别挂载了tmpfs,devpts,proc,sysfs这4类文件系统。
tmpfs是一种虚拟内存文件系统,它会将所有的文件存储在虚拟内存中,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。
tmpfs既可以使用RAM,也可以使用交换分区,会根据你的实际需要而改变大小。tmpfs的速度非常惊人,毕竟它是驻留在RAM中的,即使用了交换分区,性能仍然非常卓越。
由于tmpfs是驻留在RAM的,因此它的内容是不持久的。断电后,tmpfs的内容就消失了,这也是被称作tmpfs的根本原因。
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/ pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
2、屏蔽标准的输入输出
open_devnull_stdio();
- 1
前文生成/dev目录后,init进程将调用open_devnull_stdio函数,屏蔽标准的输入输出。
open_devnull_stdio函数会在/dev目录下生成__null__设备节点文件,并将标准输入、标准输出、标准错误输出全部重定向到__null__设备中。
void open_devnull_stdio(void)
{
// Try to avoid the mknod() call if we can. Since SELinux makes
// a /dev/null replacement available for free, let's use it.
int fd = open("/sys/fs/selinux/null", O_RDWR);
if (fd == -1) {
// OOPS, /sys/fs/selinux/null isn't available, likely because
// /sys/fs/selinux isn't mounted. Fall back to mknod.
static const char *name = "/dev/__null__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
fd = open(name, O_RDWR);
unlink(name);
}
if (fd == -1) {
exit(1);
}
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2) {
close(fd);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
open_devnull_stdio函数定义于system/core/init/util.cpp中。
这里需要说明的是,dup2函数的作用是用来复制一个文件的描述符,通常用来重定向进程的stdin、stdout和stderr。它的函数原形是:
int dup2(int oldfd, int targetfd)
该函数执行后,targetfd将变成oldfd的复制品。
因此上述过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上。因此init进程调用open_devnull_stdio函数后,通过标准的输入输出无法输出信息。
3、初始化内核log系统
我们继续回到init进程的main函数,init进程通过klog_init函数,提供输出log信息的设备。
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL)
- 1
- 2
void klog_init(void) {
if (klog_fd >= 0) return; /* Already initialized */
klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
if (klog_fd >= 0) {
return;
}
static const char* name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
klog_fd = open(name, O_WRONLY | O_CLOEXEC);
unlink(name);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
klog_init函数定义于system/core/libcutils/klog.c中。通过klog_init函数,init进程生成kmsg设备节点文件。该设备可以调用内核信息输出函数printk,以输出log信息。
4、初始化属性域
if (!is_first_stage) {
.......
property_init();
.......
}
- 1
- 2
- 3
- 4
- 5
调用property_init初始化属性域。在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API。
这里存在一个问题是,在init进程中有部分代码块以is_first_stage标志进行区分,决定是否需要进行初始化。
is_first_stage的值,由init进程main函数的入口参数决定,之前不太明白具体的含义。
后来写博客后,有朋友留言,在引入selinux机制后,有些操作必须要在内核态才能完成;
但init进程作为android的第一个进程,又是运行在用户态的。
于是,最终设计为用is_first_stage进行区分init进程的运行状态。init进程在运行的过程中,会完成从内核态到用户态的切换。
void property_init() {
if (__system_property_area_init()) {
ERROR("Failed to initialize property area\n");
exit(1);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
property_init函数定义于system/core/init/property_service.cpp中,如上面代码所示,最终调用_system_property_area_init函数初始化属性域。
5、完成SELinux相关工作
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);
- 1
- 2
init进程进程调用selinux_initialize启动SELinux。从注释来看,init进程的运行确实是区分用户态和内核态的。
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
//用于打印log的回调函数
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
//用于检查权限的回调函数
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
if (in_kernel_domain) {
//内核态处理流程
INFO("Loading SELinux policy...\n");
//用于加载sepolicy文件。该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件,后续的MAC才能开展起来。
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
//内核中读取的信息
bool kernel_enforcing = (security_getenforce() == 1);
//命令行中得到的数据
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) {
//用于设置selinux的工作模式。selinux有两种工作模式:
//1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志
//2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式
if(security_setenforce(is_enforcing)) {
........
//将重启进入recovery mode
security_failure();
}
}
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure();
}
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration());
} else {
selinux_init_all_handles();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
6、重新设置属性
// If we're in the kernel domain, re-exec init to transition to the init domain now that the SELinux policy has been loaded.
if (is_first_stage) {
//按selinux policy要求,重新设置init文件属性
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
//这里就是前面所说的,启动用户态的init进程,即second-stage
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
上述文件节点在加载Sepolicy之前已经被创建了,因此在加载完Sepolicy后,需要重新设置相关的属性。
7、创建epoll句柄
如下面代码所示,init进程调用epoll_create1创建epoll句柄。
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
- 1
- 2
- 3
- 4
- 5
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。
epoll机制一般使用epoll_create(int size)函数创建epoll句柄,size用来告诉内核这个句柄可监听的fd的数目。注意这个参数不同于select()中的第一个参数,在select中需给出最大监听数加1的值。
此外,当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,能够看到创建出的fd,因此在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
上述代码使用的epoll_create1(EPOLLCLOEXEC)来创建epoll句柄,该标志位表示生成的epoll fd具有“执行后关闭”特性。
8、装载子进程信号处理器
signal_handler_init();
- 1
init进程调用signal_handler_init装载子进程信号处理器,该函数定义于system/core/init/signal_handler.cpp中。
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,此处init进程调用signal_handler_init的目的就是捕获子进程结束的信号。
void signal_handler_init() {
// Create a signalling mechanism for SIGCHLD.
int s[2];
//利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
ERROR("socketpair failed: %s\n", strerror(errno));
exit(1);
}
signal_write_fd = s[0];
signal_read_fd = s[1];
// Write to signal_write_fd if we catch SIGCHLD.
struct sigaction act;
memset(&act, 0, sizeof(act));
//信号处理器为SIGCHLD_handler,其被存在sigaction结构体中,负责处理SIGCHLD消息
act.sa_handler = SIGCHLD_handler;
act.sa_flags = SA_NOCLDSTOP;
//调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中
sigaction(SIGCHLD, &act, 0);
//相对于6.0的代码,进一步作了封装,用于终止出现问题的子进程,详细代码于后文分析。
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
register_epoll_handler(signal_read_fd, handle_signal);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
Linux进程通过互相发送接收消息来实现进程间的通信,这些消息被称为“信号”。每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。
注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
我们来看看SIGCHLD_handler的具体工作。
static void SIGCHLD_handler(int) {
if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
}
}
- 1
- 2
- 3
- 4
- 5
从上面代码我们知道,init进程是所有进程的父进程,当其子进程终止产生SIGCHLD信号时,SIGCHLD_handler对signal_write_fd执行写操作。由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。
在装载信号监听器的最后,signal_handler_init调用register_epoll_handler,其代码如下所示,传入参数分别为signal_read_fd和handle_signal。
void register_epoll_handler(int fd, void (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = reinterpret_cast<void*>(fn);
//epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
ERROR("epoll_ctl failed: %s\n", strerror(errno));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
根据代码,我们知道:当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。
至此,结合上文我们知道:当init进程调用signal_handler_init后,一旦收到子进程终止带来的SIGCHLD消息后,将利用信号处理者SIGCHLD_handler向signal_write_fd写入信息; epoll句柄监听到signal_read_fd收消息后,将调用handle_signal进行处理。整个过程如下图所示。
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
read(signal_read_fd, buf, sizeof(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
从代码中可以看出,handle_signal只是清空signal_read_fd中的数据,然后调用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。
ServiceManager定义于system/core/init/service.cpp中,是一个单例对象:
............
//C++中默认是private属性
ServiceManager::ServiceManager() {
}
ServiceManager& ServiceManager::GetInstance() {
static ServiceManager instance;
return instance;
}
void ServiceManager::ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
............
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
ReapAnyOutstandingChildren函数实际上调用了ReapOneProcess。
我们结合代码,看看ReapOneProcess的具体工作。
bool ServiceManager::ReapOneProcess() {
int status;
//用waitpid函数获取状态发生变化的子进程pid
//waitpid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
if (pid == 0) {
return false;
} else if (pid == -1) {
ERROR("waitpid failed: %s\n", strerror(errno));
return false;
}
//利用FindServiceByPid函数,找到pid对应的服务。
//FindServiceByPid主要通过轮询解析init.rc生成的service_list,找到pid与参数一致的srvc。
Service* svc = FindServiceByPid(pid);
//输出服务结束的原因
.........
if (!svc) {
return true;
}
//结束服务,相对于6.0作了进一步的封装
if (svc->Reap()) {
waiting_for_exec = false;
//移除服务对应的信息
RemoveService(*svc);
}
return true;
}
bool Service::Reap() {
//清理未携带SVC_ONESHOT 或 携带了SVC_RESTART标志的srvc的子进程
if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
NOTICE("Service '%s' (pid %d) killing any children in process group\n", name_.c_str(), pid_);
kill(-pid_, SIGKILL);
}
//清除srvc中创建出的socket
for (const auto& si : sockets_) {
std::string tmp = StringPrintf(ANDROID_SOCKET_DIR "/%s", si.name.c_str());
unlink(tmp.c_str());
}
if (flags_ & SVC_EXEC) {
INFO("SVC_EXEC pid %d finished...\n", pid_);
return true;
}
pid_ = 0;
flags_ &= (~SVC_RUNNING);
//对于携带了SVC_ONESHOT并且未携带SVC_RESTART的srvc,将这类服务的标志置为SVC_DISABLED,不再启动
if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
flags_ |= SVC_DISABLED;
}
// Disabled and reset processes do not get restarted automatically.
if (flags_ & (SVC_DISABLED | SVC_RESET)) {
svc->NotifyStateChange("stopped");
return true;
}
time_t now = gettime();
//未携带SVC_RESTART的关键服务,在规定的间隔内,crash字数过多时,会导致整机重启;
if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
if (time_crashed_ + CRITICAL_CRASH_WINDOW >= now) {
if (++nr_crashed_ > CRITICAL_CRASH_THRESHOLD) {
..........
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
return true;
}
} else {
time_crashed_ = now;
nr_crashed_ = 1;
}
}
//将待重启srvc的标志位置为SVC_RESTARTING(init进程将根据该标志位,重启服务)
flags_ &= (~SVC_RESTART);
flags_ |= SVC_RESTARTING;
// Execute all onrestart commands for this service.
//重启在init.rc文件中带有onrestart选项的服务,相对于6.0,此处也增加了封装性
onrestart_.ExecuteAllCommands();
svc->NotifyStateChange("restarting");
return true;
}
void Action::ExecuteAllCommands() const {
for (const auto& c : commands_) {
ExecuteCommand(c);
}
}
void Action::ExecuteCommand(const Command& command) const {
Timer t;
//服务重启时,将执行对应的选项
int result = command.InvokeFunc();
//打印log
........
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
waitpid的函数原型为:
pid_t waitpid(pid_t pid, int *status, int options)
其中,第一个参数pid为预等待的子进程的识别码,pid=-1表示等待任何子进程是否发出SIGCHLD。第二个参数status,用于返回子进程的结束状态。第三个参数决定waitpid函数是否处于阻塞处理方式,WNOHANG表示若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待;若子进程结束,则返回子进程的pid。waitpid如果出错,则返回-1。
总结一下:整个signal_handler_init其实就是为了重启子进程用的,上述过程其实最终可以简化为下图:
property_load_boot_defaults();
- 1
接下来,进程调用property_load_boot_defaults进行默认属性配置相关的工作。
void property_load_boot_defaults() {
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
- 1
- 2
- 3
如代码所示,property_load_boot_defaults实际上就是调用load_properties_from_file解析配置文件;然后根据解析的结果,设置系统属性。该部分功能较为单一,不再深入分析。
10、启动配置属性的服务端
start_property_service();
- 1
init进程在共享内存区域中,创建并初始化属性域。其它进程可以访问属性域中的值,但更改属性值仅能在init进程中进行。这就是init进程调用start_property_service的原因。其它进程修改属性值时,要预先向init进程提交值变更申请,然后init进程处理该申请,并修改属性值。在访问和修改属性时,init进程都可以进行权限控制。
void start_property_service() {
//创建了一个非阻塞socket
property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0666, 0, 0, NULL);
if (property_set_fd == -1) {
ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
exit(1);
}
//调用listen函数监听property_set_fd, 于是该socket变成一个server
listen(property_set_fd, 8);
//监听server socket上是否有数据到来
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我们知道,在create_socket函数返回套接字property_set_fd时,property_set_fd是一个主动连接的套接字。此时,系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接。
由于在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接,于是需要调用listen函数使用主动连接套接字变为被连接套接字,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。
因此,调用listen后,init进程成为一个服务进程,其它进程可以通过property_set_fd连接init进程,提交设置系统属性的申请。
listen函数的第二个参数,涉及到一些网络的细节。
在进程处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态。有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。
因此,内核会在自己的进程空间里维护一个队列,以跟踪那些已完成连接但服务器进程还没有接手处理的用户,或正在进行的连接的用户。这样的一个队列不可能任意大,所以必须有一个上限。listen的第二个参数就是告诉内核使用这个数值作为上限。因此,init进程作为系统属性设置的服务器,最多可以同时为8个试图设置属性的用户提供服务。
在启动配置属性服务的最后,调用函数register_epoll_handler。根据上文所述,我们知道该函数将利用之前创建出的epoll句柄监听property_set_fd。当property_set_fd中有数据到来时,init进程将利用handle_property_set_fd函数进行处理。
static void handle_property_set_fd() {
..........
if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
return;
}
........
r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT));
.........
switch(msg.cmd) {
.........
}
.........
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
handle_propery_set_fd函数实际上是调用accept函数监听连接请求,接收property_set_fd中到来的数据,然后利用recv函数接受到来的数据,最后根据到来数据的类型,进行设置系统属性等相关操作,在此不做深入分析。
在这一部分的最后,我们简单举例介绍一下,系统属性改变的一些用途。
在init.rc中定义了一些与属性相关的触发器。当某个条件相关的属性被改变时,与该条件相关的触发器就会被触发。举例来说,如下面代码所示,debuggable属性变为1时,将执行启动console进程等操作。
on property:ro.debuggable=1
# Give writes to anyone for the trace folder on debug builds.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
start console
- 1
- 2
- 3
- 4
- 5
总结一下,其它进程修改系统属性时,大致的流程如下图所示:其它的进程像init进程发送请求后,由init进程检查权限后,修改共享内存区。
//这里将Action的function_map_替换为BuiltinFunctionMap
//下文将通过BuiltinFuntionMap的map方法,获取keyword对应的处理函数
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
//构造出解析文件用的parser对象
Parser& parser = Parser::GetInstance();
//为一些类型的关键字,创建特定的parser
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
//开始实际的解析过程
parser.ParseConfig("/init.rc");
........
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在解析init.rc文件的过程前,我们先来简单介绍一下init.rc文件。
init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作。在Android系统中,使用init.rc和init.{ hardware }.rc两个文件。
其中init.rc文件在Android系统运行过程中用于通用的环境设置与进程相关的定义,init.{hardware}.rc(例如,高通有init.qcom.rc,MTK有init.mediatek.rc)用于定义Android在不同平台下的特定进程和环境设置等。
此处解析函数传入的参数为“/init.rc”,解析的是运行时与init进程同在根目录下的init.rc文件。该文件在编译前,定义于system/core/rootdir/init.rc中(与平台相关的rc文件不在这里加载)。
init.rc文件大致分为两大部分,一部分是以“on”关键字开头的动作列表(action list):
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
.........
start ueventd
- 1
- 2
- 3
- 4
- 5
另一部分是以“service”关键字开头的服务列表(service list):
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
- 1
- 2
- 3
- 4
借助系统环境变量或Linux命令,动作列表用于创建所需目录,以及为某些特定文件指定权限,而服务列表用来记录init进程需要启动的一些子进程。如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。
接下来,我们从ParseConfig函数入手,逐步分析整个解析过程(函数定义于system/core/init/ init_parser.cpp中):
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
//传入参数为目录地址
return ParseConfigDir(path);
}
//传入参数为文件地址
return ParseConfigFile(path);
}
bool Parser::ParseConfigDir(const std::string& path) {
...........
std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
..........
//看起来很复杂,其实就是递归目录
while ((current_file = readdir(config_dir.get()))) {
std::string current_path = android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
if (current_file->d_type == DT_REG) {
//最终还是靠ParseConfigFile来解析实际的文件
if (!ParseConfigFile(current_path)) {
.............
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
从上面的代码可以看出,解析init.rc文件的函数是ParseConfigFile:
bool Parser::ParseConfigFile(const std::string& path) {
........
std::string data;
//读取路径指定文件中的内容,保存为字符串形式
if (!read_file(path.c_str(), &data)) {
return false;
}
.........
//解析获取的字符串
ParseData(path, data);
.........
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
ParseData函数定义于system/core/init/init_parser.cpp中,根据关键字解析出服务和动作。动作与服务会以链表节点的形式注册到service_list与action_list中,service_list与action_list是init进程中声明的全局结构体,其中的关键代码下所示。
void Parser::ParseData(const std::string& filename, const std::string& data) {
.......
parse_state state;
.......
std::vector<std::string> args;
for (;;) {
//next_token以行为单位分割参数传递过来的字符串
//最先走到T_TEXT分支
switch (next_token(&state)) {
case T_EOF:
if (section_parser) {
//EOF,解析结束
section_parser->EndSection();
}
return;
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
//在前文创建parser时,我们为service,on,import定义了对应的parser
//这里就是根据第一个参数,判断是否有对应的parser
if (section_parsers_.count(args[0])) {
if (section_parser) {
//结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中
section_parser->EndSection();
}
//获取参数对应的parser
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
//调用实际parser的ParseSection函数
if (!section_parser->ParseSection(args, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
//如果第一个参数不是service,on,import
//则调用前一个parser的ParseLineSection函数
//这里相当于解析一个参数块的子项
if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
}
}
//清空本次解析的数据
args.clear();
break;
case T_TEXT:
//将本次解析的内容写入到args中
args.emplace_back(state.text);
break;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
这里的解析看起来比较复杂,在6.0以前的版本中,整个解析是面向过程的。init进程统一调用一个函数来进行解析,然后在该函数中利用switch-case的形式,根据解析的内容进行相应的处理。
在Android 7.0中,为了更好地封装及面向对象,对于不同的关键字定义了不同的parser对象,每个对象通过多态实现自己的解析操作。
我们现在回忆一下init进程main函数中,创建parser的代码:
...........
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
...........
- 1
- 2
- 3
- 4
- 5
- 6
看看三个Parser的定义:
class ServiceParser : public SectionParser {......}
class ActionParser : public SectionParser {......}
class ImportParser : public SectionParser {.......}
- 1
- 2
- 3
可以看到三个Parser均是继承SectionParser,具体的实现各有不同,我们以比较常用的ServiceParser和ActionParser为例,看看解析的结果如何处理。
11.1 ServiceParser
ServiceParser定义于system/core/init/service.cpp中。从前面的代码,我们知道,解析一个service块,首先需要调用ParseSection函数,接着利用ParseLineSection处理子块,解析完所有数据后,调用EndSection。
因此,我们着重看看ServiceParser的这三个函数:
bool ServiceParser::ParseSection(.....) {
.......
const std::string& name = args[1];
.......
std::vector<std::string> str_args(args.begin() + 2, args.end());
//主要根据参数,构造出一个service对象
service_ = std::make_unique<Service>(name, "default", str_args);
return true;
}
//注意这里已经在解析子项了
bool ServiceParser::ParseLineSection(......) const {
//调用service对象的HandleLine
return service_ ? service_->HandleLine(args, err) : false;
}
bool Service::HandleLine(.....) {
........
//OptionHandlerMap继承自keywordMap<OptionHandler>
static const OptionHandlerMap handler_map;
//根据子项的内容,找到对应的handler函数
//FindFunction定义与keyword模块中,FindFunction方法利用子类生成对应的map中,然后通过通用的查找方法,即比较键值找到对应的处理函数
auto handler = handler_map.FindFunction(args[0], args.size() - 1, err);
if (!handler) {
return false;
}
//调用handler函数
return (this->*handler)(args, err);
}
class Service::OptionHandlerMap : public KeywordMap<OptionHandler> {
...........
Service::OptionHandlerMap::Map& Service::OptionHandlerMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
static const Map option_handlers = {
{"class", {1, 1, &Service::HandleClass}},
{"console", {0, 0, &Service::HandleConsole}},
{"critical", {0, 0, &Service::HandleCritical}},
{"disabled", {0, 0, &Service::HandleDisabled}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::HandleGroup}},
{"ioprio", {2, 2, &Service::HandleIoprio}},
{"keycodes", {1, kMax, &Service::HandleKeycodes}},
{"oneshot", {0, 0, &Service::HandleOneshot}},
{"onrestart", {1, kMax, &Service::HandleOnrestart}},
{"seclabel", {1, 1, &Service::HandleSeclabel}},
{"setenv", {2, 2, &Service::HandleSetenv}},
{"socket", {3, 6, &Service::HandleSocket}},
{"user", {1, 1, &Service::HandleUser}},
{"writepid", {1, kMax, &Service::HandleWritepid}},
};
return option_handlers;
}
//以class对应的处理函数为例,可以看出其实就是填充service对象对应的域
bool Service::HandleClass(const std::vector<std::string>& args, std::string* err) {
classname_ = args[1];
return true;
}
//注意此时service对象已经处理完毕
void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}
void ServiceManager::AddService(std::unique_ptr<Service> service) {
Service* old_service = FindServiceByName(service->name());
if (old_service) {
ERROR("ignored duplicate definition of service '%s'",
service->name().c_str());
return;
}
//将service对象加入到services_里
//7.0里,services_已经是个vector了
services_.emplace_back(std::move(service));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
从上面的一系列代码,我们可以看出ServiceParser其实就是:首先根据第一行的名字和参数创建出service对象,然后根据选项域的内容填充service对象,最后将创建出的service对象加入到vector类型的service链表中。
11.2 ActionParser
ActionParser定义于system/core/init/action.cpp中。Action的解析过程,其实与Service一样,也是先后调用ParseSection, ParseLineSection和EndSection。
bool ActionParser::ParseSection(....) {
........
//创建出新的action对象
auto action = std::make_unique<Action>(false);
//根据参数,填充action的trigger域,不详细分析了
if (!action->InitTriggers(triggers, err)) {
return false;
}
.........
}
bool ActionParser::ParseLineSection(.......) const {
//构造Action对象的command域
return action_ ? action_->AddCommand(args, filename, line, err) : false;
}
bool Action::AddCommand(.....) {
........
//找出action对应的执行函数
auto function = function_map_->FindFunction(args[0], args.size() - 1, err);
........
//利用所有信息构造出command,加入到action对象中
AddCommand(function, args, filename, line);
return true;
}
void Action::AddCommand(......) {
commands_.emplace_back(f, args, filename, line);
}
void ActionParser::EndSection() {
if (action_ && action_->NumCommands() > 0) {
ActionManager::GetInstance().AddAction(std::move(action_));
}
}
void ActionManager::AddAction(.....) {
........
auto old_action_it = std::find_if(actions_.begin(),
actions_.end(),
[&action] (std::unique_ptr<Action>& a) {
return action->TriggersEqual(*a);
});
if (old_action_it != actions_.end()) {
(*old_action_it)->CombineAction(*action);
} else {
//加入到action链表中,类型也是vector,其中装的是指针
actions_.emplace_back(std::move(action));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
从上面的代码可以看出,加载action块的逻辑和service一样,不同的是需要填充trigger和command域。当然,最后解析出的action也需要加入到action链表中。
这里最后还剩下一个问题,那就是哪里定义了Action中command对应处理函数?
实际上,前文已经出现了过了,在init.cpp的main函数中:
.......
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
.......
- 1
- 2
- 3
- 4
因此,Action中调用function_map_->FindFunction时,实际上调用的是BuiltinFunctionMap的FindFunction函数。我们已经知道FindFunction是keyword定义的通用函数,重点是重构的map函数。我们看看system/core/init/builtins.cpp:
BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
static const Map builtin_functions = {
{"bootchart_init", {0, 0, do_bootchart_init}},
{"chmod", {2, 2, do_chmod}},
{"chown", {2, 3, do_chown}},
{"class_reset", {1, 1, do_class_reset}},
{"class_start", {1, 1, do_class_start}},
{"class_stop", {1, 1, do_class_stop}},
{"copy", {2, 2, do_copy}},
{"domainname", {1, 1, do_domainname}},
{"enable", {1, 1, do_enable}},
{"exec", {1, kMax, do_exec}},
{"export", {2, 2, do_export}},
{"hostname", {1, 1, do_hostname}},
{"ifup", {1, 1, do_ifup}},
{"init_user0", {0, 0, do_init_user0}},
{"insmod", {1, kMax, do_insmod}},
{"installkey", {1, 1, do_installkey}},
{"load_persist_props", {0, 0, do_load_persist_props}},
{"load_system_props", {0, 0, do_load_system_props}},
{"loglevel", {1, 1, do_loglevel}},
{"mkdir", {1, 4, do_mkdir}},
{"mount_all", {1, kMax, do_mount_all}},
{"mount", {3, kMax, do_mount}},
{"powerctl", {1, 1, do_powerctl}},
{"restart", {1, 1, do_restart}},
{"restorecon", {1, kMax, do_restorecon}},
{"restorecon_recursive", {1, kMax, do_restorecon_recursive}},
{"rm", {1, 1, do_rm}},
{"rmdir", {1, 1, do_rmdir}},
{"setprop", {2, 2, do_setprop}},
{"setrlimit", {3, 3, do_setrlimit}},
{"start", {1, 1, do_start}},
{"stop", {1, 1, do_stop}},
{"swapon_all", {1, 1, do_swapon_all}},
{"symlink", {2, 2, do_symlink}},
{"sysclktz", {1, 1, do_sysclktz}},
{"trigger", {1, 1, do_trigger}},
{"verity_load_state", {0, 0, do_verity_load_state}},
{"verity_update_state", {0, 0, do_verity_update_state}},
{"wait", {1, 2, do_wait}},
{"write", {2, 2, do_write}},
};
return builtin_functions;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
上述代码的第四项就是Action每个command对应的执行函数。
12、向执行队列中添加其它action
介绍完init进程解析init.rc文件的过程后,我们继续将视角拉回到init进程的main函数:
ActionManager& am = ActionManager::GetInstance();
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
m.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = property_get("ro.bootmode");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
从上面的代码可以看出,接下来init进程中调用了大量的QueueEventTrigger和QueueBuiltinAction函数。
void ActionManager::QueueEventTrigger(const std::string& trigger) {
trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
}
- 1
- 2
- 3
此处QueueEventTrigger函数就是利用参数构造EventTrigger,然后加入到trigger_queue_中。后续init进程处理trigger事件时,将会触发相应的操作。根据前文的分析,我们知道实际上就是将action_list中,对应trigger与第一个参数匹配的action,加入到运行队列action_queue中。
void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
//创建action
auto action = std::make_unique<Action>(true);
std::vector<std::string> name_vector{name};
//保证唯一性
if (!action->InitSingleTrigger(name)) {
return;
}
//创建action的cmd,指定执行函数和参数
action->AddCommand(func, name_vector);
trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));
actions_.emplace_back(std::move(action));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
QueueBuiltinAction函数中构造新的action加入到actions_中,第一个参数作为新建action携带cmd的执行函数;第二个参数既作为action的trigger name,也作为action携带cmd的参数。
13、处理添加到运行队列的事件
while (true) {
//判断是否有事件需要处理
if (!waiting_for_exec) {
//依次执行每个action中携带command对应的执行函数
am.ExecuteOneCommand();
//重启一些挂掉的进程
restart_processes();
}
//以下决定timeout的时间,将影响while循环的间隔
int timeout = -1;
//有进程需要重启时,等待该进程重启
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
//有action待处理,不等待
if (am.HasMoreCommands()) {
timeout = 0;
}
//bootchart_sample应该是进行性能数据采样
bootchart_sample(&timeout);
epoll_event ev;
//没有事件到来的话,最多阻塞timeout时间
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
//有事件到来,执行对应处理函数
//根据上文知道,epoll句柄(即epoll_fd)主要监听子进程结束,及其它进程设置系统属性的请求。
((void (*)()) ev.data.ptr)();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
从上面代码可以看出,init进程将所有需要操作的action加入运行队列后, 进入无限循环过程,不断处理运行队列中的事件,同时进行重启service等操作。
ExecuteOneCommand中的主要部分如下图所示。
void ActionManager::ExecuteOneCommand() {
// Loop through the trigger queue until we have an action to execute
//当有可执行action或trigger queue为空时结束
while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
//轮询actions链表
for (const auto& action : actions_) {
//依次查找trigger表
if (trigger_queue_.front()->CheckTriggers(*action)) {
//当action与trigger对应时,就可以执行当前action
//一个trigger可以对应多个action,均加入current_executing_actions_
current_executing_actions_.emplace(action.get());
}
}
//trigger event出队
trigger_queue_.pop();
}
if (current_executing_actions_.empty()) {
return;
}
//每次只执行一个action,下次init进程while循环时,跳过上面的while循环,接着执行
auto action = current_executing_actions_.front();
if (current_command_ == 0) {
std::string trigger_name = action->BuildTriggersString();
INFO("processing action (%s)\n", trigger_name.c_str());
}
//实际的执行过程,此处仅处理当前action中的一个cmd
action->ExecuteOneCommand(current_command_);
//适当地清理工作,注意只有当前action中所有的command均执行完毕后,才会将该action从current_executing_actions_移除
// If this was the last command in the current action, then remove
// the action from the executing list.
// If this action was oneshot, then also remove it from actions_.
++current_command_;
if (current_command_ == action->NumCommands()) {
current_executing_actions_.pop();
current_command_ = 0;
if (action->oneshot()) {
auto eraser = [&action] (std::unique_ptr<Action>& a) {
return a.get() == action;
};
actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
void Action::ExecuteCommand(const Command& command) const {
Timer t;
//执行该command对应的处理函数
int result = command.InvokeFunc();
........
}
- 1
- 2
- 3
- 4
- 5
- 6
从代码可以看出,当while循环不断调用ExecuteOneCommand函数时,将按照trigger表的顺序,依次取出action链表中与trigger匹配的action。
每次均执行一个action中的一个command对应函数(一个action可能携带多个command)。
当一个action所有的command均执行完毕后,再执行下一个action。
当一个trigger对应的action均执行完毕后,再执行下一个trigger对应action。
restart_processes函数负责按需重启service,代码如下图所示。
static void restart_processes() {
process_needs_restart = 0;
ServiceManager::GetInstance().ForEachServiceWithFlags(
SVC_RESTARTING,
[] (Service* s) {
s->RestartIfNeeded(process_needs_restart);
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
从上面可以看出,该函数轮询service对应的链表,对于有SVC_RESTARING标志的service执行RestartIfNeeded(如上文所述,当子进程终止时,init进程会将可被重启进程的服务标志位置为SVC_RESTARTING)。
如下面代码所示,restart_service_if_needed可以重新启动服务。
void Service::RestartIfNeeded(time_t& process_needs_restart)(struct service *svc)
{
time_t next_start_time = svc->time_started + 5;
//两次服务启动时间的间隔要大于5s
if (next_start_time <= gettime()) {
svc->flags &= (~SVC_RESTARTING);
//满足时间间隔的要求后,重启服务
//Start将会重新fork服务进程,并做相应的配置
Start(svc, NULL);
return;
}
//更新main函数中,while循环需要等待的时间
if ((next_start_time < process_needs_restart) ||
(process_needs_restart == 0)) {
process_needs_restart = next_start_time;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
查阅资料发现:Bootchart 是一个能对 GNU/Linux boot 过程进行性能分析并把结果直观化的工具。它在 boot 过程中搜集资源利用情况及进程信息然后以PNG, SVG或EPS格式来显示结果。BootChart 包含数据收集工具和图像产生工具。数据收集工具在原始的BootChart中是独立的shell程序,但在Android中,数据收集工具被集成到了init 程序中。资料与代码基本吻合。
结束语
以上是自己目前对init进程的初步理解,由于经验原因,可能会有错误或不到位的地方,会持续改进。
修改记录
这篇文章原来是写Android 6.0中的init进程的,2016.8.24第一次拿到Android 7.0的代码,决定重新分析一下init进程。
原本以为init进程改动应该比较小,于是在原有的博客上直接更新,而没有选择重新新写一篇博客。
现在看来自己失误了,6.0和7.0整体的流程几乎不变,但7.0为了更好的封装性,修改了解析文件后存储用的数据结构,同时引入了一些多态相关的内容。
虽然人要往后看,但6.0中还是有很多有意思的地方,比如keywords.h的宏替换及利用listnode来组织通用的链表等。这部分内容,可以参阅一下《深入理解Android 卷I》,虽然书对应的代码比较老,但大体思想还是相似的。
-
-
nowing_fly
2017-01-15 15:48 2楼 -
不会有两个进程,execv执行的时候会替换前面的init进程,只是保持进程号不变,所以只会有一个init进程。
-
-
-
Gaugamela
2017-01-15 15:59 -
回复nowing_fly:ok,表述方式已改“于是,最终设计为用is_first_stage进行区分init进程的运行状态。init进程在运行的过程中,会完成从内核态到用户态的切换。”
-
DT235201314
2017-08-20 14:52 3楼