1. 程式人生 > >Android Init程序原始碼分析

Android Init程序原始碼分析

Init 程序原始碼分析

基於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_init程序,該程序實質上是Init程序,用於啟動使用者空間程序
	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 
	numa_default_policy();
	//建立一個kthreadd核心執行緒,用於建立新的核心程序
	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();

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);
	preempt_enable_no_resched();
	schedule(); 
	preempt_disable();

	/* Call into cpu_idle with preempt disabled */
	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 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);
    //執行儲存在__initcall_start與__early_initcall_end之間的函式
	do_pre_smp_initcalls();
	lockup_detector_init();
    //smp 多核初始化處理
	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;
}
在kernel_init函式中呼叫__initcall_start到__initcall_end之間儲存的函式進行驅動模組初始化,然後直接呼叫init_post()函式進入使用者空間,執行Init 程序程式碼。
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;
    //如果ramdisk_execute_command不為空,ramdisk_execute_command下的Init程式
	if (ramdisk_execute_command) {
		run_init_process(ramdisk_execute_command);
		printk(KERN_WARNING "Failed to execute %s\n",ramdisk_execute_command);
	}
    //如果execute_command不為空,execute_command下的Init程式
	if (execute_command) {
		run_init_process(execute_command);
		printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);
	}
	//如果以上路徑下都沒有init程式,就從/sbin、/etc、/bin三個路徑下尋找init程式,同時啟動一個sh程序
	run_init_process("/sbin/init");
	run_init_process("/etc/init");
	run_init_process("/bin/init");
	run_init_process("/bin/sh");
    //如果以上路徑都沒有找到init程式,呼叫核心panic
	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);

    /* clear the umask */
    umask(0);
    //掛載tmpfs,devpts,proc,sysfs 4類檔案系統
    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));
    //遮蔽標準的輸入輸出,即標準的輸入輸出定向到NULL裝置。
    open_devnull_stdio();
    // log 初始化
    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();
    //解析init.rc檔案
    INFO("reading config file\n");
    init_parse_config_file("/init.rc");
    //將early-init動作新增到連結串列action_queue中 
    action_for_each_trigger("early-init", action_add_queue_tail);
    //建立wait_for_coldboot_done 動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
	//建立keychord_init動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(keychord_init_action, "keychord_init");
	//建立console_init動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(console_init_action, "console_init");
    //將init動作新增到連結串列action_queue中
    action_for_each_trigger("init", action_add_queue_tail);
	//將early-fs動作新增到連結串列action_queue中
    action_for_each_trigger("early-fs", action_add_queue_tail);
	//將fs動作新增到連結串列action_queue中
    action_for_each_trigger("fs", action_add_queue_tail);
	//將post-fs動作新增到連結串列action_queue中
	action_for_each_trigger("post-fs", action_add_queue_tail);
    //非充電模式下,將post-fs-data動作新增到連結串列action_queue中
    if (!is_charger) {
        action_for_each_trigger("post-fs-data", action_add_queue_tail);
    }
    //建立property_service_init動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(property_service_init_action, "property_service_init");
	//建立signal_init動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(signal_init_action, "signal_init");
	//建立check_startup動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(check_startup_action, "check_startup");

    if (!strcmp(bootmode, "alarm")) {
        action_for_each_trigger("alarm", action_add_queue_tail);
    }
	
    if (is_charger) {
		//充電模式下,將charger動作新增到連結串列action_queue中
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
		//非充電模式下,將early-boot、boot動作新增到連結串列action_queue中
        action_for_each_trigger("early-boot", action_add_queue_tail);
        action_for_each_trigger("boot", action_add_queue_tail);
    }
    //建立queue_property_triggers動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");


