- u-boot-1.1.6
-
- u-boot配制分析:
-
- make xxx_config ---->
-
-
- @$(MKCONFIG) --->执行当前目录下的mkconfig
-
-
- [ $# -lt 4 ] && exit 1
- [ $# -gt 6 ] && exit 1
-
- cd ./include
- rm -f asm
- ln -s asm-$2 asm = ln -s asm-arm arm
-
- echo "ARCH = $2" > config.mk
- echo "CPU = $3" >> config.mk
- echo "BOARD = $4" >> config.mk
-
- u-boot启动分析:
-
- .globl _start
- _start: b reset --->
- reset:
- mrs r0,cpsr
- bic r0,r0,#0x1f
- orr r0,r0,#0xd3
- msr cpsr,r0
-
- ldr r0, =pWTCON
- mov r1, #0x0
- str r1, [r0]
-
- mov r1, #0xffffffff
- ldr r0, =INTMSK
- str r1, [r0]
- #if defined(CONFIG_S3C2410)
- ldr r1, =0x3ff
- ldr r0, =INTSUBMSK
- str r1, [r0]
- #endif //关中断
- ........
-
- ldr pc, _start_armboot
- _start_armboot: .word start_armboot --->
-
-
- start_armboot(); --->
-
- for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
-
-
- for (;;) {
- main_loop (); --->
- }
-
-
- main_loop(); --->
- s = getenv ("bootcmd");
- run_command (s, 0); --->
- parse_line();
- find_cmd();
-
- if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0);
-
- #define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
- #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
- cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
- |
- |
- | __u_boot_cmd_start = .;
- ---------------> .u_boot_cmd : { *(.u_boot_cmd) }
- __u_boot_cmd_end = .;
-
-
- U_BOOT_CMD(nboot, 4, 1, do_nandboot,
- "nboot - boot from NAND device\n",
- "[partition] | [[[loadAddr] dev] offset]\n"); --->
- do_nandboot();
-
-
- U_BOOT_CMD(bootm, CFG_MAXARGS,1,do_bootm,"XXXX"); --->
- do_bootm(); -->
- do_bootm_linux(); --->
-
- setup_start_tag (bd);
- setup_serial_tag (¶ms);
- setup_revision_tag (¶ms);
- setup_memory_tags (bd);
- setup_commandline_tag (bd, commandline);
- setup_videolfb_tag ((gd_t *) gd);
- setup_end_tag (bd);
-
- ----> theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
- theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
-
-
-
- linux-2.6.32
-
- (1),配制内核,make xxx_config 即/arch/arm/configs/XXX_config 生成 .config
- .config生成|(1),include/config/auto.conf --->
- |(2),include/linux/autoconf.h --->
- (2),make uImage
- 顶Makefile里包含了/arch/arm/Makefile(include $(srctree)/arch/$(SRCARCH)/Makefile)
-
- # Convert bzImage to zImage
- bzImage: zImage
- zImage Image xipImage bootpImage uImage: vmlinux
- $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
-
- (3),make
- 当make时,
- all: vmlinux
-
-
- vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
-
- SRCARCH := $(ARCH)
- ARCH ?= arm
- ①----->vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
- = arch/arm/kernel/vmlinux.lds
-
-
- head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
- = arch/arm/kernel/head.o arch/arm/kernel/init_task.o $(MMUEXT)为空
-
- init-y := init/ (init/下的全部文件)
- ②----->vmlinux-init := $(head-y) $(init-y)
-
- core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
- core-y := $(patsubst %/, %/built-in.o, $(core-y))
- = core/built-in.o 即core/目录下的文件全部编译为built-in.o
- drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
- = drivers/built-in.o
- net-y := $(patsubst %/, %/built-in.o, $(net-y))
- = net/built-in.o
- ③----->vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 如上面可知,/arch/arm/kernel/head.S文件是内核启动的第一个文件
-
- ENTRY(stext)
- setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 确保进入管理(SVC)模式
- @ 禁止中断
- mrc p15, 0, r9, c0, c0 @ 读取CPU ID,存入R9寄存器
- bl __lookup_processor_type @ r5=返回值 r9=输入参数,调用函数
- movs r10, r5 @ 如果不支持当前CPU,返回R5=0
- beq __error_p @ 如果R5=0, 则打印错误
- bl __lookup_machine_type @ 调用函数,返回值r5=machinfo
- movs r8, r5 @ 如果不支持当前开发板,返回r5=0
- beq __error_a @ 如果R5=0,则打印错误
- bl __vet_atags
- bl __create_page_tables
- __switch_data -----> __mmap_switched -----> start_kernel
-
-
-
-
- __lookup_processor_type:
-
- __proc_info_begin = .; .section ".proc.info.init", #alloc, #execinstr
- *(.proc.info.init) -----> .type __arm920_proc_info,#object
- __proc_info_end = .; ------ __arm920_proc_info:
- | .long 0x41009200
- | .long 0xff00fff0
- | .....
- |
- | struct proc_info_list {
- | unsigned int cpu_val;
- | unsigned int cpu_mask;
- | unsigned long __cpu_mm_mmu_flags;
- | unsigned long __cpu_io_mmu_flags;
- | unsigned long __cpu_flush;
- | const char *arch_name;
- |------> const char *elf_name;
- unsigned int elf_hwcap;
- const char *cpu_name;
- struct processor *proc;
- struct cpu_tlb_fns *tlb;
- struct cpu_user_fns *user;
- struct cpu_cache_fns *cache;
- };
-
- __lookup_machine_type:
-
- __arch_info_begin = .; #define MACHINE_START(_type,_name) \
- *(.arch.info.init) -------> static const struct machine_desc __mach_desc_##_type \
- __arch_info_end = .; __used \
- -------- __attribute__((__section__(".arch.info.init"))) = { \
- | .nr = MACH_TYPE_##_type, \
- | .name = _name,
- | #define MACHINE_END \
- | };
- |----|
- |
-
- MACHINE_START(SMDK2410, "SMDK2410") static const struct machine_desc __mach_desc_SMDK2410
- __used
- __attribute__((__section__(".arch.info.init"))) = {
- .nr = MACH_TYPE_SMDK2410,
- .phys_io = S3C2410_PA_UART, -------> .name = "SMDK2410",
- .io_pg_offst = (((u32)S3C24XX_VA_UART) .......
- >> 18) & 0xfffc, .......
- .boot_params = S3C2410_SDRAM_PA + 0x100, };
- .map_io = smdk2410_map_io,
- .init_irq = s3c24xx_init_irq,
- .init_machine = smdk2410_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
-
-
-
-
-
-
- start_kernel --->
- printk(KERN_NOTICE "%s", linux_banner);
- setup_arch(&command_line); --->
-
- setup_processor(); --->
-
- lookup_processor_type();--->
- __lookup_processor_type;
- setup_machine(); --->
-
- lookup_machine_type(); --->
- __lookup_machine_type;
- if (mdesc->boot_params){
-
- tags = phys_to_virt(mdesc->boot_params);
- }
-
- parse_tags(tags); --->
- parse_tag(t); --->
-
- t->parse(tag); --->
-
- __tagtable(BP_TAG_MEMORY, parse_tag_mem);
- __tagtable(BP_TAG_COMMAND_LINE, parse_tag_cmdline);
- .....
- parse_cmdline(cmdline_p, from);
- paging_init(struct machine_desc *mdesc); --->
- devicemaps_init(struct machine_desc *mdesc); --->
- if (mdesc->map_io){
- mdesc->map_io();--->
-
- smdk2410_map_io();
- }
-
- parse_early_param();
- parse_args(); --->
- parse_one() --->
- unknown_bootoption(); --->
- obsolete_checksetup(); --->
- p->setup_func(line + n); --->
- __setup(str, fn);
-
-
- console_init(); --->
- console_initcall();
-
- rest_init(); --->
- kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); --->
-
- kernel_init(); --->
- init_post(); --->
-
- sys_open((const char __user *) "/dev/console", O_RDWR, 0);
- (void) sys_dup(0);
- (void) sys_dup(0);
- run_init_process("/XXX/init");
-
- kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
-
-
u-boot-1.1.6
u-boot配制分析:
//Makefile
make xxx_config ---->
/*sbc2410x_config: unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t sbc2410x NULL s3c24x0*/
@$(MKCONFIG) --->执行当前目录下的mkconfig
//mkconfig
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1 //判断传入的参数的个数是否正确
cd ./include
rm -f asm
ln -s asm-$2 asm = ln -s asm-arm arm //建立软链接
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk //将配制文件写入config.mk中
u-boot启动分析:
//cpu/arm920t/start.S
.globl _start
_start: b reset --->
reset:
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0 //设置CPU为管理模式
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关看门狗
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
#if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
#endif //关中断
........
ldr pc, _start_armboot
_start_armboot: .word start_armboot ---> //跳入C程序入口
//lib_arm/board.c
start_armboot(); --->
//函数指针,进行一系列的初始化
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
//进入死循环
for (;;) {
main_loop (); --->
}
//common/main.c
main_loop(); --->
s = getenv ("bootcmd");
run_command (s, 0); --->
parse_line();
find_cmd();
//函数调用,执行相应的命令
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0);
//调用的函数为宏所定义的
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
|
| //board/smdk2410/u-boot.lds
| __u_boot_cmd_start = .;
---------------> .u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
//如
U_BOOT_CMD(nboot, 4, 1, do_nandboot,
"nboot - boot from NAND device\n",
"[partition] | [[[loadAddr] dev] offset]\n"); --->
do_nandboot();//做一些事
//启动 /command/cmd_bootm.c
U_BOOT_CMD(bootm, CFG_MAXARGS,1,do_bootm,"XXXX"); --->
do_bootm(); -->
do_bootm_linux(); --->
/*把参数保存到内存中*/
setup_start_tag (bd);
setup_serial_tag (¶ms);
setup_revision_tag (¶ms);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_videolfb_tag ((gd_t *) gd);
setup_end_tag (bd);
----> theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); //内核起动
/******************************************************************************************************************/
linux-2.6.32
//编译内核时的步骤:
(1),配制内核,make xxx_config 即/arch/arm/configs/XXX_config 生成 .config
.config生成|(1),include/config/auto.conf ---> //给子目录的Makefile用
|(2),include/linux/autoconf.h ---> //给C代码用
(2),make uImage
顶Makefile里包含了/arch/arm/Makefile(include $(srctree)/arch/$(SRCARCH)/Makefile)
///arch/arm/Makefile
# Convert bzImage to zImage
bzImage: zImage
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
(3),make
当make时,
all: vmlinux
//把这些文件编译成vmlinux.o
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
SRCARCH := $(ARCH)
ARCH ?= arm
①----->vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
= arch/arm/kernel/vmlinux.lds
//在/arch/arm/Makefile里定义
head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
= arch/arm/kernel/head.o arch/arm/kernel/init_task.o $(MMUEXT)为空
//在顶层目录的Makefile
init-y := init/ (init/下的全部文件)
②----->vmlinux-init := $(head-y) $(init-y)
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y := $(patsubst %/, %/built-in.o, $(core-y))
= core/built-in.o 即core/目录下的文件全部编译为built-in.o
drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))
= drivers/built-in.o
net-y := $(patsubst %/, %/built-in.o, $(net-y))
= net/built-in.o
③----->vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
/*Makefile的结果总结:
(1)配置文件.config中定义了一系列的变量,Makefile将结合它们来决定哪些文件被编进内核,哪些
文件被编成模块。涉及哪些文件目录
(2)顶层Makefile和arch/$(ARCH)/Makefile决定根目录下哪些子目录,arch/$(ARCH)目录下哪些文件和
目录将被编进内核
(3)最后,各级子目录下的Makefile决定所在目录下哪些文件将被编进内核,哪些文件将被编成模块(驱动)
进入哪些子目录继续调用它们的Makefile
(4)顶层Makefile和arch/$(ARCH)/Makefile设置了可以影响所有文件的编译,连接选项:CFLAGS. AFLAGS...
(5)顶层Makefile按照一定的顺序组织文件,根据连接脚本arch/$(ARCH)/kernel/vmlinux.lds生成内核映像
文件vmlinux
*/
//内核启动流程:
如上面可知,/arch/arm/kernel/head.S文件是内核启动的第一个文件
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 确保进入管理(SVC)模式
@ 禁止中断
mrc p15, 0, r9, c0, c0 @ 读取CPU ID,存入R9寄存器
bl __lookup_processor_type @ r5=返回值 r9=输入参数,调用函数
movs r10, r5 @ 如果不支持当前CPU,返回R5=0
beq __error_p @ 如果R5=0, 则打印错误
bl __lookup_machine_type @ 调用函数,返回值r5=machinfo
movs r8, r5 @ 如果不支持当前开发板,返回r5=0
beq __error_a @ 如果R5=0,则打印错误
bl __vet_atags
bl __create_page_tables //arch/arm/kernel/head-common.S
__switch_data -----> __mmap_switched -----> start_kernel //起动内核
//__lookup_processor_type,__lookup_machine_type 函数在arch/arm/kernel/head-common.S中定义
__lookup_processor_type:
//arch\arm\kernel\vmlinux-lds.S arch\arm\kernel\proc-arm920.S
__proc_info_begin = .; .section ".proc.info.init", #alloc, #execinstr
*(.proc.info.init) -----> .type __arm920_proc_info,#object
__proc_info_end = .; ------ __arm920_proc_info:
| .long 0x41009200 //CPU id,cpu_val
| .long 0xff00fff0 //cpu_mask
| .....
| //arch/arm/include/asm/procinfo.h
| struct proc_info_list {
| unsigned int cpu_val;
| unsigned int cpu_mask;
| unsigned long __cpu_mm_mmu_flags; /* used by head.S */
| unsigned long __cpu_io_mmu_flags; /* used by head.S */
| unsigned long __cpu_flush; /* used by head.S */
| const char *arch_name;
|------> const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
__lookup_machine_type:
//arch\arm\kernel\vmlinux-lds.S arch/arm/include/asm/mach/arch.h
__arch_info_begin = .; #define MACHINE_START(_type,_name) \
*(.arch.info.init) -------> static const struct machine_desc __mach_desc_##_type \
__arch_info_end = .; __used \
-------- __attribute__((__section__(".arch.info.init"))) = { \
| .nr = MACH_TYPE_##_type, \
| .name = _name,
| #define MACHINE_END \
| };
|----|
|
//linux/arch/arm/mach-s3c2410/mach-smdk2410.c
MACHINE_START(SMDK2410, "SMDK2410") static const struct machine_desc __mach_desc_SMDK2410
/* @TODO: request a new identifier and switch*/ __used
/* to SMDK2410 */ __attribute__((__section__(".arch.info.init"))) = {
/* Maintainer: Jonas Dietsche */ .nr = MACH_TYPE_SMDK2410,
.phys_io = S3C2410_PA_UART, -------> .name = "SMDK2410",
.io_pg_offst = (((u32)S3C24XX_VA_UART) .......
>> 18) & 0xfffc, .......
.boot_params = S3C2410_SDRAM_PA + 0x100, };
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
//内核C程序起动分析:
//在汇编程序/arch/arm/kernel/head-common.S中调用 start_kernel。
//init/main.c
start_kernel --->
printk(KERN_NOTICE "%s", linux_banner);//打印内核版本信息
setup_arch(&command_line); --->
//进行处理器相关的一些设置
setup_processor(); --->
//目的是获得该处理器的proc_info_list结构
lookup_processor_type();--->
__lookup_processor_type;//arch/arm/kernel/head-common.S
setup_machine(); --->
//获得开发板的machine_desc结构
lookup_machine_type(); --->
__lookup_machine_type;//arch/arm/kernel/head-common.S
if (mdesc->boot_params){//用来确定bootloader传入的启动参数的地址
//mach-smdk2410.c ---> .boot_params = S3C2410_SDRAM_PA + 0x100 = 0x30000100
tags = phys_to_virt(mdesc->boot_params);
}
//解释每一个tags
parse_tags(tags); --->
parse_tag(t); --->
//处理每一个tag,调用每一种处理函数
t->parse(tag); --->
//在/arch/arm/kernel/setup.c里定义
__tagtable(BP_TAG_MEMORY, parse_tag_mem);
__tagtable(BP_TAG_COMMAND_LINE, parse_tag_cmdline);
.....
parse_cmdline(cmdline_p, from); //对命令行进行一些先期的处理
paging_init(struct machine_desc *mdesc); --->//重新初始化页表
devicemaps_init(struct machine_desc *mdesc); --->
if (mdesc->map_io){
mdesc->map_io();--->
//arch/arm/mach-s3c2410/mach-smdk2410.c
smdk2410_map_io();//设备串口相关的东西
}
parse_early_param();
parse_args(); --->
parse_one() --->
unknown_bootoption(); --->
obsolete_checksetup(); --->
p->setup_func(line + n); --->
__setup(str, fn);//include/init/init.h
console_init(); --->
console_initcall();
rest_init(); --->
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); --->
//继续初始化内核,并创建init进程
kernel_init(); --->
init_post(); --->
//打开控制终端
sys_open((const char __user *) "/dev/console", O_RDWR, 0);
(void) sys_dup(0);
(void) sys_dup(0);
run_init_process("/XXX/init"); //开跑第一个应用程序
kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
/***************************************************************************************************************/
第一个应用程序:
//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 ?
- 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;
-
- if (!strcmp(basename(argv[0]), "ueventd"))
- return ueventd_main(argc, argv);
-
-
- umask(0);
-
-
-
-
-
-
- mkdir("/dev", 0755);
- mkdir("/proc", 0755);
- mkdir("/sys", 0755);
-
- mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
- mkdir("/dev/pts", 0755);
- mkdir("/dev/socket", 0755);
- mount("devpts", "/dev/pts", "devpts", 0, NULL);
- mount("proc", "/proc", "proc", 0, NULL);
- mount("sysfs", "/sys", "sysfs", 0, NULL);
-
-
-
-
-
-
-
- open_devnull_stdio();
- log_init();
-
- INFO("reading config file\n");
- init_parse_config_file("/init.rc");
-
-
- import_kernel_cmdline(0);
-
- get_hardware_name(hardware, &revision);
- snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
- init_parse_config_file(tmp);
-
- action_for_each_trigger("early-init", action_add_queue_tail);
-
- queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
- queue_builtin_action(property_init_action, "property_init");
- queue_builtin_action(keychord_init_action, "keychord_init");
- queue_builtin_action(console_init_action, "console_init");
- queue_builtin_action(set_init_properties_action, "set_init_properties");
-
-
- action_for_each_trigger("init", action_add_queue_tail);
- action_for_each_trigger("early-fs", action_add_queue_tail);
- action_for_each_trigger("fs", action_add_queue_tail);
- action_for_each_trigger("post-fs", action_add_queue_tail);
-
- queue_builtin_action(property_service_init_action, "property_service_init");
- queue_builtin_action(signal_init_action, "signal_init");
- queue_builtin_action(check_startup_action, "check_startup");
-
-
- action_for_each_trigger("early-boot", action_add_queue_tail);
- action_for_each_trigger("boot", action_add_queue_tail);
-
-
- queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
-
-
- #if BOOTCHART
- queue_builtin_action(bootchart_init_action, "bootchart_init");
- #endif
-
-
- for(;;) {
- int nr, i, timeout = -1;
-
-
- execute_one_command();
- restart_processes();
-
- if (!property_set_fd_init && get_property_set_fd() > 0) {
- ufds[fd_count].fd = get_property_set_fd();
- ufds[fd_count].events = POLLIN;
- ufds[fd_count].revents = 0;
- fd_count++;
- property_set_fd_init = 1;
- }
- if (!signal_fd_init && get_signal_fd() > 0) {
- ufds[fd_count].fd = get_signal_fd();
- ufds[fd_count].events = POLLIN;
- ufds[fd_count].revents = 0;
- fd_count++;
- signal_fd_init = 1;
- }
- if (!keychord_fd_init && get_keychord_fd() > 0) {
- ufds[fd_count].fd = get_keychord_fd();
- ufds[fd_count].events = POLLIN;
- ufds[fd_count].revents = 0;
- fd_count++;
- keychord_fd_init = 1;
- }
-
- if (process_needs_restart) {
- timeout = (process_needs_restart - gettime()) * 1000;
- if (timeout < 0)
- timeout = 0;
- }
-
- if (!action_queue_empty() || cur_action)
- timeout = 0;
-
- #if BOOTCHART
- if (bootchart_count > 0) {
- if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
- timeout = BOOTCHART_POLLING_MS;
- if (bootchart_step() < 0 || --bootchart_count == 0) {
- bootchart_finish();
- bootchart_count = 0;
- }
- }
- #endif
-
-
- nr = poll(ufds, fd_count, timeout);
- if (nr <= 0)
- continue;
-
- for (i = 0; i < fd_count; i++) {
- if (ufds[i].revents == POLLIN) {
- if (ufds[i].fd == get_property_set_fd())
- handle_property_set_fd();
- else if (ufds[i].fd == get_keychord_fd())
- handle_keychord();
- else if (ufds[i].fd == get_signal_fd())
- handle_signal();
- }
- }
- }
-
- return 0;
- }
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;
if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv);
/* clear the umask */
umask(0);
/* Get the basic filesystem setup we need put
* together in the initramdisk on / and then we'll
* let the rc file figure out the rest.
* 下面的代码开始建立各种用户空间的目录,如/dev、/proc、/sys等
*/
mkdir("/dev", 0755);
mkdir("/proc", 0755);
mkdir("/sys", 0755);
mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
/* We must have some place other than / to create the
* device nodes for kmsg and null, otherwise we won't
* be able to remount / read-only later on.
* Now that tmpfs is mounted on /dev, we can actually
* talk to the outside world.
*/
open_devnull_stdio();
log_init();
INFO("reading config file\n");// 分析/init.rc文件的内容
init_parse_config_file("/init.rc");
/* pull the kernel commandline and ramdisk properties file in */
import_kernel_cmdline(0);
get_hardware_name(hardware, &revision);
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);
action_for_each_trigger("early-init", action_add_queue_tail);
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
action_for_each_trigger("early-fs", action_add_queue_tail);
action_for_each_trigger("fs", action_add_queue_tail);
action_for_each_trigger("post-fs", action_add_queue_tail);
queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");
/* execute all the boot actions to get us started */
action_for_each_trigger("early-boot", action_add_queue_tail);
action_for_each_trigger("boot", action_add_queue_tail);
/* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
// 进入无限循环,建立init的子进程(init是所有进程的父进程)
for(;;) {
int nr, i, timeout = -1;
// 执行命令(子进程对应的命令)
execute_one_command();
restart_processes();
if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
signal_fd_init = 1;
}
if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
keychord_fd_init = 1;
}
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action)
timeout = 0;
#if BOOTCHART
if (bootchart_count > 0) {
if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)
timeout = BOOTCHART_POLLING_MS;
if (bootchart_step() < 0 || --bootchart_count == 0) {
bootchart_finish();
bootchart_count = 0;
}
}
#endif
// 等待下一个命令的提交
nr = poll(ufds, fd_count, timeout);
if (nr <= 0)
continue;
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents == POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return 0;
}
我们可以看到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 ?
- void get_hardware_name(char *hardware, unsigned int *revision)
- {
- char data[1024];
- int fd, n;
- char *x, *hw, *rev;
-
-
-
- if (hardware[0])
- return;
-
-
- fd = open("/proc/cpuinfo", O_RDONLY);
- if (fd < 0) return;
-
-
- n = read(fd, data, 1023);
- close(fd);
- if (n < 0) return;
-
-
- data[n] = 0;
- hw = strstr(data, "\nHardware");
- rev = strstr(data, "\nRevision");
-
- if (hw) {
- x = strstr(hw, ": ");
- if (x) {
- x += 2;
- n = 0;
- while (*x && !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);
- }
- }
- }
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 */
/* 如果hardware已经有值了,说明hardware通过内核命令行提供,直接返回 */
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;
// 从/proc/cpuinfo文件中获取Hardware字段的值
data[n] = 0;
hw = strstr(data, "\nHardware");
rev = strstr(data, "\nRevision");
if (hw) {// 成功获取Hardware字段的值
x = strstr(hw, ": ");
if (x) {
x += 2;
n = 0;
while (*x && !isspace(*x)) {
hardware[n++] = tolower(*x); // 将Hardware字段的值都转换为小写,并更新hardware参数的值
// hardware也就是在init.c文件中定义的hardware数组
x++;
if (n == 31) break;
}
hardware[n] = 0;
}
}
if (rev) {
x = strstr(rev, ": ");
if (x) {
*revision = strtoul(x + 2, 0, 16);
}
}
}
从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的语法格式如下:
- on <trigger>
- <command>
- <command>
- <command>
也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action
- on boot
- ifup lo
- hostname localhost
- domainname localdomain
其中boot是触发器,下面三行是command
那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。
1. boot
这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger
2. <name>=<value>
当属性<name>被设置成<value>时被触发。例如,
on property:vold.decrypt=trigger_reset_main
class_reset main
3. device-added-<path>
当设备节点被添加时触发
4. device-removed-<path>
当设备节点被移除时添加
5. service-exited-<name>
会在一个特定的服务退出时触发
Actions后需要跟若干个命令,这些命令如下:
1. exec <path> [<argument> ]*
创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。
2. export <name> <value>
在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)
3. ifup <interface>
启动网络接口
4. import <filename>
指定要解析的其他配置文件。常被用于当前配置文件的扩展
5. hostname <name>
设置主机名
6. chdir <directory>
改变工作目录
7. chmod <octal-mode><path>
改变文件的访问权限
8. chown <owner><group> <path>
更改文件的所有者和组
9. chroot <directory>
改变处理根目录
10. class_start<serviceclass>
启动所有指定服务类下的未运行服务。
11 class_stop<serviceclass>
停止指定服务类下的所有已运行的服务。
12. domainname <name>
设置域名
13. insmod <path>
加载<path>指定的驱动模块
14. mkdir <path> [mode][owner] [group]
创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。
15. mount <type> <device> <dir> [<mountoption> ]*
试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re
16. setkey
保留,暂时未用
17. setprop <name><value>
将系统属性<name>的值设为<value>。
18. setrlimit <resource> <cur> <max>
设置<resource>的rlimit (资源限制)
19. start <service>
启动指定服务(如果此服务还未运行)。
20.stop<service>
停止指定服务(如果此服务在运行中)。
21. symlink <target> <path>
创建一个指向<path>的软连接<target>。
22. sysclktz <mins_west_of_gmt>
设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)
23. trigger <event>
触发一个事件。用于Action排队
24. wait <path> [<timeout> ]
等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。
25. write <path> <string> [ <string> ]*
向<path>指定的文件写入一个或多个字符串。
Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:
- service <name> <pathname> [ <argument> ]*
- <option>
- <option>
例如,下面是一个标准的Service用法
- service servicemanager /system/bin/servicemanager
- class core
- user system
- group system
- critical
- onrestart restart zygote
- onrestart restart media
- onrestart restart surfaceflinger
- onrestart restart drm
Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:
1. critical
表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。
2. disabled
表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。
3. setenv <name><value>
在进程启动时将环境变量<name>设置为<value>。
4. socket <name><type> <perm> [ <user> [ <group> ] ]
Create a unix domain socketnamed /dev/socket/<name> and pass
its fd to the launchedprocess. <type> must be"dgram", "stream" or "seqpacket".
User and group default to0.
创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。
5. user <username>
在启动这个服务前改变该服务的用户名。此时默认为 root。
6. group <groupname> [<groupname> ]*
在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。
7. oneshot
服务退出时不重启。
8. class <name>
指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。
9. onrestart
当服务重启,执行一个命令(下详)。
现在接着分析一下init是如何解析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文件中实现,代码如下:
- int init_parse_config_file(const char *fn)
- {
- char *data;
- data = read_file(fn, 0);
- if (!data) return -1;
-
- parse_config(fn, data);
- DUMP();
- return 0;
- }
init_parse_config_file方法开始调用了read_file函数打开了/init.rc文件,并返回了文件的内容(char*类型),然后最核心的函数是parse_config。该函数也在init_parser.c文件中实现,代码如下:
- static void parse_config(const char *fn, char *s)
- {
- struct parse_state state;
- struct listnode import_list;
- struct listnode *node;
- char *args[INIT_PARSER_MAXARGS];
- int nargs;
-
- nargs = 0;
- state.filename = fn;
- state.line = 0;
- state.ptr = s;
- state.nexttoken = 0;
- state.parse_line = parse_line_no_op;
-
- list_init(&import_list);
- state.priv = &import_list;
-
-
- for (;;) {
-
- switch (next_token(&state)) {
- case T_EOF:
- state.parse_line(&state, 0, 0);
- goto parser_done;
- case T_NEWLINE:
-
- state.line++;
- if (nargs) {
- int kw = lookup_keyword(args[0]);
- if (kw_is(kw, SECTION)) {
- state.parse_line(&state, 0, 0);
- parse_new_section(&state, kw, nargs, args);
- } else {
- state.parse_line(&state, nargs, args);
- }
- nargs = 0;
- }
- break;
- case T_TEXT:
- if (nargs < INIT_PARSER_MAXARGS) {
- args[nargs++] = state.text;
- }
- break;
- }
- }
-
- parser_done:
-
- list_for_each(node, &import_list) {
- struct import *import = node_to_item(node, struct import, list);
- int ret;
-
- INFO("importing '%s'", import->filename);
-
- ret = init_parse_config_file(import->filename);
- if (ret)
- ERROR("could not import file '%s' from '%s'\n",
- import->filename, fn);
- }
- }
parse_config方法的代码就比较复杂了,现在先说说该方法的基本处理流程。首先会调用 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的。
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分支的完整代码。
- case T_NEWLINE:
- state.line++;
- if (nargs) {
- int kw = lookup_keyword(args[0]);
- if (kw_is(kw, SECTION)) {
- state.parse_line(&state, 0, 0);
- parse_new_section(&state, kw, nargs, args);
- } else {
- state.parse_line(&state, nargs, args);
- }
- nargs = 0;
- }
- break;
在上面的代码中首先调用了lookup_keyword方法搜索关键字。该方法的作用是判断当前行是否合法,也就是根据Init初始化语言预定义的关键字查询,如果未查到,返回K_UNKNOWN。lookup_keyword方法在init_parser.c文件中实现,代码如下:
- int lookup_keyword(const char *s)
- {
- switch (*s++) {
- case 'c':
- if (!strcmp(s, "opy")) return K_copy;
- if (!strcmp(s, "apability")) return K_capability;
- if (!strcmp(s, "hdir")) return K_chdir;
- if (!strcmp(s, "hroot")) return K_chroot;
- if (!strcmp(s, "lass")) return K_class;
- if (!strcmp(s, "lass_start")) return K_class_start;
- if (!strcmp(s, "lass_stop")) return K_class_stop;
- if (!strcmp(s, "lass_reset")) return K_class_reset;
- if (!strcmp(s, "onsole")) return K_console;
- if (!strcmp(s, "hown")) return K_chown;
- if (!strcmp(s, "hmod")) return K_chmod;
- if (!strcmp(s, "ritical")) return K_critical;
- break;
- case 'd':
- if (!strcmp(s, "isabled")) return K_disabled;
- if (!strcmp(s, "omainname")) return K_domainname;
- break;
- … …
- case 'o':
- if (!strcmp(s, "n")) return K_on;
- if (!strcmp(s, "neshot")) return K_oneshot;
- if (!strcmp(s, "nrestart")) return K_onrestart;
- break;
- case 'r':
- if (!strcmp(s, "estart")) return K_restart;
- if (!strcmp(s, "estorecon")) return K_restorecon;
- if (!strcmp(s, "mdir")) return K_rmdir;
- if (!strcmp(s, "m")) return K_rm;
- break;
- case 's':
- if (!strcmp(s, "eclabel")) return K_seclabel;
- if (!strcmp(s, "ervice")) return K_service;
- if (!strcmp(s, "etcon")) return K_setcon;
- if (!strcmp(s, "etenforce")) return K_setenforce;
- if (!strcmp(s, "etenv")) return K_setenv;
- if (!strcmp(s, "etkey")) return K_setkey;
- if (!strcmp(s, "etprop")) return K_setprop;
- if (!strcmp(s, "etrlimit")) return K_setrlimit;
- if (!strcmp(s, "etsebool")) return K_setsebool;
- if (!strcmp(s, "ocket")) return K_socket;
- if (!strcmp(s, "tart")) return K_start;
- if (!strcmp(s, "top")) return K_stop;
- if (!strcmp(s, "ymlink")) return K_symlink;
- if (!strcmp(s, "ysclktz")) return K_sysclktz;
- break;
- case 't':
- if (!strcmp(s, "rigger")) return K_trigger;
- break;
- case 'u':
- if (!strcmp(s, "ser")) return K_user;
- break;
- case 'w':
- if (!strcmp(s, "rite")) return K_write;
- if (!strcmp(s, "ait")) return K_wait;
- break;
- }
- return K_UNKNOWN;
- }
lookup_keyword方法按26个字母顺序(关键字首字母)进行处理。
现在回到parse_config方法的T_NEWLIEN分支,接下来调用了kw_is宏具体判断当前行是否合法,该宏以及SECTION宏的定义如下。根据这些代码。明显是keyword_info数组中的某个元素的flags成员变量的值取最后一位。
- #define SECTION 0x01
- #define kw_is(kw, type) (keyword_info[kw].flags & (type))
现在问题又转到keyword_info数组了。该数组也在init_parser.c文件中定义,代码如下:
- #include "keywords.h"
- #define KEYWORD(symbol, flags, nargs, func) \
- [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
- struct {
- const char *name;
- int (*func)(int nargs, char **args);
- unsigned char nargs;
- unsigned char flags;
- } keyword_info[KEYWORD_COUNT] = {
- [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
- #include "keywords.h"
- };
从表面上看,keyword_info数组是一个struct数组,但本质上,是一个map。为每一个数组元素设置了一个key,例如,数组元素{ "unknown", 0, 0,0 }的key是K_UNKNOWN,而#include “keywords.h”大有玄机。上面的代码中引用了两次keywords.h文件,现在可以看一下keywords.h文件的代码。
- #ifndef KEYWORD
- int do_chroot(int nargs, char **args);
- … …
- int do_export(int nargs, char **args);
- int do_hostname(int nargs, char **args);
- int do_rmdir(int nargs, char **args);
- int do_loglevel(int nargs, char **args);
- int do_load_persist_props(int nargs, char **args);
- int do_wait(int nargs, char **args);
- #define __MAKE_KEYWORD_ENUM__
-
-
-
- #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(load_persist_props, COMMAND, 0, do_load_persist_props)
- KEYWORD(ioprio, OPTION, 0, 0)
- #ifdef __MAKE_KEYWORD_ENUM__
- KEYWORD_COUNT,
- };
- #undef __MAKE_KEYWORD_ENUM__
- #undef KEYWORD
- #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文件使用的是如下的宏。
- #define KEYWORD(symbol, flags, nargs, func) \
- [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
这下就明白了,如果不使用keywords.h文件,直接将所有的代码都写到init_parser.c文件中,就会有下面的代码。
- int do_chroot(int nargs, char **args);
- … …
- enum
- {
- K_UNKNOWN,
- K_ capability,
- K_ chdir,
- … …
- }
- #define KEYWORD(symbol, flags, nargs, func) \
- [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
- struct {
- const char *name;
- int (*func)(int nargs, char **args);
- unsigned char nargs;
- unsigned char flags;
- } keyword_info[KEYWORD_COUNT] = {
- [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
- [K_ capability] = {" capability ", 0, 1, OPTION },
- [K_ chdir] = {"chdir", do_chdir ,2, COMMAND},
- … …
- #include "keywords.h"
- };
可能我们还记着lookup_keyword方法,该方法的返回值就是keyword_info数组的key。
在keywords.h前面定义的函数指针都是处理init.rc文件中service、action和command的。现在就剩下一个问题了,在哪里为这些函数指针赋值呢,也就是说,具体处理每个部分的函数在哪里呢。现在回到前面的语法分析部分。如果当前行合法,则会执行parse_new_section函数(在init_parser.c文件中实现),该函数将为section和action设置处理这两部分的函数。parse_new_section函数的代码如下:
- void parse_new_section(struct parse_state *state, int kw,
- int nargs, char **args)
- {
- printf("[ %s %s ]\n", args[0],
- nargs > 1 ? args[1] : "");
- switch(kw) {
- case K_service:
- state->context = parse_service(state, nargs, args);
- if (state->context) {
- state->parse_line = parse_line_service;
- return;
- }
- break;
- case K_on:
- state->context = parse_action(state, nargs, args);
- if (state->context) {
- state->parse_line = parse_line_action;
- return;
- }
- break;
- case K_import:
- parse_import(state, nargs, args);
- break;
- }
- state->parse_line = parse_line_no_op;
- }
现在看一下处理service的函数(parse_line_service)。
- static void parse_line_service(struct parse_state *state, int nargs, char **args)
- {
- struct service *svc = state->context;
- struct command *cmd;
- int i, kw, kw_nargs;
-
- if (nargs == 0) {
- return;
- }
-
- svc->ioprio_class = IoSchedClass_NONE;
-
- kw = lookup_keyword(args[0]);
-
- switch (kw) {
- case K_capability:
- break;
- … …
- case K_group:
- if (nargs < 2) {
- parse_error(state, "group option requires a group id\n");
- } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
- parse_error(state, "group option accepts at most %d supp. groups\n",
- NR_SVC_SUPP_GIDS);
- } else {
- int n;
- svc->gid = decode_uid(args[1]);
- for (n = 2; n < nargs; n++) {
- svc->supp_gids[n-2] = decode_uid(args[n]);
- }
- svc->nr_supp_gids = n - 2;
- }
- break;
- case K_keycodes:
- if (nargs < 2) {
- parse_error(state, "keycodes option requires atleast one keycode\n");
- } else {
- svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
- if (!svc->keycodes) {
- parse_error(state, "could not allocate keycodes\n");
- } else {
- svc->nkeycodes = nargs - 1;
- for (i = 1; i < nargs; i++) {
- svc->keycodes[i - 1] = atoi(args[i]);
- }
- }
- }
- break;
- … …
- }
- ……
- }
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文件中每一行的行为。