linux启动分析

  1. u-boot-1.1.6  
  2.   
  3. u-boot配制分析:  
  4.     //Makefile  
  5.     make xxx_config ---->  
  6.         /*sbc2410x_config: unconfig 
  7.             @$(MKCONFIG) $(@:_config=) arm arm920t sbc2410x NULL s3c24x0*/  
  8.         @$(MKCONFIG) --->执行当前目录下的mkconfig  
  9.           
  10.     //mkconfig  
  11.         [ $# -lt 4 ] && exit 1  
  12.         [ $# -gt 6 ] && exit 1   //判断传入的参数的个数是否正确  
  13.           
  14.         cd ./include  
  15.         rm -f asm  
  16.         ln -s asm-$2 asm  = ln -s asm-arm arm   //建立软链接  
  17.   
  18.         echo "ARCH   = $2" >  config.mk  
  19.         echo "CPU    = $3" >> config.mk  
  20.         echo "BOARD  = $4" >> config.mk    //将配制文件写入config.mk中  
  21.           
  22. u-boot启动分析:  
  23.     //cpu/arm920t/start.S  
  24.     .globl _start  
  25.     _start: b       reset   --->  
  26.         reset:  
  27.             mrs r0,cpsr  
  28.             bic r0,r0,#0x1f  
  29.             orr r0,r0,#0xd3  
  30.             msr cpsr,r0     //设置CPU为管理模式  
  31.               
  32.             ldr     r0, =pWTCON  
  33.             mov     r1, #0x0  
  34.             str     r1, [r0]    //关看门狗  
  35.               
  36.             mov r1, #0xffffffff  
  37.             ldr r0, =INTMSK  
  38.             str r1, [r0]  
  39.             #if defined(CONFIG_S3C2410)  
  40.             ldr r1, =0x3ff  
  41.             ldr r0, =INTSUBMSK  
  42.             str r1, [r0]  
  43.             #endif              //关中断  
  44.             ........  
  45.               
  46.             ldr pc, _start_armboot  
  47.             _start_armboot: .word start_armboot ---> //跳入C程序入口  
  48.   
  49.     //lib_arm/board.c  
  50.     start_armboot(); --->  
  51.             //函数指针,进行一系列的初始化  
  52.             for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {  
  53.                 if ((*init_fnc_ptr)() != 0) {  
  54.                     hang ();  
  55.                 }  
  56.             }  
  57.               
  58.             //进入死循环  
  59.             for (;;) {  
  60.                 main_loop (); --->  
  61.             }  
  62.               
  63.     //common/main.c  
  64.     main_loop(); --->  
  65.             s = getenv ("bootcmd");  
  66.             run_command (s, 0); --->  
  67.                     parse_line();  
  68.                     find_cmd();  
  69.                     //函数调用,执行相应的命令  
  70.                     if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0);  
  71.                     //调用的函数为宏所定义的  
  72.                     #define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))  
  73.                     #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \  
  74.                             cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}  
  75.                     |  
  76.                     |                   //board/smdk2410/u-boot.lds  
  77.                     |                   __u_boot_cmd_start = .;  
  78.                     ---------------> .u_boot_cmd : { *(.u_boot_cmd) }  
  79.                                         __u_boot_cmd_end = .;  
  80.       
  81.                     //如  
  82.                     U_BOOT_CMD(nboot, 4, 1, do_nandboot,  
  83.                         "nboot   - boot from NAND device\n",  
  84.                         "[partition] | [[[loadAddr] dev] offset]\n"); --->  
  85.                                 do_nandboot();//做一些事  
  86.                                   
  87.                     //启动 /command/cmd_bootm.c  
  88.                     U_BOOT_CMD(bootm,   CFG_MAXARGS,1,do_bootm,"XXXX"); --->  
  89.                                 do_bootm(); -->  
  90.                                     do_bootm_linux(); --->  
  91.                                                 /*把参数保存到内存中*/  
  92.                                                 setup_start_tag (bd);  
  93.                                                 setup_serial_tag (¶ms);                                   
  94.                                                 setup_revision_tag (¶ms);                                                 
  95.                                                 setup_memory_tags (bd);                                               
  96.                                                 setup_commandline_tag (bd, commandline);                                                                      
  97.                                                 setup_videolfb_tag ((gd_t *) gd);                                                 
  98.                                                 setup_end_tag (bd);  
  99.                                           
  100.                                     ---->    theKernel = (void (*)(intint, uint))ntohl(hdr->ih_ep);  
  101.                                             theKernel (0, bd->bi_arch_number, bd->bi_boot_params); //内核起动  
  102.   
  103.       
  104. /******************************************************************************************************************/  
  105. linux-2.6.32  
  106. //编译内核时的步骤:  
  107.     (1),配制内核,make xxx_config 即/arch/arm/configs/XXX_config 生成 .config  
  108.         .config生成|(1),include/config/auto.conf   ---> //给子目录的Makefile用  
  109.                    |(2),include/linux/autoconf.h   ---> //给C代码用  
  110.     (2),make uImage   
  111.             顶Makefile里包含了/arch/arm/Makefile(include $(srctree)/arch/$(SRCARCH)/Makefile)  
  112.             ///arch/arm/Makefile  
  113.                 # Convert bzImage to zImage  
  114.                 bzImage: zImage  
  115.                 zImage Image xipImage bootpImage uImage: vmlinux  
  116.                 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@  
  117.               
  118.     (3),make  
  119.         当make时,  
  120.         all: vmlinux  
  121.   
  122.         //把这些文件编译成vmlinux.o  
  123.         vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE  
  124.   
  125.          SRCARCH     := $(ARCH)  
  126.          ARCH        ?= arm  
  127.  ①----->vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds   
  128.                       = arch/arm/kernel/vmlinux.lds   
  129.                        
  130.         //在/arch/arm/Makefile里定义               
  131.         head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o  
  132.                        = arch/arm/kernel/head.o arch/arm/kernel/init_task.o $(MMUEXT)为空  
  133.         //在顶层目录的Makefile  
  134.         init-y        := init/    (init/下的全部文件)  
  135.  ②----->vmlinux-init := $(head-y) $(init-y)  
  136.           
  137.         core-y         += kernel/ mm/ fs/ ipc/ security/ crypto/ block/  
  138.         core-y         := $(patsubst %/, %/built-in.o, $(core-y))   
  139.                         = core/built-in.o 即core/目录下的文件全部编译为built-in.o  
  140.         drivers-y      := $(patsubst %/, %/built-in.o, $(drivers-y))  
  141.                         = drivers/built-in.o  
  142.         net-y          := $(patsubst %/, %/built-in.o, $(net-y))  
  143.                         = net/built-in.o  
  144.  ③----->vmlinux-main  := $(core-y) $(libs-y) $(drivers-y) $(net-y)  
  145.           
  146.           
  147. /*Makefile的结果总结: 
  148.     (1)配置文件.config中定义了一系列的变量,Makefile将结合它们来决定哪些文件被编进内核,哪些 
  149.          文件被编成模块。涉及哪些文件目录 
  150.     (2)顶层Makefile和arch/$(ARCH)/Makefile决定根目录下哪些子目录,arch/$(ARCH)目录下哪些文件和 
  151.          目录将被编进内核 
  152.     (3)最后,各级子目录下的Makefile决定所在目录下哪些文件将被编进内核,哪些文件将被编成模块(驱动) 
  153.          进入哪些子目录继续调用它们的Makefile 
  154.     (4)顶层Makefile和arch/$(ARCH)/Makefile设置了可以影响所有文件的编译,连接选项:CFLAGS. AFLAGS... 
  155.     (5)顶层Makefile按照一定的顺序组织文件,根据连接脚本arch/$(ARCH)/kernel/vmlinux.lds生成内核映像 
  156.          文件vmlinux 
  157. */  
  158.            
  159.            
  160.            
  161. //内核启动流程:  
  162.     如上面可知,/arch/arm/kernel/head.S文件是内核启动的第一个文件  
  163.       
  164. ENTRY(stext)  
  165.     setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 确保进入管理(SVC)模式  
  166.                                     @ 禁止中断  
  167.     mrc p15, 0, r9, c0, c0          @ 读取CPU ID,存入R9寄存器  
  168.     bl  __lookup_processor_type     @ r5=返回值 r9=输入参数,调用函数  
  169.     movs    r10, r5                 @ 如果不支持当前CPU,返回R5=0  
  170.     beq __error_p                   @ 如果R5=0, 则打印错误  
  171.     bl  __lookup_machine_type       @ 调用函数,返回值r5=machinfo  
  172.     movs    r8, r5                  @ 如果不支持当前开发板,返回r5=0  
  173.     beq __error_a                   @ 如果R5=0,则打印错误  
  174.     bl  __vet_atags  
  175.     bl  __create_page_tables                //arch/arm/kernel/head-common.S  
  176.             __switch_data      ----->   __mmap_switched  ----->    start_kernel //起动内核  
  177.                   
  178.           
  179.     //__lookup_processor_type,__lookup_machine_type 函数在arch/arm/kernel/head-common.S中定义  
  180.       
  181. __lookup_processor_type:  
  182.     //arch\arm\kernel\vmlinux-lds.S      arch\arm\kernel\proc-arm920.S  
  183.     __proc_info_begin = .;          .section ".proc.info.init", #alloc, #execinstr  
  184.     *(.proc.info.init)      ----->  .type    __arm920_proc_info,#object  
  185.     __proc_info_end = .;    ------  __arm920_proc_info:           
  186.                             |         .long 0x41009200  //CPU id,cpu_val  
  187.                             |         .long 0xff00fff0  //cpu_mask  
  188.                             |               .....  
  189.                             |       //arch/arm/include/asm/procinfo.h         
  190.                             |       struct proc_info_list {  
  191.                             |           unsigned int        cpu_val;  
  192.                             |           unsigned int        cpu_mask;  
  193.                             |           unsigned long __cpu_mm_mmu_flags;   /* used by head.S */  
  194.                             |           unsigned long __cpu_io_mmu_flags;   /* used by head.S */  
  195.                             |           unsigned long __cpu_flush;          /* used by head.S */  
  196.                             |           const char      *arch_name;  
  197.                             |------>     const char      *elf_name;  
  198.                                         unsigned int        elf_hwcap;  
  199.                                         const char      *cpu_name;  
  200.                                         struct processor    *proc;  
  201.                                         struct cpu_tlb_fns  *tlb;  
  202.                                         struct cpu_user_fns *user;  
  203.                                         struct cpu_cache_fns    *cache;  
  204.                                     };  
  205.                                           
  206. __lookup_machine_type:            
  207.     //arch\arm\kernel\vmlinux-lds.S         arch/arm/include/asm/mach/arch.h  
  208.     __arch_info_begin = .;              #define MACHINE_START(_type,_name)      \  
  209.     *(.arch.info.init)      ------->     static const struct machine_desc __mach_desc_##_type \  
  210.     __arch_info_end = .;                    __used  \  
  211.                             --------        __attribute__((__section__(".arch.info.init"))) = { \  
  212.                             |               .nr     = MACH_TYPE_##_type,        \  
  213.                             |               .name       = _name,  
  214.                             |               #define MACHINE_END         \  
  215.                             |           };  
  216.                        |----|  
  217.                        |          
  218.     //linux/arch/arm/mach-s3c2410/mach-smdk2410.c  
  219.     MACHINE_START(SMDK2410, "SMDK2410")                     static const struct machine_desc __mach_desc_SMDK2410  
  220.         /* @TODO: request a new identifier and switch*/         __used  
  221.         /* to SMDK2410 */                                       __attribute__((__section__(".arch.info.init"))) = {  
  222.         /* Maintainer: Jonas Dietsche */                            .nr     = MACH_TYPE_SMDK2410,  
  223.         .phys_io    = S3C2410_PA_UART,              ------->     .name   = "SMDK2410",  
  224.         .io_pg_offst    = (((u32)S3C24XX_VA_UART)                   .......  
  225.                         >> 18) & 0xfffc,                          .......  
  226.         .boot_params    = S3C2410_SDRAM_PA + 0x100,             };  
  227.         .map_io     = smdk2410_map_io,  
  228.         .init_irq   = s3c24xx_init_irq,  
  229.         .init_machine   = smdk2410_init,  
  230.         .timer      = &s3c24xx_timer,  
  231.     MACHINE_END  
  232.       
  233.   
  234. //内核C程序起动分析:  
  235.     //在汇编程序/arch/arm/kernel/head-common.S中调用 start_kernel。  
  236.   
  237.     //init/main.c  
  238.     start_kernel --->  
  239.             printk(KERN_NOTICE "%s", linux_banner);//打印内核版本信息  
  240.             setup_arch(&command_line); --->  
  241.                     //进行处理器相关的一些设置  
  242.                     setup_processor(); --->  
  243.                             //目的是获得该处理器的proc_info_list结构  
  244.                             lookup_processor_type();--->   
  245.                                     __lookup_processor_type;//arch/arm/kernel/head-common.S  
  246.                     setup_machine(); --->  
  247.                             //获得开发板的machine_desc结构  
  248.                             lookup_machine_type(); --->  
  249.                                     __lookup_machine_type;//arch/arm/kernel/head-common.S  
  250.                     if (mdesc->boot_params){//用来确定bootloader传入的启动参数的地址  
  251.                         //mach-smdk2410.c ---> .boot_params  = S3C2410_SDRAM_PA + 0x100 = 0x30000100  
  252.                         tags = phys_to_virt(mdesc->boot_params);  
  253.                     }  
  254.                     //解释每一个tags  
  255.                     parse_tags(tags); --->  
  256.                             parse_tag(t); --->     
  257.                                 //处理每一个tag,调用每一种处理函数  
  258.                                 t->parse(tag); --->     
  259.                                         //在/arch/arm/kernel/setup.c里定义  
  260.                                         __tagtable(BP_TAG_MEMORY, parse_tag_mem);  
  261.                                         __tagtable(BP_TAG_COMMAND_LINE, parse_tag_cmdline);  
  262.                                             .....  
  263.                     parse_cmdline(cmdline_p, from);     //对命令行进行一些先期的处理  
  264.                     paging_init(struct machine_desc *mdesc); --->//重新初始化页表  
  265.                             devicemaps_init(struct machine_desc *mdesc); --->  
  266.                                         if (mdesc->map_io){  
  267.                                             mdesc->map_io();--->  
  268.                                                     //arch/arm/mach-s3c2410/mach-smdk2410.c  
  269.                                                     smdk2410_map_io();//设备串口相关的东西  
  270.                                         }  
  271.                                       
  272.             parse_early_param();  
  273.             parse_args(); --->  
  274.                     parse_one() --->  
  275.                         unknown_bootoption(); --->  
  276.                                 obsolete_checksetup(); --->  
  277.                                         p->setup_func(line + n); --->  
  278.                                             __setup(str, fn);//include/init/init.h  
  279.               
  280.   
  281.             console_init(); --->  
  282.                     console_initcall();  
  283.               
  284.             rest_init(); --->  
  285.                     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);  --->  
  286.                         //继续初始化内核,并创建init进程  
  287.                         kernel_init(); --->  
  288.                                 init_post(); --->  
  289.                                     //打开控制终端  
  290.                                     sys_open((const char __user *) "/dev/console", O_RDWR, 0);  
  291.                                     (void) sys_dup(0);  
  292.                                     (void) sys_dup(0);  
  293.                                     run_init_process("/XXX/init"); //开跑第一个应用程序  
  294.                                       
  295.                     kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);                
  296. /***************************************************************************************************************/                                         
  297.                                           