#if BOOTCHART
    //如果BOOTCHART巨集定義了,建立bootchart_init動作並新增到連結串列action_queue和action_list中
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif

    for(;;) {
        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())
                    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;
	//建立一個字元專用檔案/dev/__null__ 
    static const char *name = "/dev/__null__";
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
        //獲取/dev/__null__的檔案描述符,並輸出該檔案
        fd = open(name, O_RDWR);
        unlink(name);
        if (fd >= 0) {
	    //將與程序相關的標準輸入(0),標準輸出(1),標準錯誤輸出(2),均定向到NULL裝置
            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__";
	//建立/dev/__kmsg__裝置節點
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
        klog_fd = open(name, O_WRONLY);
		//當程序在進行exec系統呼叫時,要確保log_fd是關閉的
        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);
        unlink(name);
    }
}

屬性儲存空間初始化

void property_init(void)
{
    init_property_area();
}
關於Android的屬性系統,請檢視Android 系統屬性SystemProperty分析一文,在這篇文章中詳細分析了Android的屬性系統。

讀取機器硬體名稱

從/proc/cpuinfo中獲取“Hardware”欄位資訊寫入<hw>;“Reversion” 欄位資訊寫入<reversion>

void get_hardware_name(char *hardware, unsigned int *revision)
{
    char data[1024];
    int fd, n;
    char *x, *hw, *rev;
    /* Hardware string was provided on kernel command line */
    if (hardware[0])
        return;
    //開啟/proc/cpuinfo檔案
    fd = open("/proc/cpuinfo", O_RDONLY);
    if (fd < 0) return;
    //讀取/proc/cpuinfo檔案內容
    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)
{
    /* 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();
}
process_kernel_cmdline函式首先修改/proc/cmdline檔案許可權,然後呼叫import_kernel_cmdline函式來讀取/proc/cmdline檔案的內容,並查詢格式為:<key> = <value> 的字串,呼叫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;
    //開啟並讀取/proc/cmdline檔案
    fd = open("/proc/cmdline", O_RDONLY);
    if (fd >= 0) {
        int n = read(fd, cmdline, 1023);
        if (n < 0) n = 0;
        /* get rid of trailing newline, it happens */
        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函式,in_qemu =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) {
        /* in the emulator, export any kernel option with the
         * ro.kernel. prefix */
        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;
    }
    //如果/proc/cmdline檔案中有qemu關鍵字
    if (!strcmp(name,"qemu")) {
        strlcpy(qemu, value, sizeof(qemu));
	//如果/proc/cmdline檔案中有以androidboot.開頭的關鍵字
    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
        const char *boot_prop_name = name + 12;
        char prop[PROP_NAME_MAX];
        int cnt;
        //格式化為ro.boot.xx 屬性
        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", },
    };
    //迴圈讀取ro.boot.xxx屬性值,並設定ro.xxx屬性
    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);
    }
    //讀取ro.boot.console屬性值
    pval = property_get("ro.boot.console");
    if (pval)
        strlcpy(console, pval, sizeof(console));
    //讀取ro.bootmode屬性值
    strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode));
    //讀取ro.boot.hardware屬性值
    pval = property_get("ro.boot.hardware");
    if (pval)
        strlcpy(hardware, pval, sizeof(hardware));
    //設定ro.hardware屬性
    property_set("ro.hardware", hardware);
    //設定ro.revision屬性
    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);
    //設定ro.factorytest屬性
    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;
    //讀取/init.rc檔案內容
    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;
	//開啟/init.rc檔案
    fd = open(fn, O_RDONLY);
    if(fd < 0) return 0;

    // for security reasons, disallow world-writable
    // or group-writable files
    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;
    //分配buffer
    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啟動配置指令碼檔案,檔案內容如下:

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#

import /init.${ro.hardware}.rc
import /init.usb.rc
import /init.trace.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_adj -16
    start ueventd
    mkdir /mnt 0775 root system

on init
    sysclktz 0
    loglevel 3
# setup the global environment
    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 ASEC_MOUNTPOINT /mnt/asec
    export LOOP_MOUNTPOINT /mnt/obb
    export BOOTCLASSPATH /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/framework2.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar

