基于Linux内核的android系统,在内核启动完成后将创建一个Init用户进程,实现了内核空间到用户空间的转变。在Android 启动过程介绍一文中介绍了Android系统的各个启动阶段,init进程启动后会读取init.rc配置文件,通过fork系统调用启动init.rc文件中配置的各个Service进程。init进程首先启动启动android的服务大管家ServiceManager服务,然后启动Zygote进程。Zygote进程的启动开创了Java世界,无论是SystemServer进程还是android的应用进程都是Zygote的子进程,Zygote进程启动过程的源代码分析一文中详细介绍了Zygote进程的启动过程,System Server进程启动过程源码分析则详细介绍了在Zygote进程启动完成后创建的第一个进程SystemServer进程的启动过程,SystemServer进程的启动包括两个阶段,在第一阶段主要是启动C++相关的本地服务,如SurfaceFlinger等,在第二阶段通过在ServerThread线程中启动android的各大关键Java服务。Zygote孵化应用进程过程的源码分析一文中详细介绍了Zygote进程创建android应用进程的过程,当用户点击Luncher上的应用图标时,Luncher进程通过socket向Zygote进程发送进程创建请求,Zygote进程接受客户端的请求后,通过fork系统调用为应用程序创建相应的进程。本文则介绍android用户进程的始祖Init进程,Init进程是Linux系统中用户空间的第一个进程,负责创建系统中的关键进程,同时提供属性服务来管理系统属性。
Android进程模型
Linux通过调用start_kernel函数来启动内核,当内核启动模块启动完成后,将启动用户空间的第一个进程——Init进程,下图为Android系统的进程模型图:
从上图可以看出,Linux内核在启动过程中,创建一个名为Kthreadd的内核进程,PID=2,用于创建内核空间的其他进程;同时创建第一个用户空间Init进程,该进程PID = 1,用于启动一些本地进程,比如Zygote进程,而Zygote进程也是一个专门用于孵化Java进程的本地进程,上图清晰地描述了整个Android系统的进程模型,为了证明以上进程模型的正确性,可以通过ps命令来查看进程的PID级PPID,下图显示了Init进程的PID为1,其他的本地进程的PPID都是1,说明它们的父进程都是Init进程,都是由Init进程启动的。
下图显示kthreadd进程的PID=2,有一部分内核进程如binder、dhd_watchdog等进程的PPID=2,说明这些进程都是由kthreadd进程创建:
上图中显示zygote进程PID=107,下图显示了zygote进程创建的子进程,从图中可以看到,zygote进程创建的都是Java进程,证明了zygote进程开创了Android系统的Java世界。
上面介绍了Android系统的进程模型设计,接下来将详细分析Init进程。
Init进程源码分析
上节介绍了Init进程在Linux内核启动时被创建的,那它是如何启动的呢?
Init进程启动分析
在Linux内核启动过程中,将调用Start_kernel来初始化配置:
asmlinkage void __init start_kernel( void )
{
..............
rest_init();
}
start_kernel函数调用一些初始化函数完成初始化工作后,调用rest_init()函数来创建新的进程:
static noinline void __init_refok rest_init( void )
__releases(kernel_lock)
{
int pid;
rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
unlock_kernel();
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
preempt_disable();
cpu_idle();
}
在rest_init函数里完成两个新进程的创建:Init进程和kthreadd进程,因为Init进程创建在先,所以其PID=1而kthreadd的PID=2,本文只对Init进程进行详细分析,如果读者对kthreadd进行感兴趣,可自行分析。
kernel_thread函数仅仅调用了fork系统调用来创建新的进程,创建的子进程和父进程都执行在fork函数调用之后的代码,子进程是父进程的一个拷贝。
static int __init kernel_init( void * unused)
{
wait_for_completion(&kthreadd_done);
set_mems_allowed(node_states[N_HIGH_MEMORY]);
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();
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);
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();
}
init_post();
return 0;
}
在kernel_init函数中调用__initcall_start到__initcall_end之间保存的函数进行驱动模块初始化,然后直接调用init_post()函数进入用户空间,执行Init 进程代码。
static noinline int init_post( void )
{
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);
}
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." );
}
当根文件系统顶层目录中不存在init进程,或未指定启动选项"init="时,内核会到/sbin、/etc、/bin目录下查找init文件。如果在这些目录中仍未找到init文件,内核就会中止执行init进程,并引发Kernel Panic。run_init_process函数通过系统调用do_execve从内核空间跳转到用户空间,并且执行用户空间的Init程序的入口函数。
static void run_init_process( const char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init);
}
这里就介绍完了内核启动流程,run_init_process函数的将执行Init程序的入口函数,Init的入口函数位于/system/core/init/init.c
Init进程源码分析
Android的init进程主要功能: 1)、分析init.rc启动脚本文件,根据文件内容执行相应的功能; 2)、当一些关键进程死亡时,重启该进程; 3)、提供Android系统的属性服务;
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 ;
if (!strcmp(basename(argv[0]), "ueventd" ))
return ueventd_main(argc, argv);
umask(0);
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);
close(open("/dev/.booting" , O_WRONLY | O_CREAT, 0000));
open_devnull_stdio();
klog_init();
property_init();
get_hardware_name(hardware, &revision);
process_kernel_cmdline();
#ifdef HAVE_SELINUX
INFO("loading selinux policy\n" );
selinux_load_policy();
#endif
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" );
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(keychord_init_action, "keychord_init" );
queue_builtin_action(console_init_action, "console_init" );
action_for_each_trigger("init" , action_add_queue_tail);
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);
if (!is_charger) {
action_for_each_trigger("post-fs-data" , action_add_queue_tail);
}
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 (!strcmp(bootmode, "alarm" )) {
action_for_each_trigger("alarm" , action_add_queue_tail);
}
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);
}
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers" );
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init" );
#endif
for (;;) {
int nr, i, timeout = -1;
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())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
文件系统简介
tmpfs文件系统
tmpfs是一种虚拟内存文件系统,因此它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有内容均为临时性的内容,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs是一个独立的文件系统,不是块设备,只要挂接,立即就可以使用。
devpts文件系统
devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。
proc文件系统
proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。
sysfs文件系统
与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。
屏蔽标准的输入输出
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);
}
将标准输入输出,错误输出重定向到/dev/_null_设备中
初始化内核log系统
void klog_init( void )
{
static const char *name = "/dev/__kmsg__" ;
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
klog_fd = open(name, O_WRONLY);
fcntl(klog_fd, F_SETFD, FD_CLOEXEC);
unlink(name);
}
}
属性存储空间初始化
void property_init( void )
{
init_property_area();
}
关于Android的属性系统,请查看 Android 系统属性SystemProperty分析一文,在这篇文章中详细分析了Android的属性系统。
读取机器硬件名称
从/proc/cpuinfo中获取“Hardware”字段信息写入;“Reversion” 字段信息写入
void get_hardware_name( char *hardware, unsigned int *revision)
{
char data[1024];
int fd, n;
char *x, *hw, *rev;
if (hardware[0])
return ;
fd = open("/proc/cpuinfo" , O_RDONLY);
if (fd < 0) return ;
n = read(fd, data, 1023);
close(fd);
if (n < 0) return ;
data[n] = 0;
hw = strstr(data, "\nHardware" );
rev = strstr(data, "\nRevision" );
if (hw) {
x = strstr(hw, ": " );
if (x) {
x += 2;
n = 0;
while (*x && *x != '\n' ) {
if (!isspace(*x))
hardware[n++] = tolower(*x);
x++;
if (n == 31) break ;
}
hardware[n] = 0;
}
}
if (rev) {
x = strstr(rev, ": " );
if (x) {
*revision = strtoul(x + 2, 0, 16);
}
}
}
get_hardware_name函数从/proc/cpuinfo文件中读取硬件名称等信息,/proc/cpuinfo文件内容如下:
Processor : ARMv7 Processor rev 1 (v7l)
BogoMIPS : 1024.00
Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc05
CPU revision : 1
Hardware : sc7710g
Revision : 0000
Serial : 0000000000000000
设置命令行参数属性
static void process_kernel_cmdline( void )
{
chmod("/proc/cmdline" , 0440);
import_kernel_cmdline(0, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(1, import_kernel_nv);
export_kernel_boot_props();
}
process_kernel_cmdline函数首先修改/proc/cmdline文件权限,然后调用import_kernel_cmdline函数来读取/proc/cmdline文件的内容,并查找格式为:
= 的字串,调用import_kernel_nv函数来设置属性。函数export_kernel_boot_props()用于设置内核启动时需要的属性。
void import_kernel_cmdline( int in_qemu, void (*import_kernel_nv)( char *name, int in_qemu))
{
char cmdline[1024];
char *ptr;
int fd;
fd = open("/proc/cmdline" , O_RDONLY);
if (fd >= 0) {
int n = read(fd, cmdline, 1023);
if (n < 0) n = 0;
if (n > 0 && cmdline[n-1] == '\n' ) n--;
cmdline[n] = 0;
close(fd);
} else {
cmdline[0] = 0;
}
ptr = cmdline;
while (ptr && *ptr) {
char *x = strchr(ptr, ' ' );
if (x != 0) *x++ = 0;
import_kernel_nv(ptr, in_qemu);
ptr = x;
}
}
/proc/cmdline文件内容如下:
initrd=0x4c00000,0x1118e8 lpj=3350528 apv="sp7710ga-userdebug 4.1.2 JZO54K W13.23.2-010544 test-keys" mem=256M init=/init mtdparts=sprd-nand:256k(spl),512k(2ndbl),256k(params),512k(vmjaluna),10m(modem),3840k(fixnv),3840k(backupfixnv),5120k(dsp),3840k(runtimenv),10m(boot),10m(recovery),260m(system),160m(userdata),20m(cache),256k(misc),1m(boot_logo),1m(fastboot_logo),3840k(productinfo),512k(kpanic),15m(firmware) console=null lcd_id=ID18 ram=256M
static void import_kernel_nv( char *name, int for_emulator)
{
char *value = strchr(name, '=' );
int name_len = strlen(name);
if (value == 0) return ;
*value++ = 0;
if (name_len == 0) return ;
#ifdef HAVE_SELINUX
if (!strcmp(name, "enforcing" )) {
selinux_enforcing = atoi(value);
} else if (!strcmp(name, "selinux" )) {
selinux_enabled = atoi(value);
}
#endif
if (for_emulator) {
char buff[PROP_NAME_MAX];
int len = snprintf( buff, sizeof (buff), "ro.kernel.%s" , name );
if (len < ( int ) sizeof (buff))
property_set( buff, value );
return ;
}
if (!strcmp(name, "qemu" )) {
strlcpy(qemu, value, sizeof (qemu));
} else if (!strncmp(name, "androidboot." , 12) && name_len > 12) {
const char *boot_prop_name = name + 12;
char prop[PROP_NAME_MAX];
int cnt;
cnt = snprintf(prop, sizeof (prop), "ro.boot.%s" , boot_prop_name);
if (cnt < PROP_NAME_MAX)
property_set(prop, value);
}
}
最后调用函数export_kernel_boot_props设置内核启动属性
static void export_kernel_boot_props( void )
{
char tmp[PROP_VALUE_MAX];
const char *pval;
unsigned i;
struct {
const char *src_prop;
const char *dest_prop;
const char *def_val;
} prop_map[] = {
{ "ro.boot.serialno" , "ro.serialno" , "" , },
{ "ro.boot.mode" , "ro.bootmode" , "unknown" , },
{ "ro.boot.baseband" , "ro.baseband" , "unknown" , },
{ "ro.boot.bootloader" , "ro.bootloader" , "unknown" , },
};
for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
pval = property_get(prop_map[i].src_prop);
property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);
}
pval = property_get("ro.boot.console" );
if (pval)
strlcpy(console, pval, sizeof (console));
strlcpy(bootmode, property_get("ro.bootmode" ), sizeof (bootmode));
pval = property_get("ro.boot.hardware" );
if (pval)
strlcpy(hardware, pval, sizeof (hardware));
property_set("ro.hardware" , hardware);
snprintf(tmp, PROP_VALUE_MAX, "%d" , revision);
property_set("ro.revision" , tmp);
if (!strcmp(bootmode, "factory" ))
property_set("ro.factorytest" , "1" );
else if (!strcmp(bootmode, "factory2" ))
property_set("ro.factorytest" , "2" );
else
property_set("ro.factorytest" , "0" );
}
init.rc 文件解析
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函数将init.rc文件的内容读取保存到data中,在调用parse_config对其进行解析
void *read_file( const char *fn, unsigned *_sz)
{
char *data;
int sz;
int fd;
struct stat sb;
data = 0;
fd = open(fn, O_RDONLY);
if (fd < 0) return 0;
if (fstat(fd, &sb) < 0) {
ERROR("fstat failed for '%s'\n" , fn);
goto oops;
}
if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
ERROR("skipping insecure file '%s'\n" , fn);
goto oops;
}
sz = lseek(fd, 0, SEEK_END);
if (sz < 0) goto oops;
if (lseek(fd, 0, SEEK_SET) != 0) goto oops;
data = (char *) malloc(sz + 2);
if (data == 0) goto oops;
if (read(fd, data, sz) != sz) goto oops;
close(fd);
data[sz] = '\n' ;
data[sz+1] = 0;
if (_sz) *_sz = sz;
return data;
oops:
close(fd);
if (data != 0) free(data);
return 0;
}
init.rc文件语法介绍
在Android根文件系统下存在多个.rc文件,该文件为Android启动配置脚本文件,文件内容如下:
init.rc是一个可配置的初始化文件,通常定制厂商可以配置额外的初始化配置,如果关键字中有空格,处理方法类似于C语言,使用/表示转义,使用“”防止关键字被断开,另外注意/在末尾表示换行,由 # (前面允许有空格)开始的行都是注释行。init.rc包含4种状态类别:Actions/Commands/Services/Options。当声明一个service或者action的时候,它将隐式声明一个section,它之后跟随的command或者option都将属于这个section,action和service不能重名,否则忽略为error。
Action
actions就是在某种条件下触发一系列的命令,通常有一个trigger,形式如: on
trigger主要包括:
boot 当/init.conf加载完毕时 = 当被设置为时 device-added- 设备被添加时 device-removed- 设备被移除时 service-exited- 服务退出时
Service
service就是要启动的本地服务进程
service [ ]*
Option
option是service的修饰词,由它来指定何时并且如何启动Services程序,主要包括: critical 表示如果服务在4分钟内存在多于4次,则系统重启到recovery mode disabled 表示服务不会自动启动,需要手动调用名字启动 setEnv 设置启动环境变量 socket [ []] 开启一个unix域的socket,名字为/dev/socket/ , 只能是dgram或者stream,和默认为0 user 表示将用户切换为,用户名已经定义好了,只能是system/root group 表示将组切换为 oneshot 表示这个service只启动一次 class 指定一个要启动的类,这个类中如果有多个service,将会被同时启动。默认的class将会是“default” onrestart 在重启时执行一条命令
Command
comand主要包括:
exec [ ]*执行一个指定的程序 export 设置一个全局变量 ifup 使网络接口连接 import 引入其他的配置文件 hostname 设置主机名 chdir 切换工作目录 chmod 设置访问权限 chown 设置用户和组 chroot 设置根目录 class_start 启动类中的service class_stop 停止类中的service domainname 设置域名 insmod 安装模块 mkdir [mode] [owner] [group] 创建一个目录,并可以指定权限,用户和组 mount [ ]* 加载指定设备到目录下 包括"ro", "rw", "remount", "noatime" setprop 设置系统属性 setrlimit 设置资源访问权限 start 开启服务 stop 停止服务 symlink 创建一个动态链接 sysclktz 设置系统时钟 trigger 触发事件 write [ ]* 向路径的文件写入多个
Properties(属性)
Init更新一些系统属性以提供对正在发生的事件的监控能力: init.action 此属性值为正在被执行的action的名字,如果没有则为""。 init.command 此属性值为正在被执行的command的名字,如果没有则为""。 init.svc. 名为的service的状态("stopped"(停止), "running"(运行), "restarting"(重启))
在默认情况下,程序在被init执行时会将标准输出和标准错误都重定向到/dev/null(丢弃)。若你想要获得调试信息,你可以通过Andoird系统中的logwrapper程序执行你的程序。它会将标准输出/标准错误都重定向到Android日志系统(通过logcat访问)。 例如: service akmd /system/bin/logwrapper /sbin/akmd
init.rc解析过程
1. 扫描init.rc中的token 找到其中的 文件结束EOF/文本TEXT/新行NEWLINE,其中的空格‘ ’、‘\t’、‘\r’会被忽略,#开头的行也被忽略掉;而对于TEXT,空格‘ ’、‘\t’、‘\r’、‘\n’都是TEXT的结束标志。 2. 对每一个TEXT token,都加入到args[]数组中 3. 当遇到新一行(‘\n’)的时候,用args[0]通过lookup_keyword()检索匹配关键字;
1) 对Section(on和service),调用parse_new_section() 解析: - 对on section,调用parse_action(),并设置解析函数parse_line为parse_line_action() - 对service section,调用parse_service(),并设置解析函数parse_line为parse_line_service() 2) 对其他关键字的行(非on或service开头的地方,也就是没有切换section)调用parse_line() - 对于on section内的命令行,调用parse_line_action()解析; - 对于service section内的命令行,调用parse_line_service()解析。
Token的定义
#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2
解析过程中的双向循环链表的使用,android用到了一个非常巧妙的链表实现方法,一般情况下如果链表的节点是一个单独的数据结构的话,那么针对不同的数据结构,都需要定义不同链表操作。而在初始化过程中使用到的链表则解决了这个问题,它将链表的节点定义为了一个非常精简的结构,只包含前向和后向指针,那么在定义不同的数据结构时,只需要将链表节点嵌入到数据结构中即可。链表节点定义如下:
struct listnode
{
struct listnode *next;
struct listnode *prev;
};
对于Action数据结构为例:
struct action {
struct listnode alist;
struct listnode qlist;
struct listnode tlist;
unsigned hash;
const char *name;
struct listnode commands;
struct command *current;
};
这样的话,所有的链表的基本操作,例如插入,删除等只会针对listnode进行操作,而不是针对特定的数据结构,链表的实现得到了统一,即精简了代码,又提高了效率。 但是这样的链表实现,存在一个问题,链表节点listnode中只有前向和后向指针,并且前向和后向指针均指向listnode,那么我们通过什么方式来访问数据结构action的内容呢?我们使用offsetof宏来计算链表节点在数据结构中的偏移量,从而计算数据结构实例的地址。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define node_to_item(node, container, member) \
(container *) (((char *) (node)) - offsetof(container, member))
这种链表的优点:(1)所有链表基本操作都是基于listnode指针的,因此添加类型时,不需要重复写链表基本操作函数(2)一个container数据结构可以含有多个listnode成员,这样就可以同时挂到多个不同的链表中。
Service数据结构定义:
struct service {
struct listnode slist;
const char *name;
const char *classname;
unsigned flags;
pid_t pid;
time_t time_started;
time_t time_crashed;
int nr_crashed;
uid_t uid;
gid_t gid;
gid_t supp_gids[NR_SVC_SUPP_GIDS];
size_t nr_supp_gids;
#ifdef HAVE_SELINUX
char *seclabel;
#endif
struct socketinfo *sockets;
struct svcenvinfo *envvars;
struct action onrestart;
int *keycodes;
int nkeycodes;
int keychord_id;
int ioprio_class;
int ioprio_pri;
int nargs;
char *args[1];
};
对于某些Service可能采用Socket来实现进程间通信,因此该Service需要创建多个socket,比如:
service wril-daemon /system/bin/rild_sp -l /system/lib/libreference-ril_sp.so -m w -n 0
class core
socket rild stream 660 root radio
socket rild-debug stream 660 radio system
disabled
user root
group radio cache inet misc audio sdcard_rw log
该service需要创建rild 和rild-debug socket,这些socket的信息在解析init.rc文件时保存在Service的成员变量sockets链表中。socketinfo 数据结构定义如下:
struct socketinfo {
struct socketinfo *next;
const char *name;
const char *type;
uid_t uid;
gid_t gid;
int perm;
};
某些Service在运行时需要设置环境变量,这些环境变量被保存在Service的成员变量envvars链表中,svcenvinfo 数据结构定义如下:
struct svcenvinfo {
struct svcenvinfo *next;
const char *name;
const char *value;
};
在每个Action或Service下可能需要执行多个Command,关于command数据结构定义如下:
struct command
{
struct listnode clist;
int (*func)( int nargs, char **args);
int nargs;
char *args[1];
};
在Init进程中分别使用了3个链表来存储init.rc文件中的Action和Service:
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
service_list链表用于保存init.rc文件中的Service配置信息,service_list链表的存储如下图所示:
service_list 链表保存init.rc文件中的所有service,每个service下的所有socket信息保存在该service的成员变量sockets链表中,当该service重启时,需要重启某些服务,对于重启某些服务的命令以Action的形式保存在Service的成员变量onrestart链表中,而真正执行的命令却存放在该Action下的commands链表里。
action_list用于保存init.rc文件中的所有以on开头的section,action_list链表的存储如下图所示:
从上图可以看出action_queue和action_list都是用来保存所有的Action,它们之间的区别是action_list用于保存从init.rc中解析出来的所有Action,而action_queue却是用于保存待执行的Action,action_queue是一个待执行队列。
在system\core\init\keywords.h文件中定义了解析关键字,其内容如下:
#ifndef KEYWORD
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_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_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_pipe( int nargs, char **args);
int do_wait( int nargs, char **args);
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
K_UNKNOWN,
#endif
KEYWORD(capability, OPTION, 0, 0)
KEYWORD(chdir, COMMAND, 1, do_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(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, 1, do_setsebool)
KEYWORD(socket, OPTION, 0, 0)
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
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(pipe, COMMAND, 2, do_pipe)
KEYWORD(ioprio, OPTION, 0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif
宏KEYWORD并未定义,因此将定义宏__MAKE_KEYWORD_ENUM__ 及KEYWORD,KEYWORD宏定义如下:
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
同时定义了枚举:
enum {
K_UNKNOWN,
KEYWORD(capability, OPTION, 0, 0)
KEYWORD(chdir, COMMAND, 1, do_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(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, 1, do_setsebool)
KEYWORD(socket, OPTION, 0, 0)
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
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(pipe, COMMAND, 2, do_pipe)
KEYWORD(ioprio, OPTION, 0, 0)
KEYWORD_COUNT,
};
该枚举的通过宏展开后定义为:
enum {
K_UNKNOWN,
K_capability,
K_chdir,
K_chroot,
K_class,
K_class_start,
K_class_stop,
K_class_reset,
K_console,
K_critical,
K_disabled,
K_domainname,
K_exec,
K_export,
K_group,
K_hostname,
K_ifup,
K_insmod,
K_import,
K_keycodes,
K_mkdir,
K_mount_all,
K_mount,
K_on,
K_oneshot,
K_onrestart,
K_restart,
K_restorecon,
K_rm,
K_rmdir
K_seclabel
K_service
K_setcon
K_setenforce
K_setenv
K_setkey
K_setprop
K_setrlimit
K_setsebool
K_socket
K_start
K_stop
K_trigger
K_symlink
K_sysclktz
K_user
K_wait
K_write
K_copy
K_chown
K_chmod
K_loglevel
K_load_persist_props
K_pipe
K_ioprio
KEYWORD_COUNT,
};
该枚举的定义主要是为每个命令指定对应的序号。在keywords.h文件最后取消了宏__MAKE_KEYWORD_ENUM__ 及KEYWORD的定义,在system\core\init\init_parser.c文件中又重定义了KEYWORD宏:
#define KEYWORD(symbol, flags, nargs, func) \
[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
该宏的定义是为了给接下来定义的keyword_info这个关键字信息数组的赋值,keyword_info定义如下:
struct {
const char *name;
int (*func)( int nargs, char **args);
unsigned char nargs;
unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown" , 0, 0, 0 },
#include "keywords.h"
};
keyword_info数组元素是keywords.h文件中的内容,因为此时KEYWORD宏已经被定义了同时__MAKE_KEYWORD_ENUM__被取消定义,因此keywords.h文件内容此时变为:
KEYWORD(capability, OPTION, 0, 0)
KEYWORD(chdir, COMMAND, 1, do_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(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, 1, do_setsebool)
KEYWORD(socket, OPTION, 0, 0)
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
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(pipe, COMMAND, 2, do_pipe)
KEYWORD(ioprio, OPTION, 0, 0)
使用上述KEYWORD宏展开得到keyword_info数组内容如下:
[ K_capability ] = { capability, 0, 1, OPTION, },
[ K_class ] = { class , 0, 1, OPTION, },
[ K_console ] = { console, 0, 1, OPTION, },
[ K_critical ] = { critical, 0, 1, OPTION, },
[ K_group ] = { group, 0, 1, OPTION, },
[ K_disabled ] = { disabled, 0, 1, OPTION, },
[ K_keycodes ] = { keycodes, 0, 1, OPTION, },
[ K_oneshot ] = { oneshot, 0, 1, OPTION, },
[ K_onrestart ] = { onrestart, 0, 1, OPTION, },
[ K_socket ] = { socket, 0, 1, OPTION, },
[ K_setenv ] = { setenv, 0, 3, OPTION, },
[ K_ioprio ] = { ioprio, 0, 1, OPTION, },
[ K_user ] = { user, 0, 1, OPTION, },
[ K_seclabel ] = { seclabel, 0, 1, OPTION, },
[ K_service ] = { service, 0, 1, SECTION, },
[ K_on ] = { on, 0, 1, SECTION, },
[ K_import ] = { import, 0, 2, SECTION, },
[ K_chdir ] = { chdir, do_chdir, 2, COMMAND, },
[ K_chroot ] = { chroot, do_chroot, 2, COMMAND, },
[ K_class_start ] = { class_start, do_class_start, 2, COMMAND, },
[ K_class_stop ] = { class_stop, do_class_stop, 2, COMMAND, },
[ K_class_reset ] = { class_reset, do_class_reset, 2, COMMAND, },
[ K_domainname ] = { domainname, do_domainname, 2, COMMAND, },
[ K_exec ] = { exec, do_exec, 2, COMMAND, },
[ K_export ] = { export, do_export, 3, COMMAND, },
[ K_hostname ] = { hostname, do_hostname, 2, COMMAND, },
[ K_ifup ] = { ifup, do_ifup, 2, COMMAND, },
[ K_insmod ] = { insmod, do_insmod, 3, COMMAND, },
[ K_mkdir ] = { mkdir, do_mkdir, 2, COMMAND, },
[ K_mount_all ] = { mount_all, do_mount_all, 2, COMMAND, },
[ K_mount ] = { mount, do_mount, 4, COMMAND, },
[ K_restart ] = { restart, do_restart, 2, COMMAND, },
[ K_restorecon ] = { restorecon, do_restorecon, 2, COMMAND, },
[ K_rm ] = { rm, do_rm, 2, COMMAND, }
[ K_rmdir ] = { rmdir, do_rmdir, 2, COMMAND, },
[ K_setcon ] = { setcon, do_setcon, 2, COMMAND, },
[ K_setenforce ] = { setenforce, do_setenforce, 2, COMMAND, },
[ K_setkey ] = { setkey, do_setkey, 1, COMMAND, },
[ K_setprop ] = { setprop, do_setprop, 3, COMMAND, },
[ K_setrlimit ] = { setrlimit, do_setrlimit, 4, COMMAND, },
[ K_setsebool ] = { setsebool, do_setsebool, 2, COMMAND, },
[ K_start ] = { start, do_start, 2, COMMAND, },
[ K_stop ] = { stop, do_stop, 2, COMMAND, },
[ K_trigger ] = { trigger, do_trigger, 2, COMMAND, },
[ K_symlink ] = { symlink, do_symlink, 2, COMMAND, },
[ K_sysclktz ] = { sysclktz, do_sysclktz, 2, COMMAND, },
[ K_wait ] = { wait, do_wait, 2, COMMAND, },
[ K_write ] = { write, do_write, 3, COMMAND, },
[ K_copy ] = { copy, do_copy, 3, COMMAND, },
[ K_chown ] = { chown, do_chown, 3, COMMAND, },
[ K_chmod ] = { chmod, do_chmod, 3, COMMAND, },
[ K_loglevel ] = { loglevel, do_loglevel, 2, COMMAND, },
[ K_load_persist_props] = { load_persist_props, do_load_persist_props,1, COMMAND, },
[ K_pipe ] = { pipe, do_pipe, 3, COMMAND, },
了解了这些内容之后,我们开始分析init.rc文件的真正解析过程:
static void parse_config( const char *fn, char *s)
{
struct parse_state state;
struct listnode import_list;
struct listnode *node;
char *args[INIT_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.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:
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE:
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]);
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:
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通过调用next_token函数来查找3个定义的token,当查找到T_NEWLINE token时,使用lookup_keyword函数来判断关键字类型,如果属于SECTION类型,则调用parse_new_section函数进行解析,如果是其他类型,则调用parse_line指向的回调函数来解析。
在前面介绍了通过定义枚举来为每个命令分配类型,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 '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;