第一个应用程序:

//system/code/init/init.c

目前Linux有很多通讯机制可以在用户空间和内核空间之间交互,
例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys目录等)。
了解Linux的同学都应该知道Linux的重要特征之一就是一切都是以文件的形式存在的,
例如,一个设备通常与一个或多个设备文件对应。这些与内核空间交互的文件都在用户空间,
所以在Linux内核装载完,需要首先建立这些文件所在的目录。
而完成这些工作的程序就是本文要介绍的init。Init是一个命令行程序。
其主要工作之一就是建立这些与内核空间交互的文件所在的目录。当Linux内核加载完后,
要做的第一件事就是调用init程序,也就是说,init是用户空间执行的第一个程序。


在分析init的核心代码之前,还需要初步了解init除了建立一些目录外,还做了如下的工作


1. 初始化属性


2. 处理配置文件的命令(主要是init.rc文件),包括处理各种Action。


3. 性能分析(使用bootchart工具)。


4. 无限循环执行command(启动其他的进程)。


     尽管init完成的工作不算很多,不过代码还是非常复杂的。Init程序并不是由一个源代码文件组成的,而是由一组源代码文件的目标文件链接而成的。这些文件位于如下的目录。


<Android源代码本目录>/system/core/init


     其中init.c是init的主文件,现在打开该文件,看看其中的内容。由于init是命令行程序,所以分析init.c首先应从main函数开始,现在好到main函数,代码如下:
 