# 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 /runtimenv 0774 system system
    mkdir /backupfixnv 0774 system system
    mkdir /productinfo 0774 system system
    mkdir /fixnv 0774 system system
    mkdir /config 0500 root root

# Create cgroup mount points for process groups
    mkdir /dev/cpuctl
    mount cgroup none /dev/cpuctl cpu
    chown system system /dev/cpuctl
    chown system system /dev/cpuctl/tasks
    chmod 0660 /dev/cpuctl/tasks
    write /dev/cpuctl/cpu.shares 1024
    write /dev/cpuctl/cpu.rt_runtime_us 950000
    write /dev/cpuctl/cpu.rt_period_us 1000000

    mkdir /dev/cpuctl/apps
    chown system system /dev/cpuctl/apps/tasks
    chmod 0666 /dev/cpuctl/apps/tasks
    write /dev/cpuctl/apps/cpu.shares 1024
    write /dev/cpuctl/apps/cpu.rt_runtime_us 800000
    write /dev/cpuctl/apps/cpu.rt_period_us 1000000

on fs
# mount mtd partitions
    # Mount /system rw first to give the filesystem a chance to save a checkpoint

    chmod 0744 /modem_control
    start modem_control

    mount yaffs2 [email protected] /system
    mount yaffs2 [email protected] /system ro remount
    mount yaffs2 [email protected] /data nosuid nodev
    mount yaffs2 [email protected] /cache nosuid nodev

on post-fs
    # once everything is setup, no need to modify /
    mount rootfs rootfs / ro remount

    mount yaffs2 [email protected] /fixnv nosuid nodev no-checkpoint
    chown system system /fixnv
    chmod 0774 /fixnv

    mount yaffs2 [email protected] /runtimenv nosuid nodev no-checkpoint
    chown system system /runtimenv
    chmod 0774 /runtimenv

    # We chown/chmod /cache again so because mount is run as root + defaults
    chown system cache /cache
    chmod 0770 /cache

    mount yaffs2 [email protected] /backupfixnv nosuid nodev no-checkpoint
    chown system system /backupfixnv
    chmod 0774 /backupfixnv

    mount yaffs2 [email protected] /productinfo nosuid nodev no-checkpoint
    chown system system /productinfo
    chmod 0774 /productinfo

    chmod 0660 /fixnv/fixnv.bin
    chmod 0660 /backupfixnv/fixnv.bin
    chmod 0660 /productinfo/productinfo.bin
    chmod 0660 /productinfo/productinfobkup.bin
    chown system system /fixnv/fixnv.bin
    chown system system /backupfixnv/fixnv.bin
    chown system system /productinfo/productinfo.bin
    chown system system /productinfo/productinfobkup.bin

    # This may have been created by the recovery system with odd permissions
    chown system cache /cache/recovery
    chmod 0770 /cache/recovery

    #change permissions on vmallocinfo so we can grab it from bugreports
    chown root log /proc/vmallocinfo
    chmod 0440 /proc/vmallocinfo

    #change permissions on kmsg & sysrq-trigger so bugreports can grab kthread stacks
    chown root system /proc/kmsg
    chmod 0440 /proc/kmsg
    chown root system /proc/sysrq-trigger
    chmod 0220 /proc/sysrq-trigger

    # create the lost+found directories, so as to enforce our permissions
    mkdir /cache/lost+found 0770 root root

on post-fs-data
    # create basic filesystem structure
    mkdir /data/misc 01771 system misc
    mkdir /data/misc/bluetoothd 0770 bluetooth bluetooth
    mkdir /data/misc/bluetooth 0770 system system
    mkdir /data/misc/keystore 0700 keystore keystore
    mkdir /data/misc/keychain 0771 system system
    mkdir /data/misc/vpn 0770 system vpn
    mkdir /data/misc/systemkeys 0700 system system

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

    # Tweak background writeout
    write /proc/sys/vm/dirty_expire_centisecs 200
    write /proc/sys/vm/dirty_background_ratio  5

    class_start core
    class_start main

