# 标签: 读博客
长文预警,请注意。
最开始学习系统框架的时候,是从android boot progress开始的。
然后很多需求都要求你知道系统的启动,流程。所以知道init进程还是很有必要的,然而这里主要谈用户态的init。
本文转载自:(我给它整合了,并适当修改了一些笔误或者说的不准确的内容)
http://blog.csdn.net/zhgxhuaa/article/details/22994095
http://blog.csdn.net/zhgxhuaa/article/details/23059849
http://blog.csdn.net/zhgxhuaa/article/details/23104119
http://blog.csdn.net/zhgxhuaa/article/details/24153441
本文需要java, c++, c, linux等相关知识 (没有怎么办,没有就看流程,千万别等我会了xxx再说。。。就要现在)
tip: 一定记住它的几个流程图!(比代码重要,代码只要有源码,你看不懂么?呵呵)
众所周知,Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。Android是基于Linux的操作系统,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。下面先简单的看一下init进程的启动过程。
@/kernel/goodfish/init/main.c
kernel_init()
static int __init kernel_init(void * unused) { /* * Wait until kthreadd is all set-up. */ wait_for_completion(&kthreadd_done); /* * init can allocate pages on any node */ set_mems_allowed(node_states[N_HIGH_MEMORY]); /* * init can run on any cpu. */ set_cpus_allowed_ptr(current, cpu_all_mask); cad_pid = task_pid(current); smp_prepare_cpus(setup_max_cpus); do_pre_smp_initcalls(); lockup_detector_init(); smp_init(); sched_init_smp(); do_basic_setup(); /* Open the /dev/console on the rootfs, this should never fail */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); (void) sys_dup(0); /* * check if there is an early userspace init. If yes, let it do all * the work */ if (!ramdisk_execute_command) ramdisk_execute_command = "/init"; if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) { ramdisk_execute_command = NULL; prepare_namespace(); } /* * Ok, we have completed the initial bootup, and * we‘re essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ init_post(); return 0; }
init_post()
/* This is a non __init function. Force it to be noinline otherwise gcc * makes it inline to init() and it becomes part of init.text section */ static noinline int init_post(void) { /* need to finish all async __init code before freeing the memory */ async_synchronize_full(); free_initmem(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy(); current->signal->flags |= SIGNAL_UNKILLABLE; if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); } run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); }
run_init_process()
static void run_init_process(const char *init_filename) { argv_init[0] = init_filename; kernel_execve(init_filename, argv_init, envp_init); }
在init_post()中会判断execute_command是否为空,如果不为空则执行run_init_process调用。execute_command的赋值在init_setup()中,所以这里应该注意在设置内核启动选项时,应设置为“ init=/init”,以便正常启动init进程,因为编译完Android后生成的文件系统中,init位于最顶层目录。 (可以在手机的根目录看到init文件)
static const char * argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };
static int __init init_setup(char *str) { unsigned int i; execute_command = str; /* * In case LILO is going to boot us with default command line, * it prepends "auto" before the whole cmdline which makes * the shell think it should execute a script with such name. * So we ignore all arguments entered _before_ init=... [MJ] */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("init=", init_setup);
当根目录中不存在init时,或者未指定启动项“init=”时,内核会到/sbin、/etc、/bin目录下查找init。
了解了init进程的启动过程后,接下来看一下init进程都干了些什么?Android中的init进程与Linux不同,其职责可以归结如下:
作为守护进程
解析和执行init.rc文件
生成设备驱动节点
属性服务
init进程的入口函数是main,它的代码如下:
@/system/core/init/init.c
int main(int argc, char **argv) { int fd_count = 0; struct pollfd ufds[4]; char *tmpdev; char* debuggable; char tmp[32]; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; //启动ueventd if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); //启动watchdogd if (!strcmp(basename(argv[0]), "watchdogd")) return watchdogd_main(argc, argv); /* clear the umask */ umask(0); /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we‘ll * let the rc file figure out the rest. */ //创建并挂在启动所需的文件目录 mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "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); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));//检测/dev/.booting文件是否可读写和创建 /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won‘t * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ open_devnull_stdio();//重定向标准输入/输出/错误输出到/dev/_null_ klog_init();//log初始化 property_init();//属性服务初始化 //从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware get_hardware_name(hardware, &revision); //导入并设置内核变量 process_kernel_cmdline(); //selinux相关,暂不分析 union selinux_callback cb; cb.func_log = klog_write; selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* 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. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon_recursive("/sys"); is_charger = !strcmp(bootmode, "charger");//关机充电相关,暂不做分析 INFO("property init\n"); if (!is_charger) property_load_boot_defaults(); INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析init.rc配置文件 /* * 解析完init.rc后会得到一系列的action等,下面的代码将执行处于early-init阶段的action。 * init将action按照执行时间段的不同分为early-init、init、early-boot、boot。 * 进行这样的划分是由于有些动作之间具有依赖关系,某些动作只有在其他动作完成后才能执行,所以就有了先后的区别。 * 具体哪些动作属于哪个阶段是在init.rc中的配置决定的 */ action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ if (!is_charger) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); } /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random * wasn‘t ready immediately after wait_for_coldboot_done */ queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); if (is_charger) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); #if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init"); #endif for(;;) {//init进入无限循环 int nr, i, timeout = -1; //检查action_queue列表是否为空。如果不为空则移除并执行列表头中的action execute_one_command(); restart_processes();//重启已经死去的进程 if (!property_set_fd_init && get_property_set_fd() > 0) { ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; property_set_fd_init = 1; } if (!signal_fd_init && get_signal_fd() > 0) { ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; signal_fd_init = 1; } if (!keychord_fd_init && get_keychord_fd() > 0) { ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; keychord_fd_init = 1; } if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } if (!action_queue_empty() || cur_action) timeout = 0; #if BOOTCHART if (bootchart_count > 0) { if (timeout < 0 || timeout > BOOTCHART_POLLING_MS) timeout = BOOTCHART_POLLING_MS; if (bootchart_step() < 0 || --bootchart_count == 0) { bootchart_finish(); bootchart_count = 0; } } #endif //等待事件发生 nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd())//处理属性服务事件 handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd())//处理keychord事件 handle_keychord(); else if (ufds[i].fd == get_signal_fd())//处理 handle_signal();//处理SIGCHLD信号 } } } return 0; }
if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv);
main函数一开始就会判断参数argv[0]的值是否等于“ueventd”,如果是就调用ueventd进程的入口函数ueventd_main()启动ueventd进程。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于“ueventd”?所以这里有必要看一下ueventd的启动过程,ueventd是在init.rc中被启动的。
on boot service ueventd /sbin/ueventd class core critical seclabel u:r:ueventd:s0
可以看出ueventd可执行文件位于/sbin/ueventd,在观察了/sbin/ueventd后我们发现,它只不过是是可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。
所以,整个过程是这样的:内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init(此时init中main函数的参数argv[0] = “/sbin/ueventd”)。因此,通过判断参数argv[0]的值,就可以知道当前正在启动的是init进程还是ueventd进程。
PS:ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点),其实现位于@system/core/init/ueventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件。
在开始所有的工作之前,main进程首先做的是创建并挂载启动所需的(其他的会在解析init.rc时创建)文件目录,如下所示:
/* Get the basic filesystem setup we need put * together in the initramdisk on / and then we‘ll * let the rc file figure out the rest. */ //创建并挂在启动所需的文件目录 mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "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);
说明:
tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘文件系统。
/dev目录保存着硬件设备访问所需要的设备驱动程序。在Android中,将相关目录作用于tmpfs,可以大幅度提高设备访问的速度。
devpts是一种虚拟终端文件系统。
proc是一种虚拟文件系统,只存在于内存中,不占用外存空间。借助此文件系统,应用程序可以与内核内部数据结构进行交互。
sysfs是一种特殊的文件系统,在Linux 2.6中引入,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息,将proc、devpts、devfs三种文件系统统一起来。
编译Android系统源码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,有init进程在运行中生成,当系统终止时,它们就会消失。上面的代码所形成的的文件层次结构为:
/* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won‘t be able to remount read-only later on. * Now that tmpfs is mounted on /dev, we can actually talk to the outside world. */ open_devnull_stdio();//重定向标准输入/输出/错误输出到/dev/_null_
open_devnull_stdio()函数的作用是重定向标准输入/输出/错误输出到/dev/_null_,至于为什么要重定向的原因在注释中已经写明。open_devnull_stdio()的实现如下:
@system/core/init/util.c
void open_devnull_stdio(void) { int fd; 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 >= 0) { dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (fd > 2) { close(fd); } return; } } exit(1); }
klog_init();
klog_init()用于初始化log,通过其实现可以看出log被打印到/dev/__kmsg__文件中。主要在代码中最后通过fcntl和unlink使得/dev/__kmsg__不可被访问,这就保证了只有log程序才可以访问。
void klog_init(void) { static const char *name = "/dev/__kmsg__"; if (klog_fd >= 0) return; /* Already initialized */ if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { klog_fd = open(name, O_WRONLY); if (klog_fd < 0) return; fcntl(klog_fd, F_SETFD, FD_CLOEXEC); unlink(name); } }
property_init
属性服务初始化,这里先不深究,接下来会单独分析。
//从/proc/cpuinfo中读取Hardware名 //在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware get_hardware_name(hardware, &revision);
get_hardware_name()函数的作用是从/proc/cpuinfo中获取Hardware和Revision的值,并保持到全局变量hardware和revision中。
下面的截图是在我的手机上的CPU info信息:
这里获取hardware信息有什么用呢?在main()函数后面的代码中,我们可以看见这样一句:
//导入并设置内核变量 process_kernel_cmdline();
下面看一下process_kernel_cmdline的实现:
@system/core/init/init.c
static void process_kernel_cmdline(void) { /* don‘t expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); /* first pass does the common stuff, and finds if we are in qemu. * second pass is only necessary for qemu to export all kernel params * as props. */ import_kernel_cmdline(0, import_kernel_nv); if (qemu[0]) import_kernel_cmdline(1, import_kernel_nv); /* now propogate the info given on command line to internal variables * used by init as well as the current required properties */ export_kernel_boot_props(); }
static void export_kernel_boot_props(void) { char tmp[PROP_VALUE_MAX]; ...... /* if this was given on kernel command line, override what we read * before (e.g. from /proc/cpuinfo), if anything */ ret = property_get("ro.boot.hardware", tmp); if (ret) strlcpy(hardware, tmp, sizeof(hardware)); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); ...... }
process_kernel_cmdline()函数用于导入和设置一些内核变量,在export_kernel_boot_props()中我们看见将hardware的值赋值给了属性"ro.hardware"。那这个赋值又是干什么的呢?我们再看一下main()函数,在解析init.rc配置文件的时候,有没有发现少了点什么?
INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析init.rc配置文件
是的,在以前比较老的代码中(例如2.3和4.0)这里除了init.rc以外还会有一个与硬件相关的rc脚本,如下:
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); init_parse_config_file(tmp);
那现在这段代码跑去哪里了呢?我们在init.rc中找到了它:
所以,之前设置的ro.hardware的值是在这里用的,在init.rc中用来导入init.${ro.hardware}.rc脚本,然后一起进行解析。与之前相比,这里只是方式变了,本质上还是一样的。
INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析init.rc配置文件 action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ if (!is_charger) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); } /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random * wasn‘t ready immediately after wait_for_coldboot_done */ queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); if (is_charger) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", action_add_queue_tail); } /* run all property triggers based on current state of the properties */ queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init"); if
这部分代码用于解析init.rc脚本,并触发执行解析生成的action。
在main()函数的最后,init进入了一个无限循环,并等待一些事情的发生。即:在执行完前面的初始化工作以后,init变为一个守护进程。init所关心的事件有三类:属性服务事件、keychord事件和SIGNAL,当有这三类事件发生时,init进程会调用相应的handle函数进行处理。
我转载,我插嘴,抱歉啦:
Init的四个作用这里已经说了俩了: 生成设备节点,作为守护进程处理相应的事件(属性服务以及其他SIGNAL),
还有俩作用下面说:
生成设备驱动节点
属性服务
对配置文件(init.rc)的解析做一下分析。
init.rc文件不同于init进程,init进程仅当编译完Android后才会生成,而init.rc文件存在于Android平台源代码中。init.rc在源代码中的位置为:@system/core/rootdir/init.rc。init.rc文件的大致结构如下图所示:
关于init.rc脚本的介绍,在@system/core/init/readme.txt中有完整的介绍,这里不再赘述,不想看英文的朋友也可以看下面的部分,这个部分关于rc脚本的介绍转自http://blog.csdn.net/nokiaguy/article/details/9109491。相当于readme的翻译吧。
init.rc文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析init.c及其相关文件的源代码毫无意义。
为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码,在<Android源代码根目录>out/target/product/geneic/root目录也可找到init.rc文件。
AIL由如下4部分组成。
1. 动作(Actions)
2. 命令(Commands)
3.服务(Services)
4. 选项(Options)
这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用反斜杠将多行代码连接成一行代码。
AIL的注释与很多Shell脚本一行,以#开头。
AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和Options在第一个Section之前被定义,它们将被忽略。
Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
下面来看看Actions、Services、Commands和Options分别应如何设置。
Actions的语法格式如下:
on <trigger> <command> <command> <command>
也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action
on boot ifup lo hostname localhost domainname localdomain
其中boot是触发器,下面三行是command
那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。
1. boot
这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger
2. <name>=<value>
当属性<name>被设置成<value>时被触发。例如,
on property:vold.decrypt=trigger_reset_main
class_reset main
3. device-added-<path>
当设备节点被添加时触发
4. device-removed-<path>
当设备节点被移除时添加
5. service-exited-<name>
会在一个特定的服务退出时触发
Actions后需要跟若干个命令,这些命令如下:
1. exec <path> [<argument> ]*
创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。
2. export <name> <value>
在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)
3. ifup <interface>
启动网络接口
4. import <filename>
指定要解析的其他配置文件。常被用于当前配置文件的扩展
5. hostname <name>
设置主机名
6. chdir <directory>
改变工作目录
7. chmod <octal-mode><path>
改变文件的访问权限
8. chown <owner><group> <path>
更改文件的所有者和组
9. chroot <directory>
改变处理根目录
10. class_start<serviceclass>
启动所有指定服务类下的未运行服务。
11 class_stop<serviceclass>
停止指定服务类下的所有已运行的服务。
12. domainname <name>
设置域名
13. insmod <path>
加载<path>指定的驱动模块
14. mkdir <path> [mode][owner] [group]
创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。
15. mount <type> <device> <dir> [<mountoption> ]*
试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re
16. setkey
保留,暂时未用
17. setprop <name><value>
将系统属性<name>的值设为<value>。
18. setrlimit <resource> <cur> <max>
设置<resource>的rlimit (资源限制)
19. start <service>
启动指定服务(如果此服务还未运行)。
20.stop<service>
停止指定服务(如果此服务在运行中)。
21. symlink <target> <path>
创建一个指向<path>的软连接<target>。
22. sysclktz <mins_west_of_gmt>
设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)
23. trigger <event>
触发一个事件。用于Action排队
24. wait <path> [<timeout> ]
等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。
25. write <path> <string> [ <string> ]*
向<path>指定的文件写入一个或多个字符串。
Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:
service <name> <pathname> [ <argument> ]* <option> <option>
例如,下面是一个标准的Service用法
service servicemanager /system/bin/servicemanager class core user system group system critical onrestart restart zygote onrestart restart media onrestart restart surfaceflinger onrestart restart drm
Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:
1. critical
表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。
2. disabled
表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。
3. setenv <name><value>
在进程启动时将环境变量<name>设置为<value>。
4. socket <name><type> <perm> [ <user> [ <group> ] ]
Create a unix domain socketnamed /dev/socket/<name> and pass
its fd to the launchedprocess. <type> must be"dgram", "stream" or "seqpacket".
User and group default to0.
创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。
5. user <username>
在启动这个服务前改变该服务的用户名。此时默认为 root。
6. group <groupname> [<groupname> ]*
在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。
7. oneshot
服务退出时不重启。
8. class <name>
指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。
9. onrestart
当服务重启,执行一个命令(下详)。
在上一篇文章中说过,init将动作执行的时间划分为几个阶段,按照被执行的先后顺序依次为:early-init、init、early-boot、boot。在init进程的main()方法中被调用的先后顺序决定了它们的先后(直接原因), 根本原因是:各个阶段的任务不同导致后面的对前面的有依赖,所以这里的先后顺序是不能乱调整的。
@system/core/init/init.c
early-init主要用于设置init进程(进程号为1)的oom_adj的值,以及启动ueventd进程。oom_adj是Linux和Android中用来表示进程重要性的一个值,取值范围为[-17, 15]。在Android中系统在杀死进程时会根据oom_adj和空闲内存大小作为依据,oom_adj越大越容易被杀死。
Android将程序分成以下几类,按照重要性依次降低的顺序:
on early-init # Set init and its forked children‘s oom_adj. write /proc/1/oom_adj -16 # Set the security context for the init process. # This should occur before anything else (e.g. ueventd) is started. setcon u:r:init:s0 start ueventd
这里设置init进程的oom_adj的值为-16.这里要说明的是,我们现在分析的是init.rc文件,在文件头部我们发现还导入了其他的rc脚本。其他rc脚本中的文件结构与init.rc是类似的。在init进程解析rc脚本时,会将所有rc脚本中的配置安装执行阶段一并解析。即:init.rc中的early-init与init,${hardware}.rc中的early-init是一并解析的。
在执行完early-init以后,接下来就是init阶段。在init阶段主要用来:设置环境变量,创建和挂在文件节点。下面是init接的的不分代码截选:
@system/core/rootdir/init.environ.rc.in
# set up the global environment on init export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin export LD_LIBRARY_PATH /vendor/lib:/system/lib export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system export ANDROID_ASSETS /system/app export ANDROID_DATA /data export ANDROID_STORAGE /storage export ASEC_MOUNTPOINT /mnt/asec export LOOP_MOUNTPOINT /mnt/obb export BOOTCLASSPATH %BOOTCLASSPATH%
以前设置环境变量这一段时在init.rc中的,现在放到了init.environ.rc.in,这样代码也更清晰一些。
@system/core/rootdir/init.rc
on init sysclktz 0 loglevel 3 # Backward compatibility symlink /system/etc /etc symlink /sys/kernel/debug /d # Right now vendor lives on the same filesystem as system, # but someday that may change. symlink /system/vendor /vendor # Create cgroup mount point for cpu accounting mkdir /acct mount cgroup none /acct cpuacct mkdir /acct/uid mkdir /system mkdir /data 0771 system system mkdir /cache 0770 system cache mkdir /config 0500 root root
接下来是fs相关的几个过程,它们主要用于文件系统的挂载,下面是截取的一小部分代码:
on post-fs # once everything is setup, no need to modify / mount rootfs rootfs / ro remount # mount shared so changes propagate into child namespaces mount rootfs rootfs / shared rec mount tmpfs tmpfs /mnt/secure private rec # We chown/chmod /cache again so because mount is run as root + defaults chown system cache /cache chmod 0770 /cache # We restorecon /cache in case the cache partition has been reset. restorecon /cache
如果你看过以前的版本的init.rc脚本,看到这里会想起,应该还有几行:
mount yaffs2 mtd@system /system mount yaffs2 mtd@userdata /data
这两行用于挂载/system分区和/data分区到yaffs2文件系统。手机领域有多种不同的内存设备,其中NAND闪存设备以其低功耗、重量轻、性能佳等优良特性,受到绝大多数厂商的青睐。NAND闪存采用yaffs2文件系统。
可以看出在Android 4.4中默认已经不再使用yaffs2。在完成文件系统的创建和挂载后,完整的Android根文件系统结构如下:
接下来看一下boot部分,该部分主要用于设置应用程序终止条件,应用程序驱动目录及文件权限等。下面是一部分代码片段:
on boot # basic network init ifup lo hostname localhost domainname localdomain # set RLIMIT_NICE to allow priorities from 19 to -20 setrlimit 13 40 40 # Memory management. Basic kernel parameters, and allow the high # level system server to be able to adjust the kernel OOM driver # parameters to match how it is managing things. write /proc/sys/vm/overcommit_memory 1 write /proc/sys/vm/min_free_order_shift 4 chown root system /sys/module/lowmemorykiller/parameters/adj chmod 0664 /sys/module/lowmemorykiller/parameters/adj chown root system /sys/module/lowmemorykiller/parameters/minfree chmod 0664 /sys/module/lowmemorykiller/parameters/minfree class_start core class_start main
在on boot部分,我们可以发现许多”on property:<name> = <value>"的代码片段,这些是根据属性的触发器,但相应的属性满足一定的条件时,就会触发相应的动作。此外,还有许多service字段,service后面第一项表示服务的名称,第二项表示服务的路径,接下来的第2行等是服务的附加内容,配合服务使用,主要包含运行权限、条件以及重启等相关选项。
前面了解了init.rc脚本的相关内容,接下来我们分析一下init进程是如何解析rc脚本的。首先,在init进程的main()函数中调用init_parse_config_file()函数对属性进行解析,下面就来看一下这个函数:
init_parse_config_file("/init.rc");//解析init.rc配置文件
@system/core/init/init_parser.c
int init_parse_config_file(const char *fn) { char *data; data = read_file(fn, 0); if (!data) return -1; parse_config(fn, data); DUMP(); return 0; }
read_file(fn, 0)函数将fn指针指向的路径(这里即:/init.rc)所对应的文件读取到内存中,保存为字符串形式,并返回字符串在内存中的地址;然后parse_config会对文件进行解析,生成动作列表(Action List)和服务列表(Service List)。关于read_file()函数的实现在@system/core/init/util.c中。下面是parse_config()的实现:
static void parse_config(const char *fn, char *s) { struct parse_state state; struct listnode import_list;//导入链表,用于保持在init.rc中通过import导入的其他rc文件 struct listnode *node; char *args[INIT_PARSER_MAXARGS]; int nargs; nargs = 0; state.filename = fn;//初始化filename的值为init.rc文件 state.line = 0;//初始化行号为0 state.ptr = s;//初始化ptr指向s,即read_file读入到内存中的init.rc文件的首地址 state.nexttoken = 0;//初始化nexttoken的值为0 state.parse_line = parse_line_no_op;//初始化行解析函数 list_init(&import_list); state.priv = &import_list; for (;;) { switch (next_token(&state)) { case T_EOF://如果返回为T_EOF,表示init.rc已经解析完成,则跳到parser_done解析import进来的其他rc脚本 state.parse_line(&state, 0, 0); goto parser_done; case T_NEWLINE: state.line++;//一行读取完成后,行号加1 if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析 int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword if (kw_is(kw, SECTION)) { state.parse_line(&state, 0, 0); parse_new_section(&state, kw, nargs, args); } else { state.parse_line(&state, nargs, args); } nargs = 0;//复位 } break; case T_TEXT://将nexttoken解析的一个text保存到args字符串数组中,nargs的最大值为INIT_PARSER_MAXARGS(64),即init.rc中一行最多不能超过INIT_PARSER_MAXARGS个text(单词) if (nargs < INIT_PARSER_MAXARGS) { args[nargs++] = state.text; } break; } } parser_done: list_for_each(node, &import_list) { struct import *import = node_to_item(node, struct import, list); int ret; INFO("importing ‘%s‘", import->filename); ret = init_parse_config_file(import->filename); if (ret) ERROR("could not import file ‘%s‘ from ‘%s‘\n", import->filename, fn); } }
parse_config()函数,代码虽然很短,实际上却比较复杂。接下来将对其进行详细分析。首先看一下struct parse_state的定义:
struct parse_state { char *ptr;//指针,指向剩余的尚未被解析的数据(即:ptr指向当前解析到的位置) char *text;//一行文本 int line; //行号 int nexttoken;//下一行的标示,T_EOF标示文件结束,T_TEXT表示需要进行解释的文本,T_NEWLINE标示一个空行或者是注释行 void *context;//一个action或者service void (*parse_line)(struct parse_state *state, int nargs, char **args);//函数指针,指向当前行的解析函数 const char *filename;//解析的rc文件 void *priv;//执行import链表的指针 };
next_token()以行为单位分割参数传递过来的字符串。
@system/core/init/parser.c
int next_token(struct parse_state *state) { char *x = state->ptr; char *s; if (state->nexttoken) {//nexttoken的值为0 int t = state->nexttoken; state->nexttoken = 0; return t; } for (;;) { switch (*x) { case 0://到底末尾,解析完成 state->ptr = x; return T_EOF; case ‘\n‘://换行符,返回T_NEWLINE,表示下一个token是新的一行 x++; state->ptr = x; return T_NEWLINE; case ‘ ‘://忽略空格、制表符等 case ‘\t‘: case ‘\r‘: x++; continue; case ‘#‘://在当前解析到的字符为#号时,将指针一直移动到#行的末尾,然后判断下一个字符是T_NEWLINE还是T_EOF while (*x && (*x != ‘\n‘)) x++;//注意x++,当指针移动到#行末尾时,x执行末尾的下一个字符 if (*x == ‘\n‘) { state->ptr = x+1; return T_NEWLINE; } else { state->ptr = x; return T_EOF; } default: goto text;//解析的为普通文本 } } textdone://x指向一个单词的开头位置,s指向末尾位置,将s设置为0(C字符串末尾为0),即表示单词结束 state->ptr = x; *s = 0; return T_TEXT; text: state->text = s = x; textresume: for (;;) { switch (*x) { case 0: goto textdone; case ‘ ‘: case ‘\t‘: case ‘\r‘: x++; goto textdone; case ‘\n‘: state->nexttoken = T_NEWLINE; x++; goto textdone; case ‘"‘: x++; for (;;) { switch (*x) { case 0: /* unterminated quoted thing */ state->ptr = x; return T_EOF; case ‘"‘: x++; goto textresume; default: *s++ = *x++; } } break; case ‘\\‘: x++; switch (*x) { case 0: goto textdone; case ‘n‘: *s++ = ‘\n‘; break; case ‘r‘: *s++ = ‘\r‘; break; case ‘t‘: *s++ = ‘\t‘; break; case ‘\\‘: *s++ = ‘\\‘; break; case ‘\r‘: /* \ <cr> <lf> -> line continuation */ if (x[1] != ‘\n‘) { x++; continue; } case ‘\n‘: /* \ <lf> -> line continuation */ state->line++; x++; /* eat any extra whitespace */ while((*x == ‘ ‘) || (*x == ‘\t‘)) x++; continue; default: /* unknown escape -- just copy */ *s++ = *x++; } continue; default: *s++ = *x++; } } return T_EOF; }
在parse_config()中通过next_token从rc脚本中解析出一行行的rc语句,下面看一下另一个重要的函数lookup_keyword()的实现:
int lookup_keyword(const char *s) { switch (*s++) { case ‘c‘: if (!strcmp(s, "opy")) return K_copy; if (!strcmp(s, "apability")) return K_capability; if (!strcmp(s, "hdir")) return K_chdir; if (!strcmp(s, "hroot")) return K_chroot; if (!strcmp(s, "lass")) return K_class; if (!strcmp(s, "lass_start")) return K_class_start; if (!strcmp(s, "lass_stop")) return K_class_stop; if (!strcmp(s, "lass_reset")) return K_class_reset; if (!strcmp(s, "onsole")) return K_console; if (!strcmp(s, "hown")) return K_chown; if (!strcmp(s, "hmod")) return K_chmod; if (!strcmp(s, "ritical")) return K_critical; break; case ‘d‘: if (!strcmp(s, "isabled")) return K_disabled; if (!strcmp(s, "omainname")) return K_domainname; break; case ‘e‘: if (!strcmp(s, "xec")) return K_exec; if (!strcmp(s, "xport")) return K_export; break; case ‘g‘: if (!strcmp(s, "roup")) return K_group; break; case ‘h‘: if (!strcmp(s, "ostname")) return K_hostname; break; case ‘i‘: if (!strcmp(s, "oprio")) return K_ioprio; if (!strcmp(s, "fup")) return K_ifup; if (!strcmp(s, "nsmod")) return K_insmod; if (!strcmp(s, "mport")) return K_import; break; case ‘k‘: if (!strcmp(s, "eycodes")) return K_keycodes; break; case ‘l‘: if (!strcmp(s, "oglevel")) return K_loglevel; if (!strcmp(s, "oad_persist_props")) return K_load_persist_props; break; case ‘m‘: if (!strcmp(s, "kdir")) return K_mkdir; if (!strcmp(s, "ount_all")) return K_mount_all; if (!strcmp(s, "ount")) return K_mount; break; case ‘o‘: if (!strcmp(s, "n")) return K_on; if (!strcmp(s, "neshot")) return K_oneshot; if (!strcmp(s, "nrestart")) return K_onrestart; break; case ‘p‘: if (!strcmp(s, "owerctl")) return K_powerctl; case ‘r‘: if (!strcmp(s, "estart")) return K_restart; if (!strcmp(s, "estorecon")) return K_restorecon; if (!strcmp(s, "mdir")) return K_rmdir; if (!strcmp(s, "m")) return K_rm; break; case ‘s‘: if (!strcmp(s, "eclabel")) return K_seclabel; if (!strcmp(s, "ervice")) return K_service; if (!strcmp(s, "etcon")) return K_setcon; if (!strcmp(s, "etenforce")) return K_setenforce; if (!strcmp(s, "etenv")) return K_setenv; if (!strcmp(s, "etkey")) return K_setkey; if (!strcmp(s, "etprop")) return K_setprop; if (!strcmp(s, "etrlimit")) return K_setrlimit; if (!strcmp(s, "etsebool")) return K_setsebool; if (!strcmp(s, "ocket")) return K_socket; if (!strcmp(s, "tart")) return K_start; if (!strcmp(s, "top")) return K_stop; if (!strcmp(s, "wapon_all")) return K_swapon_all; if (!strcmp(s, "ymlink")) return K_symlink; if (!strcmp(s, "ysclktz")) return K_sysclktz; break; case ‘t‘: if (!strcmp(s, "rigger")) return K_trigger; break; case ‘u‘: if (!strcmp(s, "ser")) return K_user; break; case ‘w‘: if (!strcmp(s, "rite")) return K_write; if (!strcmp(s, "ait")) return K_wait; break; } return K_UNKNOWN; }
lookup_keyword()主要用解析出args中的关键字,这个函数本身没有什么特别,也非常简单,但是其实现方法在我们自己实现类似通过switch等的查找判断时是值得借鉴的,即:先通过单词的首字母将内容分组,在定位到哪一个组后再依次比较。这样就减少了程序中比较的次数,提高了效率。
case T_NEWLINE: state.line++;//一行读取完成后,行号加1 if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析 int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword if (kw_is(kw, SECTION)) { state.parse_line(&state, 0, 0); parse_new_section(&state, kw, nargs, args); } else { state.parse_line(&state, nargs, args); } nargs = 0;//复位 } break;
在parse_config()中,在找的keyword以后,接下来会判断这个keyword是否是section,是则走解析section的逻辑,否则走其他逻辑。下面我们看一下kw_is的实现:
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
可以看出kw_is只不过是一个宏定义,这里又引出了keyword_info,下面让我们一起来看一下keyword的相关定义:
@system/core/init/keywords.h
#ifndef KEYWORD//如果没有定义KEYWORD则执行下面的分支 //声明一些函数,这些函数即Action的执行函数 int do_chroot(int nargs, char **args); int do_chdir(int nargs, char **args); int do_class_start(int nargs, char **args); int do_class_stop(int nargs, char **args); int do_class_reset(int nargs, char **args); int do_domainname(int nargs, char **args); int do_exec(int nargs, char **args); int do_export(int nargs, char **args); int do_hostname(int nargs, char **args); int do_ifup(int nargs, char **args); int do_insmod(int nargs, char **args); int do_mkdir(int nargs, char **args); int do_mount_all(int nargs, char **args); int do_mount(int nargs, char **args); int do_powerctl(int nargs, char **args); int do_restart(int nargs, char **args); int do_restorecon(int nargs, char **args); int do_rm(int nargs, char **args); int do_rmdir(int nargs, char **args); int do_setcon(int nargs, char **args); int do_setenforce(int nargs, char **args); int do_setkey(int nargs, char **args); int do_setprop(int nargs, char **args); int do_setrlimit(int nargs, char **args); int do_setsebool(int nargs, char **args); int do_start(int nargs, char **args); int do_stop(int nargs, char **args); int do_swapon_all(int nargs, char **args); int do_trigger(int nargs, char **args); int do_symlink(int nargs, char **args); int do_sysclktz(int nargs, char **args); int do_write(int nargs, char **args); int do_copy(int nargs, char **args); int do_chown(int nargs, char **args); int do_chmod(int nargs, char **args); int do_loglevel(int nargs, char **args); int do_load_persist_props(int nargs, char **args); int do_wait(int nargs, char **args); #define __MAKE_KEYWORD_ENUM__//定义一个宏 /* * 定义KEYWORD宏,这里KEYWORD宏中有四个参数,其各自的含义如下: * symbol表示keyword的名称(即init.rc中的关键字); * flags表示keyword的类型,包括SECTION、COMMAND和OPTION三种类型,其定义在init_parser.c中; * nargs表示参数的个数,即:该keyword需要几个参数 * func表示该keyword所对应的处理函数。 * * KEYWORD宏虽然有四个参数,但是这里只用到了symbol,其中K_##symbol中的##表示连接的意思, * 即最后的得到的值为K_symbol。 */ #define KEYWORD(symbol, flags, nargs, func) K_##symbol, enum { K_UNKNOWN, #endif KEYWORD(capability, OPTION, 0, 0)//根据上面KEYWORD的宏定义,这一行就变成了K_capability, KEYWORD(chdir, COMMAND, 1, do_chdir)//key_chdir,后面的依次类推 KEYWORD(chroot, COMMAND, 1, do_chroot) KEYWORD(class, OPTION, 0, 0) KEYWORD(class_start, COMMAND, 1, do_class_start) KEYWORD(class_stop, COMMAND, 1, do_class_stop) KEYWORD(class_reset, COMMAND, 1, do_class_reset) KEYWORD(console, OPTION, 0, 0) KEYWORD(critical, OPTION, 0, 0) KEYWORD(disabled, OPTION, 0, 0) KEYWORD(domainname, COMMAND, 1, do_domainname) KEYWORD(exec, COMMAND, 1, do_exec) KEYWORD(export, COMMAND, 2, do_export) KEYWORD(group, OPTION, 0, 0) KEYWORD(hostname, COMMAND, 1, do_hostname) KEYWORD(ifup, COMMAND, 1, do_ifup) KEYWORD(insmod, COMMAND, 1, do_insmod) KEYWORD(import, SECTION, 1, 0) KEYWORD(keycodes, OPTION, 0, 0) KEYWORD(mkdir, COMMAND, 1, do_mkdir) KEYWORD(mount_all, COMMAND, 1, do_mount_all) KEYWORD(mount, COMMAND, 3, do_mount) KEYWORD(on, SECTION, 0, 0) KEYWORD(oneshot, OPTION, 0, 0) KEYWORD(onrestart, OPTION, 0, 0) KEYWORD(powerctl, COMMAND, 1, do_powerctl) KEYWORD(restart, COMMAND, 1, do_restart) KEYWORD(restorecon, COMMAND, 1, do_restorecon) KEYWORD(rm, COMMAND, 1, do_rm) KEYWORD(rmdir, COMMAND, 1, do_rmdir) KEYWORD(seclabel, OPTION, 0, 0) KEYWORD(service, SECTION, 0, 0) KEYWORD(setcon, COMMAND, 1, do_setcon) KEYWORD(setenforce, COMMAND, 1, do_setenforce) KEYWORD(setenv, OPTION, 2, 0) KEYWORD(setkey, COMMAND, 0, do_setkey) KEYWORD(setprop, COMMAND, 2, do_setprop) KEYWORD(setrlimit, COMMAND, 3, do_setrlimit) KEYWORD(setsebool, COMMAND, 2, do_setsebool) KEYWORD(socket, OPTION, 0, 0) KEYWORD(start, COMMAND, 1, do_start) KEYWORD(stop, COMMAND, 1, do_stop) KEYWORD(swapon_all, COMMAND, 1, do_swapon_all) KEYWORD(trigger, COMMAND, 1, do_trigger) KEYWORD(symlink, COMMAND, 1, do_symlink) KEYWORD(sysclktz, COMMAND, 1, do_sysclktz) KEYWORD(user, OPTION, 0, 0) KEYWORD(wait, COMMAND, 1, do_wait) KEYWORD(write, COMMAND, 2, do_write) KEYWORD(copy, COMMAND, 2, do_copy) KEYWORD(chown, COMMAND, 2, do_chown) KEYWORD(chmod, COMMAND, 2, do_chmod) KEYWORD(loglevel, COMMAND, 1, do_loglevel) KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props) KEYWORD(ioprio, OPTION, 0, 0) #ifdef __MAKE_KEYWORD_ENUM__ KEYWORD_COUNT, }; #undef __MAKE_KEYWORD_ENUM__ #undef KEYWORD//取消KEYWORD宏的定义 #endif
看一下keyword在init_parse.c中是如何被使用的:
#include "keywords.h" #define KEYWORD(symbol, flags, nargs, func) \ [ K_##symbol ] = { #symbol, func, nargs + 1, flags, }, struct { const char *name;//关键字的名称 int (*func)(int nargs, char **args);//对应关键字的处理函数 unsigned char nargs;//参数个数,每个关键字的参数个数是固定的 unsigned char flags;//关键字属性,包括:SECTION、OPTION和COMMAND,其中COMMAND有对应的处理函数,见keyword的定义。 } keyword_info[KEYWORD_COUNT] = { [ K_UNKNOWN ] = { "unknown", 0, 0, 0 }, #include "keywords.h" }; #undef KEYWORD #define kw_is(kw, type) (keyword_info[kw].flags & (type)) #define kw_name(kw) (keyword_info[kw].name) #define kw_func(kw) (keyword_info[kw].func) #define kw_nargs(kw) (keyword_info[kw].nargs)
从上面的代码我们看到一个很有意思的地方,keyword.h头文件被包含引用了两次。
第一次包含keywords.h时,它声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class、K_mkdir等关键字。
第二次包含keywords.h后,得到了keyword_info结构体数组,这个keyword_info结构体数组以前定义的枚举值为索引,存储对应的关键字信息。
flags的取值也在init_parse.c中定义:
#define SECTION 0x01 #define COMMAND 0x02 #define OPTION 0x04
在了解了keyword后,下面我们继续来分析rc脚本的解析,让我们回到之前的代码,继续分析。
case T_NEWLINE: state.line++;//一行读取完成后,行号加1 if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析 int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword if (kw_is(kw, SECTION)) { state.parse_line(&state, 0, 0); parse_new_section(&state, kw, nargs, args); } else { state.parse_line(&state, nargs, args); } nargs = 0;//复位 } break;
解析section的函数为parse_new_section,其实现为:
void parse_new_section(struct parse_state *state, int kw, int nargs, char **args) { printf("[ %s %s ]\n", args[0], nargs > 1 ? args[1] : ""); switch(kw) { case K_service://解析Service state->context = parse_service(state, nargs, args);//当service_list中不存在同名service时,执行新加入service_list中的service if (state->context) {//service为新增加的service时,即:<span style="font-family: Arial, Helvetica, sans-serif;">service_list中不存在同名service</span> state->parse_line = parse_line_service;//制定解析service行的函数为<span style="font-family: Arial, Helvetica, sans-serif;">parse_line_service</span> return; } break; case K_on://解析section state->context = parse_action(state, nargs, args); if (state->context) { state->parse_line = parse_line_action; return; } break; case K_import://解析import parse_import(state, nargs, args); break; } state->parse_line = parse_line_no_op; }
先看一下service的解析:
static void *parse_service(struct parse_state *state, int nargs, char **args) { struct service *svc;//保持Service相关信息 if (nargs < 3) { parse_error(state, "services must have a name and a program\n"); return 0; } if (!valid_name(args[1])) { parse_error(state, "invalid service name ‘%s‘\n", args[1]); return 0; } //service_list中是否已存在同名service<span style="white-space:pre"> </span> svc = service_find_by_name(args[1]); if (svc) {//<span style="font-family: Arial, Helvetica, sans-serif;">如果已存在同名service则直接返回,不再做其他操作</span> parse_error(state, "ignored duplicate definition of service ‘%s‘\n", args[1]); return 0; } nargs -= 2; svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs); if (!svc) { parse_error(state, "out of memory\n"); return 0; } svc->name = args[1]; svc->classname = "default";//设置classname为“default” memcpy(svc->args, args + 2, sizeof(char*) * nargs); svc->args[nargs] = 0; svc->nargs = nargs; svc->onrestart.name = "onrestart"; list_init(&svc->onrestart.commands); list_add_tail(&service_list, &svc->slist);//将service添加到全局链表service_list中 return svc; }
init中使用了一个叫做service的结构体来保存与service相关的信息。
@system/core/init/init.h
struct service { /* list of all services */ struct listnode slist;//双向链表 const char *name;//service的名字 const char *classname;//service所属class的名字,默认是“default” unsigned flags;//service的属性 pid_t pid;//进程号 time_t time_started; /* time of last start 上一次启动的时间*/ time_t time_crashed; /* first crash within inspection window 第一次死亡的时间*/ int nr_crashed; /* number of times crashed within window 死亡次数*/ uid_t uid; gid_t gid; gid_t supp_gids[NR_SVC_SUPP_GIDS]; size_t nr_supp_gids; char *seclabel; struct socketinfo *sockets;//有些service需要使用socket,socketinfo用来描述socket相关信息 struct svcenvinfo *envvars;//service一般运行在一个单独的进程中,envvars用来描述创建这个进程时所需的环境变量信息 //关键字onrestart标示一个OPTION,可是onrestart后面一般跟着COMMAND,下面这个action结构体可用来存储command信息 struct action onrestart; /* Actions to execute on restart. */ /* keycodes for triggering this service via /dev/keychord */ int *keycodes; int nkeycodes; int keychord_id; int ioprio_class; int ioprio_pri; int nargs;//参数个数 /* "MUST BE AT THE END OF THE STRUCT" */ char *args[1];//用于存储参数 }; /* ^-------‘args‘ MUST be at the end of this struct! */
从parse_service函数可以看出,它的作用就是讲service添加到service_list列表中,并制定解析函数为parse_line_service,也就是说具体的service的解析靠的是parse_line_service方法。
static void parse_line_service(struct parse_state *state, int nargs, char **args) { struct service *svc = state->context; struct command *cmd; int i, kw, kw_nargs; if (nargs == 0) { return; } svc->ioprio_class = IoSchedClass_NONE; kw = lookup_keyword(args[0]); switch (kw) { case K_capability: break; case K_class: if (nargs != 2) { parse_error(state, "class option requires a classname\n"); } else { svc->classname = args[1]; } break; case K_console: svc->flags |= SVC_CONSOLE; break; case K_disabled: svc->flags |= SVC_DISABLED; svc->flags |= SVC_RC_DISABLED; break; case K_ioprio: if (nargs != 3) { parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n"); } else { svc->ioprio_pri = strtoul(args[2], 0, 8); if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) { parse_error(state, "priority value must be range 0 - 7\n"); break; } if (!strcmp(args[1], "rt")) { svc->ioprio_class = IoSchedClass_RT; } else if (!strcmp(args[1], "be")) { svc->ioprio_class = IoSchedClass_BE; } else if (!strcmp(args[1], "idle")) { svc->ioprio_class = IoSchedClass_IDLE; } else { parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n"); } } break; case K_group: if (nargs < 2) { parse_error(state, "group option requires a group id\n"); } else if (nargs > NR_SVC_SUPP_GIDS + 2) { parse_error(state, "group option accepts at most %d supp. groups\n", NR_SVC_SUPP_GIDS); } else { int n; svc->gid = decode_uid(args[1]); for (n = 2; n < nargs; n++) { svc->supp_gids[n-2] = decode_uid(args[n]); } svc->nr_supp_gids = n - 2; } break; case K_keycodes: if (nargs < 2) { parse_error(state, "keycodes option requires atleast one keycode\n"); } else { svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0])); if (!svc->keycodes) { parse_error(state, "could not allocate keycodes\n"); } else { svc->nkeycodes = nargs - 1; for (i = 1; i < nargs; i++) { svc->keycodes[i - 1] = atoi(args[i]); } } } break; case K_oneshot: svc->flags |= SVC_ONESHOT; break; case K_onrestart: nargs--; args++; kw = lookup_keyword(args[0]); if (!kw_is(kw, COMMAND)) { parse_error(state, "invalid command ‘%s‘\n", args[0]); break; } kw_nargs = kw_nargs(kw); if (nargs < kw_nargs) { parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1, kw_nargs > 2 ? "arguments" : "argument"); break; } cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); cmd->func = kw_func(kw); cmd->nargs = nargs; memcpy(cmd->args, args, sizeof(char*) * nargs); list_add_tail(&svc->onrestart.commands, &cmd->clist); break; case K_critical: svc->flags |= SVC_CRITICAL; break; case K_setenv: { /* name value */ struct svcenvinfo *ei; if (nargs < 2) { parse_error(state, "setenv option requires name and value arguments\n"); break; } ei = calloc(1, sizeof(*ei)); if (!ei) { parse_error(state, "out of memory\n"); break; } ei->name = args[1]; ei->value = args[2]; ei->next = svc->envvars; svc->envvars = ei; break; } case K_socket: {/* name type perm [ uid gid ] */ struct socketinfo *si; if (nargs < 4) { parse_error(state, "socket option requires name, type, perm arguments\n"); break; } if (strcmp(args[2],"dgram") && strcmp(args[2],"stream") && strcmp(args[2],"seqpacket")) { parse_error(state, "socket type must be ‘dgram‘, ‘stream‘ or ‘seqpacket‘\n"); break; } si = calloc(1, sizeof(*si)); if (!si) { parse_error(state, "out of memory\n"); break; } si->name = args[1]; si->type = args[2]; si->perm = strtoul(args[3], 0, 8); if (nargs > 4) si->uid = decode_uid(args[4]); if (nargs > 5) si->gid = decode_uid(args[5]); si->next = svc->sockets; svc->sockets = si; break; } case K_user: if (nargs != 2) { parse_error(state, "user option requires a user id\n"); } else { svc->uid = decode_uid(args[1]); } break; case K_seclabel: if (nargs != 2) { parse_error(state, "seclabel option requires a label string\n"); } else { svc->seclabel = args[1]; } break; default: parse_error(state, "invalid option ‘%s‘\n", args[0]); } }
可以看出parse_line_service中会根据keyword找的对应的keyword的处理函数,具体进程处理。
section的处理与service类似,通过分析init.rc的解析过程,我们知道,所谓的解析就是将rc脚本中的内容通过解析,填充到service_list和action_list中去。那他们是在哪里进行调用的呢,让我们回忆一下init进程中main函数的实现。
INFO("reading config file\n"); init_parse_config_file("/init.rc");//解析init.rc配置文件 action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); /* execute all the boot actions to get us started */ action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ if (!is_charger) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", action_add_queue_tail); }
init.rc脚本的解析就完了。
我转载,我又来插嘴:
解析init.rc做了什么?
原文:http://my.oschina.net/wizardmerlin/blog/511926