[cpp] view plain copy print ?
  1. int main(int argc, char **argv)  
  2. {  
  3.     int fd_count = 0;  
  4.     struct pollfd ufds[4];  
  5.     char *tmpdev;  
  6.     char* debuggable;  
  7.     char tmp[32];  
  8.     int property_set_fd_init = 0;  
  9.     int signal_fd_init = 0;  
  10.     int keychord_fd_init = 0;  
  11.   
  12.     if (!strcmp(basename(argv[0]), "ueventd"))  
  13.         return ueventd_main(argc, argv);  
  14.   
  15.     /* clear the umask */  
  16.     umask(0);  
  17.   
  18.         /* Get the basic filesystem setup we need put 
  19.          * together in the initramdisk on / and then we'll 
  20.          * let the rc file figure out the rest. 
  21.          * 下面的代码开始建立各种用户空间的目录,如/dev、/proc、/sys等 
  22.          */  
  23.     mkdir("/dev", 0755);  
  24.     mkdir("/proc", 0755);  
  25.     mkdir("/sys", 0755);  
  26.   
  27.     mount("tmpfs""/dev""tmpfs", 0, "mode=0755");  
  28.     mkdir("/dev/pts", 0755);  
  29.     mkdir("/dev/socket", 0755);  
  30.     mount("devpts""/dev/pts""devpts", 0, NULL);  
  31.     mount("proc""/proc""proc", 0, NULL);  
  32.     mount("sysfs""/sys""sysfs", 0, NULL);  
  33.   
  34.         /* We must have some place other than / to create the 
  35.          * device nodes for kmsg and null, otherwise we won't 
  36.          * be able to remount / read-only later on. 
  37.          * Now that tmpfs is mounted on /dev, we can actually 
  38.          * talk to the outside world. 
  39.          */  
  40.     open_devnull_stdio();  
  41.     log_init();  
  42.       
  43.     INFO("reading config file\n");//  分析/init.rc文件的内容  
  44.     init_parse_config_file("/init.rc");  
  45.   
  46.     /* pull the kernel commandline and ramdisk properties file in */  
  47.     import_kernel_cmdline(0);  
  48.   
  49.     get_hardware_name(hardware, &revision);  
  50.     snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);  
  51.     init_parse_config_file(tmp);  
  52.   
  53.     action_for_each_trigger("early-init", action_add_queue_tail);  
  54.   
  55.     queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");  
  56.     queue_builtin_action(property_init_action, "property_init");  
  57.     queue_builtin_action(keychord_init_action, "keychord_init");  
  58.     queue_builtin_action(console_init_action, "console_init");  
  59.     queue_builtin_action(set_init_properties_action, "set_init_properties");  
  60.   
  61.         /* execute all the boot actions to get us started */  
  62.     action_for_each_trigger("init", action_add_queue_tail);  
  63.     action_for_each_trigger("early-fs", action_add_queue_tail);  
  64.     action_for_each_trigger("fs", action_add_queue_tail);  
  65.     action_for_each_trigger("post-fs", action_add_queue_tail);  
  66.   
  67.     queue_builtin_action(property_service_init_action, "property_service_init");  
  68.     queue_builtin_action(signal_init_action, "signal_init");  
  69.     queue_builtin_action(check_startup_action, "check_startup");  
  70.   
  71.     /* execute all the boot actions to get us started */  
  72.     action_for_each_trigger("early-boot", action_add_queue_tail);  
  73.     action_for_each_trigger("boot", action_add_queue_tail);  
  74.   
  75.         /* run all property triggers based on current state of the properties */  
  76.     queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");  
  77.   
  78.   
  79. #if BOOTCHART  
  80.     queue_builtin_action(bootchart_init_action, "bootchart_init");  
  81. #endif  
  82.   
  83.     //  进入无限循环,建立init的子进程(init是所有进程的父进程)  
  84.     for(;;) {  
  85.         int nr, i, timeout = -1;  
  86.   
  87.     //  执行命令(子进程对应的命令)  
  88.         execute_one_command();  
  89.         restart_processes();  
  90.   
  91.         if (!property_set_fd_init && get_property_set_fd() > 0) {  
  92.             ufds[fd_count].fd = get_property_set_fd();  
  93.             ufds[fd_count].events = POLLIN;  
  94.             ufds[fd_count].revents = 0;  
  95.             fd_count++;  
  96.             property_set_fd_init = 1;  
  97.         }  
  98.         if (!signal_fd_init && get_signal_fd() > 0) {  
  99.             ufds[fd_count].fd = get_signal_fd();  
  100.             ufds[fd_count].events = POLLIN;  
  101.             ufds[fd_count].revents = 0;  
  102.             fd_count++;  
  103.             signal_fd_init = 1;  
  104.         }  
  105.         if (!keychord_fd_init && get_keychord_fd() > 0) {  
  106.             ufds[fd_count].fd = get_keychord_fd();  
  107.             ufds[fd_count].events = POLLIN;  
  108.             ufds[fd_count].revents = 0;  
  109.             fd_count++;  
  110.             keychord_fd_init = 1;  
  111.         }  
  112.   
  113.         if (process_needs_restart) {  
  114.             timeout = (process_needs_restart - gettime()) * 1000;  
  115.             if (timeout < 0)  
  116.                 timeout = 0;  
  117.         }  
  118.   
  119.         if (!action_queue_empty() || cur_action)  
  120.             timeout = 0;  
  121.   
  122. #if BOOTCHART  
  123.         if (bootchart_count > 0) {  
  124.             if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)  
  125.                 timeout = BOOTCHART_POLLING_MS;  
  126.             if (bootchart_step() < 0 || --bootchart_count == 0) {  
  127.                 bootchart_finish();  
  128.                 bootchart_count = 0;  
  129.             }  
  130.         }  
  131. #endif  
  132.   
  133.     //  等待下一个命令的提交    
  134.         nr = poll(ufds, fd_count, timeout);  
  135.         if (nr <= 0)  
  136.             continue;  
  137.   
  138.         for (i = 0; i < fd_count; i++) {  
  139.             if (ufds[i].revents == POLLIN) {  
  140.                 if (ufds[i].fd == get_property_set_fd())  
  141.                     handle_property_set_fd();  
  142.                 else if (ufds[i].fd == get_keychord_fd())  
  143.                     handle_keychord();  
  144.                 else if (ufds[i].fd == get_signal_fd())  
  145.                     handle_signal();  
  146.             }  
  147.         }  
  148.     }  
  149.   
  150.     return 0;  
  151. }  