on nonencrypted
    class_start late_start

on charger
    class_start core
    class_start charger

on alarm
    insmod /system/lib/modules/ft5306_ts.ko
    class_start core
    start media
    exec /bin/poweroff_alarm

on property:vold.decrypt=trigger_reset_main
    class_reset main

on property:vold.decrypt=trigger_load_persist_props
    load_persist_props

on property:vold.decrypt=trigger_post_fs_data
    trigger post-fs-data

on property:vold.decrypt=trigger_restart_min_framework
    class_start main

on property:vold.decrypt=trigger_restart_framework
    class_start main
    class_start late_start

on property:vold.decrypt=trigger_shutdown_framework
    class_reset late_start
    class_reset main

## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd
    class core
    critical

service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group log

on property:ro.debuggable=1
    start console

# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd
    class core
    disabled

# adbd on at boot in emulator
on property:ro.kernel.qemu=1
    start adbd

# This property trigger has added to imitiate the previous behavior of "adb root".
# The adb gadget driver used to reset the USB bus when the adbd daemon exited,
# and the host side adb relied on this behavior to force it to reconnect with the
# new adbd instance after init relaunches it. So now we force the USB bus to reset
# here when adbd sets the service.adb.root property to 1.  We also restart adbd here
# rather than waiting for init to notice its death and restarting it so the timing
# of USB resetting and adb restarting more closely matches the previous behavior.
on property:service.adb.root=1
    write /sys/class/android_usb/android0/enable 0
    restart adbd
    write /sys/class/android_usb/android0/enable 1

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

service vold /system/bin/vold
    class core
    socket vold stream 0660 root mount
    ioprio be 2

service netd /system/bin/netd
    class main
    socket netd stream 0660 root system
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system

service debuggerd /system/bin/debuggerd
    class main

#service ril-daemon /system/bin/rild
#    class main
#    socket rild stream 660 root radio
#    socket rild-debug stream 660 radio system
#    user root
#    group radio cache inet misc audio sdcard_r sdcard_rw log

service surfaceflinger /system/bin/surfaceflinger
    class main
    user system
    group graphics
    onrestart restart zygote

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

service bootanim /system/bin/bootanimation
    class main
    user graphics
    group graphics
    disabled
    oneshot

service dbus /system/bin/dbus-daemon --system --nofork
    class main
    socket dbus stream 660 bluetooth bluetooth
    user bluetooth
    group bluetooth net_bt_admin

service bluetoothd /system/bin/bluetoothd -n
    class main
    socket bluetooth stream 660 bluetooth bluetooth
    socket dbus_bluetooth stream 660 bluetooth bluetooth
    # init.rc does not yet support applying capabilities, so run as root and
    # let bluetoothd drop uid to bluetooth with the right linux capabilities
    group bluetooth net_bt_admin misc
    disabled

service installd /system/bin/installd
    class main
    socket installd stream 600 system system

service flash_recovery /system/etc/install-recovery.sh
    class main
    oneshot

service racoon /system/bin/racoon
    class main
    socket racoon stream 600 system system
    # IKE uses UDP port 500. Racoon will setuid to vpn after binding the port.
    group vpn net_admin inet
    disabled
    oneshot

service mtpd /system/bin/mtpd
    class main
    socket mtpd stream 600 system system
    user vpn
    group vpn net_admin inet net_raw
    disabled
    oneshot

service keystore /system/bin/keystore /data/misc/keystore
    class main
    user keystore
    group keystore drmrpc
    socket keystore stream 666
