init进程
本篇基于android2.2.3
init进程是Android启动后,由内核启动的第一份用户级进程。
内核启动过程
init进程是在顺序执行完start_kernel()函数,init_post()函数,run_init_process()函数后,最后启动执行的
static noinline int init_post(void)
{
if (execute_command) {
run_init_process(execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
...
}
init_post()函数调用run_init_process()函数,获取注册在exxecute_command中的进程文件路径,执行execve()系统调用。execve()函数执行由参数传递过来的文件路径下的进程。
init执行
1.init进程注册信号处理器(即指定信号对应的处理函数)(2.3版本后没有这步)
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
act.sa_mask = 0;
act.sa_restorer = NULL;
sigaction(SIGCHLD, &act, 0);
2.对umask进行清零(umask设置了用户创建文件的默认权限)
umask命令允许你设定文件创建时的缺省模式,对应每一类用户(文件属主、同组用户、其他用户)存在一个相应的umask值中的数字。对于文件来说,这一数字的最大值分别是6。系统不允许你在创建一个文本文件时就赋予它执行权限,必须在创建后用chmod命令增加这一权限。目录则允许设置执行权限,这样针对目录来说,umask中各个数字最大可以到7。
umask(0);
3.创建linux中根文件系统的目录,并挂载分区
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
4.生成/dev/_null_节点文件,标准输入,标准输出,标准错误文件描述符重定向到_null_
open_devnull_stdio();
5.生成/dev/_kmsg_节点文件记录log
log_init();
6.解析init.rc文件(init.rc文件在Android系统运行过程中用于通用的环境设置及与进程相关的定义)
parse_config_file("/init.rc");
7.初始化QEMU设备,设置模拟器环境(2.3版本后没有这步)
qemu_init();
8.从”/proc/cmdline”中读取内核命令行参数,并在读取完后修改此文件的权限,禁止非授权用户操作此文件
import_kernel_cmdline(0);
9.从”/proc/cpuinfo”中读取系统的CPU硬件信息。
get_hardware_name();
10.根据读取的硬件信息来解析特定于硬件的配置信息
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
parse_config_file(tmp);
11.触发在init脚本文件中名字为early-init的action,并且执行其commands,其实是: on early-init
action_for_each_trigger("early-init", action_add_queue_tail);
drain_action_queue();
12.初始化动态设备管理,设备文件有变化时反应给内核
device_fd = device_init();
13.加载启动动画,如果动画打开失败,则在屏幕上打印: A N D R O I D字样。
if( load_565rle_image(INIT_IMAGE_FILE) ) {
fd = open("/dev/tty0", O_WRONLY);
if (fd >= 0) {
const char *msg;
msg = "\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
" A N D R O I D ";
write(fd, msg, strlen(msg));
close(fd);
}
}
14.触发在init脚本文件中名字为init的action,并且执行其commands,其实是:on init
action_for_each_trigger("init", action_add_queue_tail);
drain_action_queue();
15.启动系统属性服务: system property service
property_set_fd = start_property_service();
16.创建socket用来处理进程信号
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
signal_fd = s[0];
signal_recv_fd = s[1];
fcntl(s[0], F_SETFD, FD_CLOEXEC);
fcntl(s[0], F_SETFL, O_NONBLOCK);
fcntl(s[1], F_SETFD, FD_CLOEXEC);
fcntl(s[1], F_SETFL, O_NONBLOCK);
}
17.触发在init脚本文件中名字为early-boot和boot的action,并且执行其commands
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
drain_action_queue();
18.启动所有属性变化触发命令,其实是: on property:ro.xx.xx=xx
queue_all_property_triggers();
drain_action_queue();
19.事件处理循环
for(;;) {
int nr, i, timeout = -1;
for (i = 0; i < fd_count; i++)
ufds[i].revents = 0;
drain_action_queue();
restart_processes();
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
分析及运行init.rc文件
分析init.rc文件
动作列表用于创建所需目录,以及为某些特定文件指定权限。
服务列表用于记录初始化程序需要启动的一些程序
Android init脚本语言的规范
Android初始化语言包含了四种类型的声明:
- Actions(动作)
- Commands(命令)
- Services(服务)
Options(选项)
初始化语言以行为单位,以空格间隔的语言符号组成。
- C风格的反斜杠转义符可以用来在语言符号中插入空格。
- 双引号也可以用来防止文本被空格分成多个语言符号。
- 当反斜杠在行末时,作为折行符。
- 以#开始的行为注释行
- Actions和Service隐含声明一个新的段落,所有该段落下的Command与Option的声明皆属于该段落
- Actions和Service的名称是唯一的。在它们之后声明相同命名的类将被视为无效
Actions(行为)
Actions其实就是一系列的Commands(命令)。Actions都有一个trigger(触发器),它被用于决定action的执行时间。当一个符合action触发条件的事件发生时,action会被加入到执行队列的末尾,除非它已经在队列里了。队列中的每一个action都被依次提取出,而这个action中的每个command(命令)都将被依次执行。
Actions的形式如下:
on <trigger>
<command1>
<command2>
<command3>
on后面跟着一个触发器,当trigger被触发时,command1,command2,command3,会依次执行,直到下一个Action或下一个Service。
简单来说,Actions就是Android在启动时定义的一个启动脚本,当条件满足时,会执行该脚本,脚本里都是一些命令commands,不同的脚本用on来区分。
Triggers(触发器)
Triggers(触发器)是一个用于匹配特定事件类型的字符串,用于使Actions发生。
boot
当init程序执行,并载入/init.conf文件时触发.<name>=<value>
当改变属性值时触发device-added-<path>
当添加设备时触发.device-removed-<path>
当设备移除时触发.service-exited-<name>
当指定的服务退出时触发.
Services(服务)
Services(服务)是一个程序,它在初始化时启动,并在退出时可选择让其重启。Services(服务)的形式如下:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
- name:服务名
- pathname:当前服务对应的程序位置
- option:当前服务设置的选项
Options(选项)
Options(选项)是一个Services(服务)的修饰。他们影响Services(服务)在何时,并以何种方式运行。
critical
据设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式。disabled
服务不会自动运行,必须按照名称明确指定后才可以启动setenv <name> <value>
设置环境变量socket <name> <type> <perm> [ <user> [ <group> ] ]
在/dev/socket/下创建一个unix domain的socket,并传递创建的文件描述符fd给服务进程.其中type必须为dgram或stream,seqpacket.用户名和组名默认为0user <username>
在执行此服务之前先切换用户名。当前默认为root.group <groupname> [ <groupname> ]*
类似于user,切换组名,默认组名为rootoneshot
当此服务退出时不会自动重启.class <name>
给服务指定一个类属,这样方便操作多个服务同时启动或停止.默认情况下为default.onrestart
当服务重启时执行一条指令,
Commands(命令)
exec <path> [ <argument> ]*
fork并执行指定路径下的程序,并传递参数.这将阻塞init进程直到程序执行完毕export <name> <value>
设置全局环境参数,此参数被设置后对所有进程都有效.ifup <interface>
使指定的网络接口”上线”,相当激活指定的网络接口import <filename>
导入一个额外的init配置文件.hostname <name>
设置主机名chdir <directory>
改变工作目录.chmod <octal-mode> <path>
改变指定文件的读取权限.chown <owner> <group> <path>
改变指定文件的拥有都和组名的属性.chroot <directory>
改变进行的根目录.class_start <serviceclass>
启动所有指定服务类下的未运行服务class_stop <serviceclass>
停止指定服务类下的所有已运行的服务domainname <name>
设置域名insmod <path>
安装指定路径的模块.mkdir <path> [mode] [owner] [group]
用指定参数创建一个目录,在默认情况下,创建的目录读取权限为755.用户名为root,组名为root.mount <type> <device> <dir> [ <mountoption> ]*
类似于linux的mount(挂载)指令setkey
TBD(To Be Determined),待定.setprop <name> <value>
设置属性及对应的值.setrlimit <resource> <cur> <max>
设置资源的rlimit(资源限制)start <service>
如果指定的服务未启动,则启动它.stop <service>
如果指定的服务当前正在运行,则停止它.symlink <target> <path>
创建一个符号链接.sysclktz <mins_west_of_gmt>
设置系统基准时间.trigger <event>
触发一个事件。用于将一个action与另一个 action排列write <path> <string> [ <string> ]*
往指定的文件写字符串.
Properties(属性)
Init更新一些系统属性以提供对正在发生的事件的监控能力:
init.action
当前正在执行的动作,如果没有则为空字符串””init.command
当前正在执行的命令.没有则为空字符串.init.svc.<name>
当前某个服务的状态,可为”stopped”, “running”, “restarting”
init.rc文件分析函数
init/main.c调用parse_config_file(const char *fn)函数来分析init.rc脚本文件
int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);//读取文件
if (!data) return -1;
parse_config(fn, data);//分析读入的字符串
DUMP();
return 0;
}
parse_config()函数会分析read_file()函数返回的字符串,并生成动作列表(Action List)与服务列表(Service List)
static void parse_config(const char *fn, char *s)
{
...
for (;;) {
switch (next_token(&state)) {//以行为单位分割参数传递过来的字符串
...
case T_NEWLINE:
if (nargs) {
int kw = lookup_keyword(args[0]);//返回init.rc脚本中每行首个单词在keyword_list结构体数组中的数组编号
if (kw_is(kw, SECTION)) {//判断是否为SECTION
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
...
}
}
}
KEYWORD宏转换为keyword_info结构体数组形成的列表,其形式为:【“列表编号”】={“关键字”,“组”,“参数个数”,“映射函数”}
parse_new_section()函数将kw_is()宏筛选出的命令,分别注册动动作列表或服务列表中。在parse_new_section()函数会将服务列表与动作列表分别保存到全局变量service_list与action_list中
执行early-init动作列表
Android 中的ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点)
执行init动作列表
在“on init”根文件系统挂载部分,主要挂载/system与/data两个目录。两个目录挂载完毕后,Android的根文件系统就准备好了
动作列表的运行
void drain_action_queue(void)
{
struct listnode *node;
struct command *cmd;
struct action *act;
int ret;
while ((act = action_remove_queue_head())) {
INFO("processing action %p (%s)\n", act, act->name);
list_for_each(node, &act->commands) {
cmd = node_to_item(node, struct command, clist);//从action_queue中取出动作列表,并转换成command结构体
ret = cmd->func(cmd->nargs, cmd->args);
INFO("command '%s' r=%d\n", cmd->args[0], ret);
}
}
}
action_remove_queue_head()函数用来获取全局链表action_queue的head,action_queue保存有由待执行的命令构成的动作列表
command结构体的func变量指定与动作列表中的命令相对应的函数,即各命令的映射函数
服务列表的运行
on boot段落中,最后一行命令为class_start,init进程通过该命令运行“service”段落中的所有程序。class_start命令对应的执行函数为do_class_start()
int do_class_start(int nargs, char **args)
{
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
do_class_start调用service_for_each_class
void service_for_each_class(const char *classname, void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) { // 遍历service的结构体
svc = node_to_item(node, struct service, slist); // 从slist里取出每一个结构体
if (!strcmp(svc->classname, classname)) { // 如果名字是匹配的话,就会进入这个判断
func(svc); // 执行service_start_if_not_disable, 并且将当前的service结构体给传递进去
}
}
}
service_for_each_class()会遍历service_list链表,找到所有和classname匹配的service节点,如果这个节点没有被disabled的话,那么就启动其对应的服务
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
}
}
void service_start(struct service *svc, const char *dynamic_args)
{
struct stat s;
pid_t pid;
int needs_console;
int n;
// 这个service即将被启动,将其从disable或reset的状态给移除掉,置其为重新运行的状态
svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING));
svc->time_started = 0;
// 如果这个service仍然是运行态的话,即return
if (svc->flags & SVC_RUNNING) {
return;
}
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
if (needs_console && (!have_console)) {
ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED;
return;
} // 如果这个service的flags是初始console,但是这个已经启动了的话,就会设置当前的flags为disabled
if (stat(svc->args[0], &s) != 0) {// 如果要执行的这个service的start的command不存在的话,返回error
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
...
NOTICE("starting '%s'\n", svc->name);
// fork一个子进程,即所有从init.rc启动的service,都是一个子进程
pid = fork();
if (pid == 0) { // pid = 0, 进入到子进程中
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
// 得到属性存储空间的信息并加入到环境变量中
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
// 将service自己声明的envvars加入到环境变量中
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
// 根据socket info设置socket
for (si = svc->sockets; si; si = si->next) {
int s = create_socket(si->name,
!strcmp(si->type, "dgram") ?
SOCK_DGRAM : SOCK_STREAM,
si->perm, si->uid, si->gid);
if (s >= 0) {
publish_socket(si->name, s);
}
}
if (svc->ioprio_class != IoSchedClass_NONE) {
if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) {
ERROR("Failed to set pid %d ioprio = %d,%d: %s\n",
getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno));
}
}
if (needs_console) {
setsid();
open_console();
} else {
zap_stdio();
}
#if 0
for (n = 0; svc->args[n]; n++) {
INFO("args[%d] = '%s'\n", n, svc->args[n]);
}
for (n = 0; ENV[n]; n++) {
INFO("env[%d] = '%s'\n", n, ENV[n]);
}
#endif
setpgid(0, getpid());
if (svc->gid) { // 设置gid
setgid(svc->gid);
}
if (svc->nr_supp_gids) {
setgroups(svc->nr_supp_gids, svc->supp_gids);
}
if (svc->uid) { // 设置uid
setuid(svc->uid);
}
// 因为dynamic_args设置的为null,我们在第一次从init.rc启动的时候,一定会进入到这个判断。
if (!dynamic_args) {
//执行当前的service的启动的命令,也就是说从这边开始,我们就可以理解为已经从init进程中,去像kernel执行init一样,就去执行各个service所对应的启动函数了!
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
} else {
char *arg_ptrs[INIT_PARSER_MAXARGS+1];
int arg_idx = svc->nargs;
char *tmp = strdup(dynamic_args);
char *next = tmp;
char *bword;
...
}
...
}
生成设备驱动节点
init进程通过两种方式创建设备节点。第一种,以预先定义的设备信息为基础,当init进程被启动运行时,同一创建设备节点文件,这种连接已定义的设备的方法,称为“冷拔插”,第二种,在系统运行时,当有设备插入USB端口时,init经常就会接收到这一事件,为插入的设备动态创建设备节点文件,这种在系统运行的状态下连接设备,称为“热拔插”
从内核2.6X开始引入udev(userspace device)实用程序。udev以守护进程的形式运行,当设备驱动被加载时,它会掌握主设备号,次设备号,以及设备类型,而后在“/dev”目录下自动创建设备节点文件
在系统运行中,若某个设备被插入,内核就会加载与该设备相关的驱动程序。而后驱动程序会调用启动函数probe(),将主设备号,次设备号,以及设备类型保存到“/sys”文件系统中。然后发出uevent,并传递给udev守护进程
uevent是内核向用户空间进程传递信息的信号系统,即在添加或删除设备时,内核使用uevent将设备信息传递给用户空间。uevent包含设备名称、类别、主设备号、次设备号、设备节点文件创建的目录等信息,并将这些信息传递给udev守护进程
系统内核启动后,udev进程运行在用户空间内,它无法处理内核启动过程中发生的uevent。虽然内核空间内的设备驱动程序可以正常运行,但由于未创建访问设备驱动所需的设备节点文件,将会出现应用程序无法使用相关设备的问题。
Linuxx系统中,在udec守护经常运行前,通过提供与加载的设备驱动程序冷拔插机制,来解决设备节点文件没被创建的问题。
当内核启动后,冷拔插机制启动udev守护进程,从/sys目录下读取事先注册好的设备信息,而后引发与各设备相对应的uevent,创建设备节点文件。Android也采用这种处理方式来创建设备节点文件,不同的是使用init进程来扮演udev守护进程的角色。
创建静态设备节点
内核启动完毕后,init进程启动,对于像Binder驱动程序这样无法创建设备节点文件的驱动,将采用冷拔插方式进行处理。init进程事先获知等待冷拔插处理的驱动程序,并事先定义好各驱动程序的设备节点文件。在Android源代码的devices.c文件中,列出了init进程创建的节点文件的目录。
static struct perms_ devperms[] = {
87 { "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 },
88 { "/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 },
89 { "/dev/full", 0666, AID_ROOT, AID_ROOT, 0 },
90 { "/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 },
91 { "/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 },
...
}
在冷拔插处理时,init进程会引起devperms结构体,在/dev 目录下创建设备节点文件。devperms结构体中,分别列出了等待冷拔插处理的设备节点文件的名称、访问权限、用户ID、组ID。若想为用户定义的新设备创建设备节点文件,需要将相关驱动信息添加到devperms结构体中。
init经常进行冷拔插处理的步骤。
init进程调用device_init()函数
int device_init(void)
{
...
fd = open_uevent_socket();
...
t0 = get_usecs();
coldboot(fd, "/sys/class");
coldboot(fd, "/sys/block");
coldboot(fd, "/sys/devices");
t1 = get_usecs();
log_event_print("coldboot %ld uS\n", ((long) (t1 - t0)));
...
}
deviice_init()函数先创建一个套接字,用来接收uevenr。再通过coldboot()函数调用do_coldboot()函数,对内核启动时注册到/sys,目录下的驱动程序,进行冷拔插处理。
do_coldboot
static void do_coldboot(int event_fd, DIR *d)
{
...
fd = openat(dfd, "uevent", O_WRONLY);
if(fd >= 0) {
write(fd, "add\n", 4);
close(fd);
handle_device_fd(event_fd);
}
...
}
do_coldboot()函数接收参数传递过来的目录路径,通过该路径查找到保存的uevent文件,向相关文件写入“add”信息,而后强制引起uevent。然后在handler_ddevice_fd()函数中接收相关的uevent,获取uevent的信息
void handle_device_fd(int fd)
{
for(;;) {
...
struct uevent uevent;
parse_event(msg, &uevent);
handle_device_event(&uevent);
...
}
}
handle_device_fd()函数在收到uevent时,调用parse_event()函数,将uevent信息希尔uevent结构体。
uevent
struct uevent {
const char *action;
const char *path;
const char *subsystem;
const char *firmware;
int major;
int minor;
};
向uevent结构体写完信息后,调用handle_device_event()函数,创建节点文件。
static void handle_device_event(struct uevent *uevent)
{
...
if(!strncmp(uevent->subsystem, "block", 5)) {
block = 1;
base = "/dev/block/";
mkdir(base, 0755);
}
...
if(!strcmp(uevent->action, "add")) {
make_device(devpath, block, uevent->major, uevent->minor);
return;
}
...
}
handle_device_event()先检查uevent结构体的subsystem变量,而后在/dev目录下创建子目录。subsystem根据硬件用途的不同而表示不同的组。若硬件是存储设备,则subsystem是block,创建的目录为/dev/block。
在创建完所有下层目录后,调用make_device()函数,创建设备节点文件
static void make_device(const char *path, int block, int major, int minor)
301{
...
mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR);
dev = (major << 8) | minor;
setegid(gid);
mknod(path, mode, dev);
chown(path, uid, -1);
setegid(AID_ROOT);
}
make_device()函数从设备节点文件列表中获取用户ID、组ID信息。而后调用mknod函数,创建设备节点文件。
创建动态设备节点
init经常支持热拔插处理,在系统运行中为新的设备创建节点文件。热插拔由init进程的事件处理循环来完成
init.c
int main(int argc, char **argv)
{
for(;;) {
...
nr = poll(ufds, fd_count, timeout);
...
if (ufds[0].revents == POLLIN)
handle_device_fd(device_fd);
...
}
}
init经常的事件处理循环循环调用poll()函数监听来自驱动程序的uevent,而后调用handle_device_fd()函数,创建设备节点文件。
处理子进程终止
若init启动的某个进程终止,则会对系统的运行产生影响。比如“服务管理器”,它是应用程序使用系统服务必须运行的进程。如果该进程出现意外终止,那么进程间的通讯、图像输出、音频输出等功能将无法使用。因此,在init启动的进程中,除了一小部分外,其他大部分进程出现意外终止时,init进程要重新启动它们。
当init的子进程意外终止时,会向父进程init进程传递SIGCHLD信号,init进程接收该信号,检查进程选项是否设置为oneshot,若设置oneshot,init进程将放弃重启进程,否则重启进程。
init进程中的事件处理循环,当其子进程终止时,init会接收传递过来的SIGCHLD信号,并调用与之相对应的处理函数sigchld_handler()
static void sigchld_handler(int s)
{
write(signal_fd, &s, 1);
}
signal_fd记录信号标号,由套接字对创建,信号编号被传递至接收端套接字描述符signal_recv_fd中。由于接收信号编号的signal_recv_fd已被注册至POLL,wait_for_one_process()就会被调用执行
...
for(;;) {
...
//当发生SIGCHLD信号时,程序就会从事件监听状态中跳出,而后执行poll()函数
nr = poll(ufds, fd_count, timeout);
if(nr <= 0)
continue;
if (ufds[2].revents == POLLIN) {
read(signal_recv_fd, tmp, sizeof(tmp));
while (!wait_for_one_process(0));
continue;
}
...
}
...
当ufds[2]被注册进数据,即触发数据输出事件时,signal_recv_fd即调用并执行wait_for_one_process()函数。wait_for_one_process()函数在产生SIGCHLD信号的进程的服务列表中,检查进程的设置选项。若选项非oneshot,则添加重启选项(SVC_RESTARTING).oneshot选项被定义在init.rc文件的service部分中,若进程带有oneshot选项。进程终止时不会被重启
static int wait_for_one_process(int block)
{
...
while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
...
svc = service_find_by_pid(pid);
...
if (!(svc->flags & SVC_ONESHOT)) {
kill(-pid, SIGKILL);
...
}
//删除进程持有的所有socketDescriptor
for (si = svc->sockets; si; si = si->next) {
...
unlink(tmp);
}
//删除SVC_RUNNING
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
//将已设置进程标记为SVC_DISABLED,并从wait_for_one_process()函数中跳出,相关进程将不被重新启动
if (svc->flags & SVC_ONESHOT) {
svc->flags |= SVC_DISABLED;
}
if (svc->flags & SVC_DISABLED) {
notify_service_state(svc->name, "stopped");
return 0;
}
...
//向当前服务项的标记中添加SVC_RESTART。
svc->flags |= SVC_RESTARTING;
//检查待重启的进程在init.rc文件中是否带有onrestart选项
list_for_each(node, &svc->onrestart.commands) {
cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
...
}
- 当产生信号的进程被终止时,waitpid()函数用来回收进程所占用的资源,它带有三个参数。其中,第一个参数pid为欲等待的子进程的识别码,设置为-1,表示查看所有子进程是否发出SIGCHLD信号,第二个参数status,用于返回子进程的结束状态,第三个参数决定waitpid()函数是否应用阻塞处理方式。waitpid()函数返回pid值,返回值即是产生SIGCHLD信号的进程的pid号
- service_find_by_pid()函数用来取出与服务列表中终止经常相关的服务项目
- 在取出的服务项目选项中,检查SVC_ONESHOT是否已设置。SVC_ONESHOT表示经常仅运行一次,带有此选项的进程在运行一次后,不会被重新启动,由kill(-pid,SIGKILL)函数终止
当wait_for_one_process()函数 执行完毕后,事件处理循环中的restart_process()函数就会被调用执行。
static void restart_processes()
{
process_needs_restart = 0;
service_for_each_flags(SVC_RESTARTING,restart_service_if_needed);
}
restart_processes()函数运行服务列表中带有SVC_RESTART标记的进程。当一个带有此标记的进程被终止,产生SIGCHLD信号时,restart_processes()函数将重新启动它。
属性服务
属性变更请求是init事件处理循环处理的另一个事件。在Android平台中,为了让运行中的所有进程共享系统运行时所需要的各种设置值,系统开辟了属性存储区域,并提供了访问该区域的API.属性由键与值构造,其表现形式为“键=值”
在Android平台中,访问属性值时,添加了访问权限控制,增强了访问的安全性。
系统中所有运行的进程都可以访问属性值,但仅有init进程才能修改属性值。
在其他进程修改属性值时,必须向init进程提出请求,最终由init进程负责修改属性值。在此过程中,init进程会先检查各属性的访问权限,而后再修改属性值
当属性值更改后,若定义在init.rc文件中的某个特定条件得到满足,则与此条件相匹配的动作就会发生
init经常的main()函数中,调用property_init()函数,用来初始化属性域
int main(int argc, char **argv)
{
...
property_init();
...
}
void property_init(void)
{
init_property_area();
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}
property_init()函数首先在内存中开辟一块共享区域,而后将其作用在ashmen(Android Shared Menory)。外部进程可以访问这块共享内存域,获取属性值,但它们不能通过直接访问共享内存域的方法来更改属性值。一个进程若想更改属性值,必须先向init进程提交属性变更请求,由init进程更改共享内存中的属性值
init_property_area()函数被调用执行后,所创建的属性域被初始化。
属性域的起始1024个字节作为属性域头,用来保存管理属性表所需要的一些数值。其余31616个字节空间被划分成247块,每块大小为128字节,用来保存属性值。
访问属性值使用property_get()
修改属性值使用property_set()
在属性域完成初始化之后,就会从指定的文件中读取初始值,并设置为属性值
init的main()函数中调用了start_property_service()函数,用来创建启动属性服务所需要的Unix域套接字,并保存套接字描述符。
int main(int argc, char **argv)
{
...
property_set_fd = start_property_service();
...
}
int start_property_service(void)
{
int fd;
//读取存储在各文件中的基本设置值,将它们设置为属性值
load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
//读取保存在/data/property目录中的属性值
load_persistent_properties();
//创建名称为/dev/socket/property_service的Unix域套接字
fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
if(fd < 0) return -1;
fcntl(fd, F_SETFD, FD_CLOEXEC);
fcntl(fd, F_SETFL, O_NONBLOCK);
listen(fd, 8);
return fd;
}
/data/property目录中保存着系统运行中其他进程新生成的属性值或更改的属性值,属性的key被用作文件名,value被保存在文件中。
通过上面创建的Unix域套接字,接收到属性变更请求后,init进程就会调用handle_property_set_fd()函数
void handle_property_set_fd(int fd)
{
...
struct ucred cr;
...
//从套接字获取SO_PEERCRED值,以便检查传递信息的进程的访问权限
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
ERROR("Unable to recieve socket options\n");
return;
}
...
switch(msg.cmd) {
case PROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if(memcmp(msg.name,"ctl.",4) == 0) {
//检查访问权限,仅有system server、root以及相关进程才能使用ctl消息,终止或启动进程
if (check_control_perms(msg.value, cr.uid, cr.gid)) {
handle_control_message((char*) msg.name + 4, (char*) msg.value);
}
...
} else {
if (check_perms(msg.name, cr.uid, cr.gid)) {
property_set((char*) msg.name, (char*) msg.value);
}
...
}
break;
default:
break;
}
}
在struct ucred结构体,存储着传递次那个系的进程的uid、pid与gid值。通过此结构体中的值,以及消息的类型,检查进程的访问权限。
在属性消息中,以“ctl”开头的消息并非请求更改系统属性值的消息,而是请求进程启动与终止的消息。除此之外,其他消息都被用来更改系统的属性值,
check_perms()函数检查访问权限。各属性的访问权限采用Linux的uid进行区分,其定义如下
property_perms
struct {
const char *prefix;
unsigned int uid;
unsigned int gid;
} property_perms[] = {
{ "net.rmnet0.", AID_RADIO, 0 },
{ "net.gprs.", AID_RADIO, 0 },
{ "net.ppp", AID_RADIO, 0 },
{ "ril.", AID_RADIO, 0 },
{ "gsm.", AID_RADIO, 0 },
{ "persist.radio", AID_RADIO, 0 },
{ "net.dns", AID_RADIO, 0 },
{ "net.", AID_SYSTEM, 0 },
{ "dev.", AID_SYSTEM, 0 },
{ "runtime.", AID_SYSTEM, 0 },
{ "hw.", AID_SYSTEM, 0 },
{ "sys.", AID_SYSTEM, 0 },
{ "service.", AID_SYSTEM, 0 },
{ "wlan.", AID_SYSTEM, 0 },
{ "dhcp.", AID_SYSTEM, 0 },
{ "dhcp.", AID_DHCP, 0 },
{ "vpn.", AID_SYSTEM, 0 },
{ "vpn.", AID_VPN, 0 },
{ "debug.", AID_SHELL, 0 },
{ "log.", AID_SHELL, 0 },
{ "service.adb.root", AID_SHELL, 0 },
{ "persist.sys.", AID_SYSTEM, 0 },
{ "persist.service.", AID_SYSTEM, 0 },
{ "persist.security.", AID_SYSTEM, 0 },
{ NULL, 0, 0 }
};
若要在系统运行中biang属性设置,应充分考虑各属性的访问权限。
property_set()函数会接着调用property_changed()函数
int property_set(const char *name, const char *value)
{
...
property_changed(name, value);
...
}
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled) {
queue_property_triggers(name, value);
drain_action_queue();
}
}
在init.rc脚本文件中,记录着某个属性改变后要采取的动作,动作执行的条件以“on property:<key>=<value>
”形式给出。当某个条件相关的键值被设定后,与该条件相关的触发其就会被触发。
参考资料
Amdroid框架揭秘
Android init进程启动
Android init进程启动
Android启动流程分析(十) action的执行和service的启动