我们可以看到main函数是非常复杂的,不过我们也不需要每条语句都弄得非常清楚(因为这样弄是非常困难的),通常只需要了解init的主线即可。其实从init的main函数可以看出。Init实际上就分为如下两部分。

1.  初始化(包括建立/dev、/proc等目录、初始化属性、执行init.rc等初始化文件中的action等)。

2.  使用for循环无限循环建立子进程。

     第一项工作很好理解。而第二项工作是init中的核心。在Linux系统中init是一切应用空间进程的父进程。所以我们平常在Linux终端执行的命令,并建立进程。实际上都是在这个无限的for循环中完成的。也就是说,在Linux终端执行ps –e 命令后,看到的所有除了init外的其他进程,都是由init负责创建的。而且init也会常驻内容。当然,如果init挂了,Linux系统基本上就崩溃了。

    由于init比较复杂,所以本文只分析其中的一部分,在后续文章中将详细分析init的各个核心组成部分。

      对于main函数最开始完成的建立目录的工作比较简单,这部分也没什么可以分析的。就是调用了一些普通的API(mkdir)建立一些目录。现在说一些题外话,由于Android的底层源代码(包括init)实际上是属于Linux应用编程领域,所以要想充分理解Android源代码,除了Linux的基本结构要了解外,Linux应用层的API需要熟悉。为了满足这些读者的需要,后续我会写一些关于Linux应用编程的文章。Ok,现在言归正传,接下来分析一个比较重要的部分:配置文件的解析。

      这里的配置文件主要指init.rc。读者可以进到Android的shell,会看到根目录有一个init.rc文件。该文件是只读的,即使有了root权限,可以修改该文件也没有。因为我们在根目录看到的文件只是内存文件的镜像。也就是说,android启动后,会将init.rc文件装载到内存。而修改init.rc文件的内容实际上只是修改内存中的init.rc文件的内容。一旦重启android,init.rc文件的内容又会恢复到最初的装载。想彻底修改init.rc文件内容的唯一方式是修改Android的ROM中的内核镜像(boot.img)。其实boot.img名曰内核镜像,不过该文件除了包含完整的Linux内核文件(zImage)外,还包括另外一个镜像文件(ramdisk.img)。ramdisk.img就包含了init.rc文件和init命令。所以只有修改ramdisk.img文件中的init.rc文件,并且重新打包boot.img文件,并刷机,才能彻底修改init.rc文件。如果读者有Android源代码,编译后,就会看到out目录中的相关子目录会生成一个root目录,该目录实际上就是ramdisk.img解压后的内容。会看到有init命令和init.rc文件。在后续的文章中将会讨论具体如何修改init.rc文件,如何刷机。不过这些内容与本文关系不大,所以不做详细的讨论。

现在回到main函数,在创建完目录后,会看到执行了如下3个函数。

    property_init();
    get_hardware_name(hardware, &revision);
    process_kernel_cmdline();

     其中property_init主要是为属性分配一些存储空间,该函数并不是核心。不过当我们查看init.rc文件时会发现该文件开始部分用一些import语句导入了其他的配置文件,例如,/init.usb.rc。大多数配置文件都直接使用了确定的文件名,只有如下的代码使用了一个变量(${ro.hardware})执行了配置文件名的一部分。那么这个变量值是从哪获得的呢?

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

     首先要了解init.${ro.hardware}.rc配置文件的内容通常与当前的硬件有关。现在我们先来关注get_hardware_name函数,代码如下:

[cpp] view plain copy print ?
  1. void get_hardware_name(char *hardware, unsigned int *revision)  
  2. {  
  3.     char data[1024];  
  4.     int fd, n;  
  5.     char *x, *hw, *rev;  
  6.   
  7.     /* Hardware string was provided on kernel command line */  
  8.     /* 如果hardware已经有值了,说明hardware通过内核命令行提供,直接返回 */  
  9.     if (hardware[0])  
  10.         return;  
  11.   
  12.     //  打开/proc/cpuinfo文件    
  13.     fd = open("/proc/cpuinfo", O_RDONLY);  
  14.     if (fd < 0) return;  
  15.   
  16.     //  读取/proc/cpuinfo文件的内容   
  17.     n = read(fd, data, 1023);  
  18.     close(fd);  
  19.     if (n < 0) return;  
  20.   
  21.    //  从/proc/cpuinfo文件中获取Hardware字段的值  
  22.     data[n] = 0;  
  23.     hw = strstr(data, "\nHardware");  
  24.     rev = strstr(data, "\nRevision");  
  25.   
  26.     if (hw) {//  成功获取Hardware字段的值  
  27.         x = strstr(hw, ": ");  
  28.         if (x) {  
  29.             x += 2;  
  30.             n = 0;  
  31.             while (*x && !isspace(*x)) {  
  32.                 hardware[n++] = tolower(*x);        //  将Hardware字段的值都转换为小写,并更新hardware参数的值    
  33.                                 //  hardware也就是在init.c文件中定义的hardware数组    
  34.                 x++;  
  35.                 if (n == 31) break;  
  36.             }  
  37.             hardware[n] = 0;  
  38.         }  
  39.     }  
  40.   
  41.     if (rev) {  
  42.         x = strstr(rev, ": ");  
  43.         if (x) {  
  44.             *revision = strtoul(x + 2, 0, 16);  
  45.         }  
  46.     }  
  47. }  

从get_hardware_name方法的代码可以得知,该方法主要用于确定hardware和revision的变量的值。Revision这里先不讨论,只要研究hardware。获取hardware的来源是从Linux内核命令行或/proc/cpuinfo文件中的内容。Linux内核命令行暂且先不讨论(因为很少传递该值),先看看/proc/cpuinfo,该文件是虚拟文件(内存文件),执行cat /proc/cpuinfo命令会看到该文件中的内容,如图1所示。在白框中就是Hardware字段的值。由于该设备是Nexus 7,所以值为grouper。如果程序就到此位置,那么与硬件有关的配置文件名是init.grouper.rc。有Nexus 7的读者会看到在根目录下确实有一个init.grouper.rc文件。说明Nexus 7的原生ROM并没有在其他的地方设置配置文件名,所以配置文件名就是从/proc/cpuinfo文件的Hardware字段中取的值。


 在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性。在本文将会详细分析init最重要的一环:解析init.rc文件。

init.rc文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析init.c及其相关文件的源代码毫无意义。

     为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码,在<Android源代码根目录>out/target/product/generic/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的语法格式如下:

[plain]  view plain copy
  1. on <trigger>  
  2.    <command>  
  3.    <command>  
  4.    <command>  

也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action

[plain]  view plain copy
  1. on boot  
  2.     ifup lo  
  3.     hostname localhost  
  4.     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 (服务)的形式如下:

[plain]  view plain copy
  1. service <name> <pathname> [ <argument> ]*  
  2.       <option>  
  3.       <option>  

例如,下面是一个标准的Service用法

[plain]  view plain copy
  1. service servicemanager /system/bin/servicemanager  
  2.     class core  
  3.     user system  
  4.     group system  
  5.     critical  
  6.     onrestart restart zygote  
  7.     onrestart restart media  
  8.     onrestart restart surfaceflinger  
  9.     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是如何解析init.rc的。现在打开system/core/init/init.c文件,找到main函数。在上一篇文章中分析了main函数的前一部分(初始化属性、处理内核命令行等),现在找到init_parse_config_file函数,调用代码如下:

init_parse_config_file("/init.rc");

这个方法主要负责初始化和分析init.rc文件。init_parse_config_file函数在init_parser.c文件中实现,代码如下:

[cpp]  view plain copy
  1. int init_parse_config_file(const char *fn)  
  2. {  
  3.     char *data;  
  4.     data = read_file(fn, 0);  
  5.     if (!data) return -1;  
  6.     /*  实际分析init.rc文件的代码  */  
  7.     parse_config(fn, data);  
  8.     DUMP();  
  9.     return 0;  
  10. }  

      init_parse_config_file方法开始调用了read_file函数打开了/init.rc文件,并返回了文件的内容(char*类型),然后最核心的函数是parse_config。该函数也在init_parser.c文件中实现,代码如下:

[cpp]  view plain copy
  1. static void parse_config(const char *fn, char *s)  
  2. {  
  3.     struct parse_state state;  
  4.     struct listnode import_list;  
  5.     struct listnode *node;  
  6.     char *args[INIT_PARSER_MAXARGS];  
  7.     int nargs;  
  8.   
  9.     nargs = 0;  
  10.     state.filename = fn;  
  11.     state.line = 0;  
  12.     state.ptr = s;  
  13.     state.nexttoken = 0;  
  14.     state.parse_line = parse_line_no_op;  
  15.   
  16.     list_init(&import_list);  
  17.     state.priv = &import_list;  
  18.     /*  开始获取每一个token,然后分析这些token,每一个token就是有空格、字表符和回车符分隔的字符串 
  19.    */  
  20.     for (;;) {  
  21.         /*  next_token函数相当于词法分析器  */  
  22.         switch (next_token(&state)) {  
  23.         case T_EOF:  /*  init.rc文件分析完毕  */  
  24.             state.parse_line(&state, 0, 0);  
  25.             goto parser_done;  
  26.         case T_NEWLINE:  /*  分析每一行的命令  */  
  27.             /*  下面的代码相当于语法分析器  */  
  28.             state.line++;  
  29.             if (nargs) {  
  30.                 int kw = lookup_keyword(args[0]);  
  31.                 if (kw_is(kw, SECTION)) {  
  32.                     state.parse_line(&state, 0, 0);  
  33.                     parse_new_section(&state, kw, nargs, args);  
  34.                 } else {  
  35.                     state.parse_line(&state, nargs, args);  
  36.                 }  
  37.                 nargs = 0;  
  38.             }  
  39.             break;  
  40.         case T_TEXT:  /*  处理每一个token  */  
  41.             if (nargs < INIT_PARSER_MAXARGS) {  
  42.                 args[nargs++] = state.text;  
  43.             }  
  44.             break;  
  45.         }  
  46.     }  
  47.   
  48. parser_done:  
  49.     /*  最后处理由import导入的初始化文件  */  
  50.     list_for_each(node, &import_list) {  
  51.          struct import *import = node_to_item(node, struct import, list);  
  52.          int ret;  
  53.   
  54.          INFO("importing '%s'", import->filename);  
  55.          /*  递归调用  */   
  56.          ret = init_parse_config_file(import->filename);  
  57.          if (ret)  
  58.              ERROR("could not import file '%s' from '%s'\n",  
  59.                    import->filename, fn);  
  60.     }  
  61. }  

       parse_config方法的代码就比较复杂了,现在先说说该方法的基本处理流程。首先会调用  list_init(&import_list)初始化一个链表,该链表是用于存储通过import语句导入的初始化文件名。然后开始开始在for循环中分析init.rc文件中的每一行代码。最后将init.rc文件分析完后,就会进入parser_done部分,并递归调用init_parse_config_file方法分析通过import导入的初始化文件。

      通过分析parse_config方法的原理,感觉也并不是很复杂。不过分析parse_config方法的具体代码,还需要点编译原理的知识(只是概念上的就可以)。在for循环中调用了一个next_token方法不断从init.rc文件中获取token。这里的token,就是一种编程语言的最小单元,也就是不可再分。例如,对于传统的编程语言,if、then等关键字、变量名等标识符都属于一个token。而对于init.rc文件来说,import、on、以及触发器的参数值,都属于一个token。

     一个完整的编译器(或解析器)最开始需要进行词法和语法分析,词法分析就是在源代码文件中挑出一个个的Token,也就是说,词法分析器的返回值是Token,而语法分析器的输入就是词法分析器的输出。也就是说,语法分析器需要分析一个个的token,而不是一个个的字符。由于init解析语言很简单,所以就将词法和语法分析器放到了一起。词法分析器就是next_token函数,而语法分析器就是T_NEWLINE分支中的代码。这些就清楚多了。现在先看看next_token函数(在parser.c文件中实现)是如何获取每一个token的。

[cpp]  view plain copy
  1. int next_token(struct parse_state *state)  
  2. {  
  3.     char *x = state->ptr;  
  4.     char *s;  
  5.   
  6.     if (state->nexttoken) {  
  7.         int t = state->nexttoken;  
  8.         state->nexttoken = 0;  
  9.         return t;  
  10.     }  
  11.     /*  在这里开始一个字符一个字符地分析  */  
  12.     for (;;) {  
  13.         switch (*x) {  
  14.         case 0:  
  15.             state->ptr = x;  
  16.             return T_EOF;  
  17.         case '\n':  
  18.             x++;  
  19.             state->ptr = x;  
  20.             return T_NEWLINE;  
  21.         case ' ':  
  22.         case '\t':  
  23.         case '\r':  
  24.             x++;  
  25.             continue;  
  26.         case '#':  
  27.             while (*x && (*x != '\n')) x++;  
  28.             if (*x == '\n') {  
  29.                 state->ptr = x+1;  
  30.                 return T_NEWLINE;  
  31.             } else {  
  32.                 state->ptr = x;  
  33.                 return T_EOF;  
  34.             }  
  35.         default:  
  36.             goto text;  
  37.         }  
  38.     }  
  39.   
  40. textdone:  
  41.     state->ptr = x;  
  42.     *s = 0;  
  43.     return T_TEXT;  
  44. text:  
  45.     state->text = s = x;  
  46. textresume:  
  47.     for (;;) {  
  48.         switch (*x) {  
  49.         case 0:  
  50.             goto textdone;  
  51.         case ' ':  
  52.         case '\t':  
  53.         case '\r':  
  54.             x++;  
  55.             goto textdone;  
  56.         case '\n':  
  57.             state->nexttoken = T_NEWLINE;  
  58.             x++;  
  59.             goto textdone;  
  60.         case '"':  
  61.             x++;  
  62.             for (;;) {  
  63.                 switch (*x) {  
  64.                 case 0:  
  65.                         /* unterminated quoted thing */  
  66.                     state->ptr = x;  
  67.                     return T_EOF;  
  68.                 case '"':  
  69.                     x++;  
  70.                     goto textresume;  
  71.                 default:  
  72.                     *s++ = *x++;  
  73.                 }  
  74.             }  
  75.             break;  
  76.         case '\\':  
  77.             x++;  
  78.             switch (*x) {  
  79.             case 0:  
  80.                 goto textdone;  
  81.             case 'n':  
  82.                 *s++ = '\n';  
  83.                 break;  
  84.             case 'r':  
  85.                 *s++ = '\r';  
  86.                 break;  
  87.             case 't':  
  88.                 *s++ = '\t';  
  89.                 break;  
  90.             case '\\':  
  91.                 *s++ = '\\';  
  92.                 break;  
  93.             case '\r':  
  94.                     /* \ <cr> <lf> -> line continuation */  
  95.                 if (x[1] != '\n') {  
  96.                     x++;  
  97.                     continue;  
  98.                 }  
  99.             case '\n':  
  100.                     /* \ <lf> -> line continuation */  
  101.                 state->line++;  
  102.                 x++;  
  103.                     /* eat any extra whitespace */  
  104.                 while((*x == ' ') || (*x == '\t')) x++;  
  105.                 continue;  
  106.             default:  
  107.                     /* unknown escape -- just copy */  
  108.                 *s++ = *x++;  
  109.             }  
  110.             continue;  
  111.         default:  
  112.             *s++ = *x++;  
  113.         }  
  114.     }  
  115.     return T_EOF;  
  116. }  

      next_token函数的代码还是很多的,不过原理到很简单。就是逐一读取init.rc文件(还有import导入的初始化文件)的字符,并将由空格、“/t”和“/r”分隔的字符串挑出来,并通过state->text返回。如果返回了正常的token,next_token函数就返回T_TEXT。如果一行结束,就返回T_NEWLINE,如果init.rc文件的内容已读取完,就返回T_EOF。当返回T_NEWLINE时,开始语法分析(由于init初始化语言是基于行的,所以语言分析实际上就是分析init.rc文件的每一行,只是这些行已经被分解成一个个token了)。感兴趣的读者可以详细分析一下next_token函数的代码,尽管代码很多,但并不复杂。而且还很有意思。

      现在回到parse_config函数,先看一下T_TEXT分支。该分支将获得的每一行的token都存储在args数组中。现在来看T_NEWLINE分支。该分支的代码涉及到一个state.parse_line函数指针,该函数指针指向的函数负责具体的分析工作。但我们发现,一看是该函数指针指向了一个空函数parse_line_no_op,实际上,一开始该函数指针什么都不做,只是为了使该函数一开始不至于为null,否则调用出错。

     现在来回顾一下T_NEWLINE分支的完整代码。