init.rc是一個可配置的初始化檔案,通常定製廠商可以配置額外的初始化配置,如果關鍵字中有空格,處理方法類似於C語言,使用/表示轉義,使用“”防止關鍵字被斷開,另外注意/在末尾表示換行,由 # (前面允許有空格)開始的行都是註釋行。init.rc包含4種狀態類別:Actions/Commands/Services/Options。當宣告一個service或者action的時候,它將隱式宣告一個section,它之後跟隨的command或者option都將屬於這個section,action和service不能重名,否則忽略為error。
Action

actions就是在某種條件下觸發一系列的命令,通常有一個trigger,形式如:  

on <trigger>
      <command>
      <command>

trigger主要包括:

boot 當/init.conf載入完畢時
<name>=<value> 當<name>被設定為<value>時
device-added-<path> 裝置<path>被新增時
device-removed-<path> 裝置<path>被移除時
service-exited-<name> 服務<name>退出時

Service

service就是要啟動的本地服務程序

service <name> <pathname> [ <argument> ]*
      <option>
     <option>

Option

option是service的修飾詞,由它來指定何時並且如何啟動Services程式,主要包括:
     critical  表示如果服務在4分鐘記憶體在多於4次,則系統重啟到recovery mode
     disabled   表示服務不會自動啟動,需要手動呼叫名字啟動
     setEnv <name> <value>  設定啟動環境變數
     socket <name> <type> <permission> [<user> [<group>]] 開啟一個unix域的socket,名字為/dev/socket/<name> , <type>只能是dgram或者stream,<user>和<group>預設為0
     user <username> 表示將使用者切換為<username>,使用者名稱已經定義好了,只能是system/root
     group <groupname>  表示將組切換為<groupname>
     oneshot 表示這個service只啟動一次
     class <name> 指定一個要啟動的類,這個類中如果有多個service,將會被同時啟動。預設的class將會是“default”
     onrestart  在重啟時執行一條命令

Command

comand主要包括:

 exec <path> [ <argument> ]*執行一個<path>指定的程式
 export <name> <value> 設定一個全域性變數
 ifup <interface> 使網路介面<interface>連線
 import <filename> 引入其他的配置檔案
 hostname <name> 設定主機名
 chdir <directory> 切換工作目錄
 chmod <octal-mode> <path> 設定訪問許可權
 chown <owner> <group> <path> 設定使用者和組
 chroot <directory> 設定根目錄
 class_start <serviceclass> 啟動類中的service
 class_stop <serviceclass> 停止類中的service
 domainname <name> 設定域名
 insmod <path> 安裝模組
 mkdir <path> [mode] [owner] [group] 建立一個目錄,並可以指定許可權,使用者和組
 mount <type> <device> <dir> [ <mountoption> ]* 載入指定裝置到目錄下<mountoption> 包括"ro", "rw", "remount", "noatime"
 setprop <name> <value> 設定系統屬性
 setrlimit <resource> <cur> <max> 設定資源訪問許可權
 start <service> 開啟服務
 stop <service> 停止服務
 symlink <target> <path> 建立一個動態連結
 sysclktz <mins_west_of_gmt> 設定系統時鐘
 trigger <event> 觸發事件
 write <path> <string> [ <string> ]* 向<path>路徑的檔案寫入多個<string>

Properties(屬性)

Init更新一些系統屬性以提供對正在發生的事件的監控能力:
       init.action 此屬性值為正在被執行的action的名字,如果沒有則為""。
       init.command  此屬性值為正在被執行的command的名字,如果沒有則為""。
       init.svc.<name> 名為<name>的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 {
	/* node in list of all actions */
	struct listnode alist;
	/* node in the queue of pending actions */
	struct listnode qlist;
	/* node in list of actions for a trigger */
	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 {
        /* list of all services */
    struct listnode slist;
    const char *name;
    const char *classname;
    unsigned flags;
    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;

#ifdef HAVE_SELINUX
    char *seclabel;
#endif
    struct socketinfo *sockets;
    struct svcenvinfo *envvars;
    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];
};
對於某些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
{
        /* list of commands in an action */
    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,