[cpp]  view plain copy
  1. case T_NEWLINE:  
  2.     state.line++;  
  3.     if (nargs) {  
  4.         int kw = lookup_keyword(args[0]);  
  5.         if (kw_is(kw, SECTION)) {  
  6.             state.parse_line(&state, 0, 0);  
  7.             parse_new_section(&state, kw, nargs, args);  
  8.         } else {  
  9.             state.parse_line(&state, nargs, args);  
  10.         }  
  11.         nargs = 0;  
  12.     }  
  13.     break;  
    在上面的代码中首先调用了lookup_keyword方法搜索关键字。该方法的作用是判断当前行是否合法,也就是根据Init初始化语言预定义的关键字查询,如果未查到,返回K_UNKNOWN。lookup_keyword方法在init_parser.c文件中实现,代码如下:

[cpp]  view plain copy
  1. int lookup_keyword(const char *s)  
  2. {  
  3.     switch (*s++) {  
  4.     case 'c':  
  5.     if (!strcmp(s, "opy")) return K_copy;  
  6.         if (!strcmp(s, "apability")) return K_capability;  
  7.         if (!strcmp(s, "hdir")) return K_chdir;  
  8.         if (!strcmp(s, "hroot")) return K_chroot;  
  9.         if (!strcmp(s, "lass")) return K_class;  
  10.         if (!strcmp(s, "lass_start")) return K_class_start;  
  11.         if (!strcmp(s, "lass_stop")) return K_class_stop;  
  12.         if (!strcmp(s, "lass_reset")) return K_class_reset;  
  13.         if (!strcmp(s, "onsole")) return K_console;  
  14.         if (!strcmp(s, "hown")) return K_chown;  
  15.         if (!strcmp(s, "hmod")) return K_chmod;  
  16.         if (!strcmp(s, "ritical")) return K_critical;  
  17.         break;  
  18.     case 'd':  
  19.         if (!strcmp(s, "isabled")) return K_disabled;  
  20.         if (!strcmp(s, "omainname")) return K_domainname;  
  21.         break;  
  22.      … …  
  23.     case 'o':  
  24.         if (!strcmp(s, "n")) return K_on;  
  25.         if (!strcmp(s, "neshot")) return K_oneshot;  
  26.         if (!strcmp(s, "nrestart")) return K_onrestart;  
  27.         break;  
  28.     case 'r':  
  29.         if (!strcmp(s, "estart")) return K_restart;  
  30.         if (!strcmp(s, "estorecon")) return K_restorecon;  
  31.         if (!strcmp(s, "mdir")) return K_rmdir;  
  32.         if (!strcmp(s, "m")) return K_rm;  
  33.         break;  
  34.     case 's':  
  35.         if (!strcmp(s, "eclabel")) return K_seclabel;  
  36.         if (!strcmp(s, "ervice")) return K_service;  
  37.         if (!strcmp(s, "etcon")) return K_setcon;  
  38.         if (!strcmp(s, "etenforce")) return K_setenforce;  
  39.         if (!strcmp(s, "etenv")) return K_setenv;  
  40.         if (!strcmp(s, "etkey")) return K_setkey;  
  41.         if (!strcmp(s, "etprop")) return K_setprop;  
  42.         if (!strcmp(s, "etrlimit")) return K_setrlimit;  
  43.         if (!strcmp(s, "etsebool")) return K_setsebool;  
  44.         if (!strcmp(s, "ocket")) return K_socket;  
  45.         if (!strcmp(s, "tart")) return K_start;  
  46.         if (!strcmp(s, "top")) return K_stop;  
  47.         if (!strcmp(s, "ymlink")) return K_symlink;  
  48.         if (!strcmp(s, "ysclktz")) return K_sysclktz;  
  49.         break;  
  50.     case 't':  
  51.         if (!strcmp(s, "rigger")) return K_trigger;  
  52.         break;  
  53.     case 'u':  
  54.         if (!strcmp(s, "ser")) return K_user;  
  55.         break;  
  56.     case 'w':  
  57.         if (!strcmp(s, "rite")) return K_write;  
  58.         if (!strcmp(s, "ait")) return K_wait;  
  59.         break;  
  60.     }  
  61.     return K_UNKNOWN;  
  62. }  

     lookup_keyword方法按26个字母顺序(关键字首字母)进行处理。

     现在回到parse_config方法的T_NEWLIEN分支,接下来调用了kw_is宏具体判断当前行是否合法,该宏以及SECTION宏的定义如下。根据这些代码。明显是keyword_info数组中的某个元素的flags成员变量的值取最后一位。

[cpp]  view plain copy
  1. #define SECTION 0x01  
  2. #define kw_is(kw, type) (keyword_info[kw].flags & (type))  

现在问题又转到keyword_info数组了。该数组也在init_parser.c文件中定义,代码如下:

[cpp]  view plain copy
  1. #include "keywords.h"  
  2. #define KEYWORD(symbol, flags, nargs, func) \  
  3.     [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },  
  4. struct {  
  5.     const char *name;  
  6.     int (*func)(int nargs, char **args);  
  7.     unsigned char nargs;  
  8.     unsigned char flags;  
  9. } keyword_info[KEYWORD_COUNT] = {  
  10.     [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },  
  11. #include "keywords.h"  
  12. };  

       从表面上看,keyword_info数组是一个struct数组,但本质上,是一个map。为每一个数组元素设置了一个key,例如,数组元素{ "unknown", 0, 0,0 }的key是K_UNKNOWN,而#include “keywords.h”大有玄机。上面的代码中引用了两次keywords.h文件,现在可以看一下keywords.h文件的代码。

[cpp]  view plain copy
  1. #ifndef KEYWORD  
  2. int do_chroot(int nargs, char **args);  
  3. … …  
  4. int do_export(int nargs, char **args);  
  5. int do_hostname(int nargs, char **args);  
  6. int do_rmdir(int nargs, char **args);  
  7. int do_loglevel(int nargs, char **args);  
  8. int do_load_persist_props(int nargs, char **args);  
  9. int do_wait(int nargs, char **args);  
  10. #define __MAKE_KEYWORD_ENUM__  
  11. /* 
  12. "K_chdir", ENUM 
  13. */  
  14. #define KEYWORD(symbol, flags, nargs, func) K_##symbol,  
  15. enum {  
  16.     K_UNKNOWN,  
  17. #endif  
  18.     KEYWORD(capability,  OPTION,  0, 0)  
  19.     KEYWORD(chdir,       COMMAND, 1, do_chdir)  
  20.     KEYWORD(chroot,      COMMAND, 1, do_chroot)  
  21.     KEYWORD(class,       OPTION,  0, 0)  
  22.     KEYWORD(class_start, COMMAND, 1, do_class_start)  
  23.     KEYWORD(class_stop,  COMMAND, 1, do_class_stop)  
  24.     KEYWORD(class_reset, COMMAND, 1, do_class_reset)  
  25.     KEYWORD(console,     OPTION,  0, 0)  
  26.     … …  
  27.     KEYWORD(critical,    OPTION,  0, 0)  
  28.     KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)  
  29.     KEYWORD(ioprio,      OPTION,  0, 0)  
  30. #ifdef __MAKE_KEYWORD_ENUM__  
  31.     KEYWORD_COUNT,  
  32. };  
  33. #undef __MAKE_KEYWORD_ENUM__  
  34. #undef KEYWORD  
  35. #endif  

      从keywords.h文件的代码可以看出,如果未定义KEYWORD宏,则在keywords.h文件中定义一个KEYWORD宏,以及一个枚举类型,其中K_##symbol的##表示连接的意思。而这个KEYWORD宏只用了第一个参数(symbol)。例如,KEYWORD(chdir,       COMMAND, 1, do_chdir)就会生成K_chdir。

     而在keyword_info结构体数组中再次导入keywords.h文件,这是KEYWORD宏已经在init_parser.c文件中重新定义,所以第一次导入keywords.h文件使用的是如下的宏。

[cpp]  view plain copy
  1. #define KEYWORD(symbol, flags, nargs, func) \  
  2.     [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },  
     这下就明白了,如果不使用keywords.h文件,直接将所有的代码都写到init_parser.c文件中,就会有下面的代码。

[cpp]  view plain copy
  1. int do_chroot(int nargs, char **args);  
  2. … …  
  3. enum  
  4. {  
  5. K_UNKNOWN,  
  6. K_ capability,  
  7. K_ chdir,  
  8. … …  
  9. }  
  10. #define KEYWORD(symbol, flags, nargs, func) \  
  11.     [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },  
  12. struct {  
  13.     const char *name;  
  14.     int (*func)(int nargs, char **args);  
  15.     unsigned char nargs;  
  16.     unsigned char flags;  
  17. } keyword_info[KEYWORD_COUNT] = {  
  18.     [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },  
  19.     [K_ capability] = {" capability ", 0, 1, OPTION },  
  20.     [K_ chdir] = {"chdir", do_chdir ,2, COMMAND},  
  21.     … …  
  22. #include "keywords.h"  
  23. };  
      可能我们还记着lookup_keyword方法,该方法的返回值就是keyword_info数组的key。

      在keywords.h前面定义的函数指针都是处理init.rc文件中service、action和command的。现在就剩下一个问题了,在哪里为这些函数指针赋值呢,也就是说,具体处理每个部分的函数在哪里呢。现在回到前面的语法分析部分。如果当前行合法,则会执行parse_new_section函数(在init_parser.c文件中实现),该函数将为section和action设置处理这两部分的函数。parse_new_section函数的代码如下:

[cpp]  view plain copy
  1. void parse_new_section(struct parse_state *state, int kw,  
  2.                        int nargs, char **args)  
  3. {  
  4.     printf("[ %s %s ]\n", args[0],  
  5.            nargs > 1 ? args[1] : "");  
  6.     switch(kw) {  
  7.     case K_service:  //  处理service  
  8.         state->context = parse_service(state, nargs, args);  
  9.         if (state->context) {  
  10.             state->parse_line = parse_line_service;  
  11.             return;  
  12.         }  
  13.         break;  
  14.     case K_on:  //  处理action  
  15.         state->context = parse_action(state, nargs, args);  
  16.         if (state->context) {  
  17.             state->parse_line = parse_line_action;  
  18.             return;  
  19.         }  
  20.         break;  
  21.     case K_import:   //  单独处理import导入的初始化文件。  
  22.         parse_import(state, nargs, args);  
  23.         break;  
  24.     }  
  25.     state->parse_line = parse_line_no_op;  
  26. }  

      现在看一下处理service的函数(parse_line_service)。

[cpp]  view plain copy
  1. static void parse_line_service(struct parse_state *state, int nargs, char **args)  
  2. {  
  3.     struct service *svc = state->context;  
  4.     struct command *cmd;  
  5.     int i, kw, kw_nargs;  
  6.   
  7.     if (nargs == 0) {  
  8.         return;  
  9.     }  
  10.   
  11.     svc->ioprio_class = IoSchedClass_NONE;  
  12.   
  13.     kw = lookup_keyword(args[0]);  
  14.     //  下面处理每一个option  
  15.     switch (kw) {  
  16.     case K_capability:  
  17.         break;  
  18.     … …  
  19.     case K_group:  
  20.         if (nargs < 2) {  
  21.             parse_error(state, "group option requires a group id\n");  
  22.         } else if (nargs > NR_SVC_SUPP_GIDS + 2) {  
  23.             parse_error(state, "group option accepts at most %d supp. groups\n",  
  24.                         NR_SVC_SUPP_GIDS);  
  25.         } else {  
  26.             int n;  
  27.             svc->gid = decode_uid(args[1]);  
  28.             for (n = 2; n < nargs; n++) {  
  29.                 svc->supp_gids[n-2] = decode_uid(args[n]);  
  30.             }  
  31.             svc->nr_supp_gids = n - 2;  
  32.         }  
  33.         break;  
  34.     case K_keycodes:  
  35.         if (nargs < 2) {  
  36.             parse_error(state, "keycodes option requires atleast one keycode\n");  
  37.         } else {  
  38.             svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));  
  39.             if (!svc->keycodes) {  
  40.                 parse_error(state, "could not allocate keycodes\n");  
  41.             } else {  
  42.                 svc->nkeycodes = nargs - 1;  
  43.                 for (i = 1; i < nargs; i++) {  
  44.                     svc->keycodes[i - 1] = atoi(args[i]);  
  45.                 }  
  46.             }  
  47.         }  
  48.         break;  
  49.         … …  
  50.      }  
  51.     ……  
  52. }  

      Action的处理方式与service类似,读者可以自行查看相应的函数代码。现在一切都清楚了。处理service的函数是parse_line_service,处理action的函数是parse_line_action。而前面的state.parse_line根据当前是service还是action,指向这两个处理函数中的一个,并执行相应的函数处理actioncommand和serviceoption。

     综合上述,实际上分析init.rc文件的过程就是通过一系列地处理,最终转换为通过parse_line_service或parse_line_action函数分析Init.rc文件中每一行的行为。


你可能感兴趣的:(linux启动分析)