内核的配置和编译及代码分析(一)

关于内核的配置和编译和uboot是一样的
1 解压缩
2 打patch
3 配置内核
4 编译
配置内核有3种方法:
1 make menuconfig这样就需要配置所有的配置项
2 使用默认的配置,在其上面做修改。在arch/arm/configs下有很多默认的配置,XXX_defconfig,可以根据你板子所
  使用的芯片来选择以下相似的配置,比如make s3c2410_defconfig,之后再make menuconfig,make menuconfig是需要顶
  层目录下有一个.config文件
3 使用厂家提供的 比如: 厂家_config


 注意:我们一般会选用 make XXX_defconfig 然后再make menuconfig。make XXX_defconfig之后会在源码树下生成.config文件,(其实,我们也不需要执行该条命令,可以这样做,直接执行 cp /arch/arm/configs/s3c2410_defconfig  .config  )都是对配置项的编译与否。以一个配置项CONFIG_DM9000网卡为例,这个配置项有三种选择,CONFIG_DM9000=y,CONFIG_DM9000=m,CONFIG_DM9000=空,为y说明将这个网卡编译进内核,为m说明将这个网卡编译成模块,为空说明内核不支持该网卡。
 之后我们再make menuconfig会生成两个很重要的文件,include/linux/autoconf.h和include/config/auto.conf 这两个
 文件都是来源于.config文件,autoconf.h文件里此时CONFIG_DM9000=1,也即只要是在.config文件里无论编译选项
 是m或者y的,都#define CONFIG_DM9000 1,autoconf.h文件会被c语言源码用到该宏。
 auto.conf文件里此时CONFIG_DM9000 = y(如果你在.config文件里是y),
 该文件会被顶层makefile所包含(-include include/config/auto.conf)主要是用于子目录makefile所链接需要,
 如obj-$(CONFIG_DM9000) += xxx;生成的auto.conf文件与.config文件有点类似。当然,对伊厂家提供的 厂家_config,
 可以直接把它变成.config文件,即cp 厂家_config  .config,然后make menuconfig


 当我们编译的时候用make 或者make uImage ?这时候我们就需要分析makefile文件了,非常重要的两个makefile文件,
 一个是顶层makefile,一个是arch/arm/makefile当我们编译的时候如果用make的话,则会生成vmlinux,这个是真正的内核。
 而make uImage,会生成uImage,是 uboot能够识别和解析的内核:也即 头部+真正的内核(vmlinux).我们在顶层makefile中
 是搜索不到uImage的,可以搜到vmlinux,但是在arch/arm/makefile下可以搜到uImage的。所以顶层makefile一定是会
 包含arch/arm/makefile的,如include $(srctree)/arch/$(SRCARCH)/Makefile  我们在搜索 SRCARCH := $(ARCH)  
 当然 我们可以在这写死 ARCH = arm;否则,你就要写 make uImage ARCH=arm,意思是使用arch/arm下的makefile,


另外你同样也要配置编译器连接器。。。,同样写死CROSS_COMPILE = arm-linux- ;因为后面都会用到
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump


如果你不写死,那么你编译的时候就需要 “make uImage ARCH = arm CROSS_COMPILE=arm-linux-” 所以,你打开顶层makefile
的时候一般需要将这写死,ARCH ?= $(SUBARCH)CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) 
改成 ARCH = arm  CROSS_COMPILE = arm-linux-
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC 这样子目录下的makefile就可以用了
    在顶层makefile里搜索all:,出现all: vmlinux,这个是默认下的内核,也是真正的内核,当仅仅用make编译的时候。
    我们在搜vmlinux:,出现vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_OBJS := $(vmlinux-all)


  在这里我们先分析下 vmlinux-init := $(head-y) $(init-y)  head-y:是在arch/arm/makefile里定义的 
  head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o这几个是最开始调用的文件。 
  init-y := init/, init-y:= $(patsubst %/, %/built-in.o, $(init-y));patsubst是makefile的函数,是模式字符串替换
  的函数,即将init目录下的所涉及到的文件编译成 init/built-in.o 如果我们想看到具体的编译链接规则,
  可以make uImage V=1  我们现在进入arch/arm下的makefile文件 搜索uImage:  
  出现zImage Image xipImage bootpImage uImage: vmlinux。即我们的uImage是基于真正的内核vmlinux而来的,
  而vmlinux的编译是在顶层makefile写好的。会生成两个文件,vmlinux和uImage vmlinux是在顶层目录下 。
  最终生成的uImage是在arch/arm/boot目录下


   顶层makefile决定了内核跟目录下哪些子目录被编译进内核,arch/arm/makefile决定了arch/arm目录下哪些文件或者
   目录被编译进内核了,各级子目录下的makefile决定所在目录下哪些文件将被编译进内核,哪些文件将被编译成模块,
   进入哪些子目录继续调用他们的makefile。
   
  第一种情况下的makefile(顶层makefile)
   由上:
   vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
export KBUILD_VMLINUX_OBJS := $(vmlinux-all)


head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o //在arch/arm/makefile 里定义
init-y := init/  init-y:= $(patsubst %/, %/built-in.o, $(init-y));
drivers-y := drivers/ sound/ firmware/           drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y))      
net-y := net/                                  net-y := $(patsubst %/, %/built-in.o, $(net-y))
libs-y := lib/                                libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))       libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
core-y := usr/
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/    core-y := $(patsubst %/, %/built-in.o, $(core-y))






可见顶层makefile将源码树下这14个目录分成5类 init-y, drivers-y,net-y,libs-y,core-y 
只有include目录,documention目录,scripts目录,没有被编译进内核,因为他们不含内核源码,
arch下各相关的架构的makefile也被编译进内核了因为arch/arm下的makefile已经被包含进了顶层makefile  
include $(srctree)/arch/$(SRCARCH)/Makefile


所以编译内核的时候一次进入init-y, drivers-y,net-y,libs-y,core-y所列出来的目录执行他们目录下的makefile,
每个子目录下都会生成built-in.o文件,lib下会生成built-in.o和lib.a两个,真正的内核vmlinux就是将这些各子目录
下的built.o和lib.a文件链接生成的


   对于第二种情况下的makefile(arch/arm/makefile决定了arch/arm目录下哪些文件或者目录被编译进内核了)就需
   要顶层makefile中包含的auto.conf文件了
   -include include/config/auto.conf    //包含了很重要的有.config而make menuconfig生成的auto.conf。 
    auto.conf文件与.config文件非常相似,
   
   
   
   
 
   
  //它只是将.config文件中的注释去掉,并根据顶层makefile中定义的变量增加一些变量而已
  该文件主要是给各个子目录下的makefile使用(第二种情况和第三种情况)
 
core-$(CONFIG_FPE_NWFPE) += arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE) += $(FASTFPE_OBJ)
core-$(CONFIG_VFP) += arch/arm/vfp/


# If we have a machine-specific directory, then include it in the build.
core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y += $(machdirs) $(platdirs)


drivers-$(CONFIG_OPROFILE)      += arch/arm/oprofile/


libs-y := arch/arm/lib/ $(libs-y)


这些配置项需要auto.conf文件,同时他们又进一步扩展了core-y,libs-y的内容


   对于第三种情况下的makefile(也即各子目录下的makefile)
   obj-y += a.o c.o  //a.c c.c文件被编译进内核,最终和当前目录下的各子目录内的built-in.o链接生成当前目录下的built-in.o文件,当前目录下的built-in.o文件又被它的上一层makefile所使用
   obj-m += b.o      // 将b.c一个文件编译成内核模块b.ko
                    // 将a.c b.c c.c三个文件编译成内核模块 yangbo.ko
   obj-m += yangbo.o
   yangbo-objs :=a.o b.o c.o
  
  
   
分析一下arch/arm下的目录结构:
boot 目录: 生成的image zimage uimage等等会放在此目录内
configs 目录: 默认的一些单板配置文件,隐形的.config文件
tools 目录: 有一个mach-types文件,很重要,定义单板的机器ID
include 目录: 头文件目录


common, kernel,mm目录:会被编译 放在core-y 这三个目录非常关键
lib目录: 库文件 被编译 放在libs-y
mach-xxx,plat-xxx目录:根据宏的定义分别从mach目录和plat目录找一个进行编译,放在core-y


nwfpe目录:根据定义宏来是否编译 若被编译,放到core-y或者core-m
vfp目录:  根据定义宏来是否编译 若被编译,放到core-y或者core-m
oprofile目录: 根据定义宏来是否编译 若被编译,放到drivers-y或者drivers-m






   KCONFIG
   


1. 依据arch/arm/kernel/vmlinux.lds 生成linux内核源码根目录下的vmlinux,这个vmlinux属于未压缩,带调试信息、
符号表的最初的内核,大小约23MB; 
arm-linux-gnu-ld -EL  -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds 


 


arch/arm/kernel/head.o  
arch/arm/kernel/init_task.o   
init/built-in.o  
--start-group   
usr/built-in.o   
arch/arm/kernel/built-in.o   
arch/arm/mm/built-in.o   
arch/arm/common/built-in.o   
arch/arm/mach-s3c2410/built-in.o   
arch/arm/nwfpe/built-in.o   
kernel/built-in.o          
mm/built-in.o   
fs/built-in.o   
ipc/built-in.o   
security/built-in.o   
crypto/built-in.o   
lib/lib.a   
arch/arm/lib/lib.a   
lib/built-in.o   
arch/arm/lib/built-in.o   
drivers/built-in.o   
sound/built-in.o   
net/built-in.o  
--end-group .tmp_kallsyms2.o 
2. 将上面的vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/Image,这是不带多余信息的linux内核,
Image的大小约3.2MB; 
命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S  vmlinux arch/arm/boot/Image 


3. 将 arch/arm/boot/Image 用gzip -9 压缩生成arch/arm/boot/compressed/piggy.gz大小约1.5MB; 
命令:gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz


4. 编译arch/arm/boot/compressed/piggy.S 生成arch/arm/boot/compressed/piggy.o大小约1.5MB,这里实际上是
将piggy.gz通过piggy.S编译进piggy.o文件中。而piggy.S文件仅有6行,只是包含了文件piggy.gz; 
 命令:arm-linux-gnu-gcc -Wp,-MD,arch/arm/boot/compressed/.piggy.o.d  -nostdinc -isystem /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/include -D__KERNEL__ -Iinclude  -mlittle-endian -D__ASSEMBLY__ -Wa,-L -gdwarf-2 -mapcs-32 -mno-thumb-interwork -D__LINUX_ARM_ARCH__=4 -march=armv4 -mtune=arm9tdmi -msoft-float    -c -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S 
 
5. 依据arch/arm/boot/compressed/vmlinux.lds 将arch/arm/boot/compressed/目录下的文件head.o 、piggy.o 、misc.o链接
生成 arch/arm/boot/compressed/vmlinux,这个vmlinux是经过压缩且含有自解压代码的内核,大小约1.5MB; 
命令:arm-linux-gnu-ld -EL   --defsym zreladdr=0x30008000 --defsym params_phys=0x30000100 -p --no-undefined -X /home/justin/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux-gnu/lib/gcc/arm-linux-gnu/3.4.5/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux 


6. 将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage大小约1.5MB;
这已经是一个可以使用的linux内核映像文件了; 
命令:arm-linux-gnu-objcopy -O binary -R .note -R .comment -S  arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage 


7. 将arch/arm/boot/zImage添加64Bytes的相关信息打包为arch/arm/boot/uImage大小约1.5MB; 
命令:/bin/sh /home/farsight/Resources/kernel/linux-2.6.14/scripts/mkuboot.sh -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n 'Linux-2.6.14' -d arch/arm/boot/zImage arch/arm/boot/uImage   在这里会用uboot工具mkimage生成uimage
  
小结:真正的生成的内核是vmlinux,但它包含调试信息,注释和各种符号表
这样,去掉这些东西就是Image,在通过各种压缩形式,有Image生成zImage
所以一旦执行make,会在顶层目录下生成vmlinux,在arch/arm/boot目录下生成image,zimage
若要生成uimage,则需要用zimage来生成 
------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 
 
 
 
 
   
linux内核的启动过程分析
也分为两个阶段,第一个阶段是汇编写的,第二阶段是c语言写的
先分析第一阶段 在arch/arm/kernel/head.S


  ENTRY(stext)
     msr cpsr_c, #PSR_F_BIT|PSR_I_BIT|SVC_MODE   //设置为svc管理模式,静止中断
     mrc p15, 0, r9,c0,c0   //读取协处理器cp15的寄存器c0获得CPUID,存放在r9寄存器中注意,这里的cpu主要是
      只cpu核,如arm920t,arm11eb,x86等等     r5寄存器返回一个用来描述这个处理器的结构体的地址 
    即proc_info_list结构
     bl _lookup_processor_type  //调用该函数确定内核是否支持该款cpu,如果支持,则r5返回一个描述处理器结构
      的地址,r5=procinfo 否则r5等于0 该函数在arch/arm/kernel/head-common.S中定义的
     movs r10, r5
     beq _error_p  如果r5=0,则报错         r5寄存器返回一个用来描述这个开发板的结构体的地址 即machine_desc结构
     bl _lookup_machine_type    //调用该函数确定内核是否支持该款机器ID 此时我们uboot传过来的机器ID是放在r1寄存器中,返回值为r5=machinfo,也即描述该款机器结构的地址,
     movs r8,r5               // 如果内核不支持该款机器,则r5=0;
     beq _error_a  // r5=0,则报错
     
     
     内核用若干个proc_info_list结构来描述不同的cpu,也即这些都是内核所支持的,该结构体被强制定义为
     段属性为.proc.info.init的结构,在vmlinux.lds中看到,
     __proc_info_begin =.;  //proc_info_list结构的起始地址
     *(.proc.info.init)
     __proc_info_end =.;  //proc_info_list结构的结束地址
     
     struct proc_info_list {


   unsigned int    cpu_val;   // 该成员表示cpu id


   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_processor_type函数
     这个函数是来检查机器型号的,它会读取你bootloader传进来的机器ID和他能够处 理的机器ID进行比较看是否能
     够处理。内核的ID号定义在arc/arm/tool/mach_types文件中MACH_TYPE_xxxx宏定义。
     内核究竟就如何检查是否是它支持的机器的呢?实际上每个机器都会
     在/arc/arm/mach-xxxx/smdk-xxxx.c文件中有个描述特定机器的数据结构,如下
     
     MACHINE_START(S3C2440,"SMDK2440")   
       /* Maintainer: Ben Dooks */   
       .phys_io   =S3C2410_PA_UART,   
       .io_pg_offst     = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,   
       .boot_params   = S3C2410_SDRAM_PA + 0x100,   
    
       .init_irq   =s3c24xx_init_irq,   
       .map_io          =smdk2440_map_io,   
       .init_machine   = smdk2440_machine_init,   
       .timer           =&s3c24xx_timer,   
MACHINE_END   


   之后展开为
   staticconst struct machine_desc mach_desc_S3C2440     \   
 __used                                             \   
 __attribute__((__section__(".arch.info.init")))= {    \   
       .nr          =MACH_TYPE_S3C2440,          \   
       .name        =”SMDK2440”,};  
        
.phys_io  = S3C2410_PA_UART,   
       .io_pg_offst     = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,   
       .boot_params   = S3C2410_SDRAM_PA + 0x100,   
    
       .init_irq   =s3c24xx_init_irq,   
       .map_io          =smdk2440_map_io,   
       .init_machine = smdk2440_machine_init,   
       .timer           =&s3c24xx_timer,     



每个机器都会有一个machine_desc mach_desc结构,内核通过检查每个machine_desc mach_desc的nr 号和bootloader传上来
的ID进行比较,如果相同,内核就认为支持该机器,
而且内核在后面的工作中会调用该机器的 machine_desc mach_desc_结构中的方法进行一些初始化工作。


在arch/arm/kernel/vmlinux.lds脚本文件中


__arch_info_begin = .; //machine_desc结构体的开始地址
*(.arch.info.init)
__arch_info_end =.;   //machine_desc结构体的结束地址
即所有的struct machine_desc结构都被链接进.arch.info.init段内,开始地址为__arch_info_begin,结束地址为__arch_info_end
同样在arch/arm/kernel/head-common.S中


3: .long .
    .long __arch_info_begin
    .long __arch_info_end
    
    .type __lookup_machine_type,%function
    
 __lookup_machine_type:           
     adr r3,3b            //读入3处的运行地址也即物理地址给r3 b指back 后面的意思
     ldmia r3, {r4,r5,r6} //r4= 3处的虚拟地址 r5=__arch_info_begin r6=__arch_info_end  r5,r6都是在链接脚本上定义的虚拟地址,因为此时,mmu并没有使能,所以要使用物理地址
     sub r3, r3, r4       // r3 = 物理地址和虚拟地址的差值
     add r5, r5, r3       //  r5 = 虚拟地址转化成物理地址 __arch_info_begin对应的物理地址
     add r6, r6, r3       //  r6 = __arch_info_end对应的物理地址
     
1:  ldr r3,[r5,#MACHINFO_TYPE]  // 此时r3= 第一个machine_desc结构体的nr成员
    teq r3,r1     //比较r3和r1是否相等,r1存放的是uboot传过来的机器ID
    beq 2f        如果相等,则意味着匹配,执行2处
    add r5, r5, #SIZEOF_MACHINE_DESC //否则r5指向下一个machine_desc结构
    cmp r5, r6    //是否比较完所有的machine_desc结构?
    blo 1b       //没有则继续比较,跳到1处执行
    mov r5, #0   //比较完毕,没有匹配的machine_decs结构,则r5=0;
    
2:  mov pc, lr


 r5将返回__lookup_machine_type函数所确定的machine_desc结构
 
        _
 两个汇编函数lookup_processor_type和lookup_machine_type 分别用来表示内核是否支持该款CPU ID和板子 ID
 内核把自己所支持的所有CPU id存放在.proc.info.init段内(vmlinux.lds中) 内核把自己所支持的所有单板结构
 放在.proc.info.init段内(vmlinux.lds中)这两个汇编函数的原理都是一样的,lookup_processor_type 通过cp15协处理器
 读取cpu的id存放在r9寄存器中,该函数枚举.proc.info.init段内的某proc_info_list结构的cpu_val成员与r9进行比较
 若相等,则返回该proc_info_list结构的地址 _lookup_machine_type  uboot在临死之前传的机器id存放在r1寄存器,
 内核把自己所支持的所有机器结构放在.arch.info.init段内(vmlinux.lds中),该函数枚举段内的
 某machine_desc结构的nr成员与 r1进行比较,若相等,则返回macinene_desc结构的地址
 
 
 
 
 
 
 
 
 
 
 
 
 
 ------------------------------------------------------------------------------------------------------------------------------------------------------------
 
 现在我们来分析内核启动的第二阶段:在init/main.c中start_kernel函数
 
 
 asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];


smp_setup_processor_id();


/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
debug_objects_early_init();


/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();


cgroup_init_early();


local_irq_disable();
early_boot_irqs_disabled = true;


/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
tick_init();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE "%s", linux_banner);     
//打印内核版本信息,但是此时并没有打印,此时printk函数只是将打印信息放在缓冲区中,并没有打印到控制台上
        (比如串口,lcd屏)上
//因为这个时候控制台还没有初始化化,在执行完console_init之后才打印输出

setup_arch(&command_line);
//同时获取从uboot传过来的命令参数放在command_line指针     
//非常重要,主要用来处理uboot传过来的参数
//setup_arch函数主要目的两个:第一,解析uboot传过来的参数,第二,对于machine_desc结构体相关函数的调用


mm_init_owner(&init_mm, &init_task);
setup_command_line(command_line);   //重要 拷贝command_line的值放在static_command_line
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */


build_all_zonelists(NULL);
page_alloc_init();


printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);  /
/打印命令行参数,也即uboot传过来的命令行参数




parse_early_param();   //或调用do_eary_param函数,主要是处理early_param宏
parse_args("Booting kernel", static_command_line, __start___param,    //parse_args很重要,解析命令行参数,获取root=xxx的值交给unknown_bootoption来执行,这样就可以挂接根文件系统了
  __stop___param - __start___param,
  &unknown_bootoption);               
   
 // __setup宏和early_param宏非常相似,定义的都是同一个类型的结构体,放在同一个段里,
  用parse_early_param函数来解析所有的early_param宏,用parse_args来解析所有的__setup宏




/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();  //异常向量表
mm_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
if (!irqs_disabled()) {
printk(KERN_WARNING "start_kernel(): bug: interrupts were "
"enabled *very* early, fixing it\n");
local_irq_disable();
}
idr_init_cache();
perf_event_init();
rcu_init();
radix_tree_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ(); //中断的初始化
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
if (!irqs_disabled())
printk(KERN_CRIT "start_kernel(): bug: interrupts were "
"enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();


/* Interrupts are enabled now so all GFP allocations are safe. */
gfp_allowed_mask = __GFP_BITS_MASK;


kmem_cache_init_late();


/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();              //执行到此处,内核才打印出内核版本信息。。。
if (panic_later)
panic(panic_later, panic_param);


lockdep_info();


/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();


#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
   page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
   "disabling it.\n",
   page_to_pfn(virt_to_page((void *)initrd_start)),
   min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
enable_debug_pagealloc();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
pidmap_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
thread_info_cache_init();
cred_init();
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);  //该函数第三处最重要的地方,在内存中建立了一颗vfs目录树
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();


check_bugs();


acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();


ftrace_init();


/* Do the rest non-__init'ed, we're now alive */
rest_init(); // 很重要。
}
















分析setup_arch函数 是在arch/arm/kernel/setup.c中




static struct init_tags {
struct tag_header hdr1;  第一个tag
struct tag_core   core;
struct tag_header hdr2;  第二个tag
struct tag_mem32  mem;
struct tag_header hdr3;  第三个tag
} init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },  ATAG_CORE为tag其实设置标记
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE, PHYS_OFFSET },
{ 0, ATAG_NONE }   ATAG_NONE为结束标记
};


该内核版本有点老了~ 分析较新的内核
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;//通过parse_tags函数中的__tagtable(ATAG_CMDLINE, parse_tag_cmdline);会将命令行字符串拷贝到default_command_line,见后


init_tags.mem.start = PHYS_OFFSET;  设置内存的起始地址


unwind_init();


setup_processor();   
//处理器相关的设置,它会再次调用内核第一阶段的lookup_processor_type的函数,
以获得该处理器的proc_info_list结构




mdesc = setup_machine(machine_arch_type); 
//该函数会再次调用内核启动第一阶段的lookup_machine_type函数,根据当前的机器ID确定这款板子machine_desc


machine_desc = mdesc;
machine_name = mdesc->name;


if (mdesc->soft_reboot)
reboot_setup("s");


if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) {
#ifdef CONFIG_MMU
/*
* We still are executing with a minimal MMU mapping created
* with the presumption that the machine default for this
* is located in the first MB of RAM.  Anything else will
* fault and silently hang the kernel at this point.
*/
 
if (mdesc->boot_params < PHYS_OFFSET ||    
 //若是定义了启动参数的地址? 若是设置的启动参数地址小于内存起始地址,但在离内存起始地址超过1M的地方
  ,则表示出错


   mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {
printk(KERN_WARNING
      "Default boot params at physical 0x%08lx out of reach\n",
      mdesc->boot_params);
} else
#endif
{                     // 执行此处
tags = phys_to_virt(mdesc->boot_params);   
//tags 这是地址就是tag列表中的首地址,因为mmu已经使能,所以要将物理地址转换为虚拟地址
}
}


#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;


if (mdesc->fixup)                             //调用mdesc的fixup结构
mdesc->fixup(mdesc, tags, &from, &meminfo);


if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)          //如果在内核中已经定义了meminfo结构
squash_mem_tags(tags);            //则忽略内存tag
save_atags(tags);
parse_tags(tags); 
//开始解析和处理每个tag,进过解析命令行参数的时候 from获得从uboot传过来的命令行参数的值
}


init_mm.start_code = (unsigned long) _text;
init_mm.end_code   = (unsigned long) _etext;
init_mm.end_data   = (unsigned long) _edata;
init_mm.brk   = (unsigned long) _end;


/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);  
//所以 from boot_command_line cmd_line值是一样的,都是从uboot传过来的命令行参数


/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;                  
//此时setup_arch函数将通过cmdling_p这个二级指针获取uboot传过来的命令行参数


parse_early_param();  
//解析early_param参数  对于early_param("mem", early_mem); early_param该宏表示,如果命令行字符串中出现mem,就用early_mem函数来处理,见后


arm_memblock_init(&meminfo, mdesc);


paging_init(mdesc);   // 重新初始化页表,此处paging_init-->devicemaps_init--->mdesc->map_io
request_standard_resources(mdesc);


#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
reserve_crashkernel();


cpu_init();
tcm_init();


#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif


#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();


if (mdesc->init_early)
mdesc->init_early();
}




 ===============================================新内核==================================================
较新的内核分析:


static struct init_tags {
struct tag_header hdr1;  第一个tag
struct tag_core   core;
struct tag_header hdr2;  第二个tag
struct tag_mem32  mem;
struct tag_header hdr3;  第三个tag

init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },  ATAG_CORE为tag其实设置标记
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE, PHYS_OFFSET },
{ 0, ATAG_NONE }   ATAG_NONE为结束标记
};


void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;


unwind_init();


setup_processor();  // 见下  获取该cpu结构proc_info_list结构
mdesc = setup_machine(machine_arch_type);见下 获取该单板结构machine_desc
machine_name = mdesc->name;


if (mdesc->soft_reboot)
reboot_setup("s");


if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);  
// 执行 uboot临终之前传给内核的启动参数地址都是以struct tag *形式


/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)         不执行
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)         不执行
tags = (struct tag *)&init_tags;


if (mdesc->fixup)  执行
mdesc->fixup(mdesc, tags, &from, &meminfo);


if (tags->hdr.tag == ATAG_CORE) {     执行
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);  开始解析tag   这个是该函数最重要的函数
}


init_mm.start_code = (unsigned long) _text;
init_mm.end_code   = (unsigned long) _etext;
init_mm.end_data   = (unsigned long) _edata;
init_mm.brk   = (unsigned long) _end;


/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);


/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;


parse_early_param();


paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);


#ifdef CONFIG_SMP
smp_init_cpus();
#endif


cpu_init();
tcm_init();


/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;     
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;


#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}








extern struct proc_info_list *lookup_processor_type(unsigned int);    
extern struct machine_desc *lookup_machine_type(unsigned int);


static void __init setup_processor(void)
{
struct proc_info_list *list;


/*
* locate processor in the list of supported processor
* types.  The linker builds this table for us from the
* entries in arch/arm/mm/proc-*.S
*/
list = lookup_processor_type(read_cpuid_id());// 根据cpu id获得内核所支持的cpu结构proc_info_list
if (!list) {
printk("CPU configuration botched (ID %08x), unable "
      "to continue.\n", read_cpuid_id());
while (1);
}


cpu_name = list->cpu_name;


#ifdef MULTI_CPU
processor = *list->proc;
#endif
#ifdef MULTI_TLB
cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
cpu_cache = *list->cache;
#endif


printk("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
      cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
      proc_arch[cpu_architecture()], cr_alignment);


sprintf(init_utsname()->machine, "%s%c", list->arch_name, ENDIANNESS);
sprintf(elf_platform, "%s%c", list->elf_name, ENDIANNESS);
elf_hwcap = list->elf_hwcap;
#ifndef CONFIG_ARM_THUMB
elf_hwcap &= ~HWCAP_THUMB;
#endif


cacheid_init();
cpu_proc_init();
}


static struct machine_desc * __init setup_machine(unsigned int nr)
{
struct machine_desc *list;


/*
* locate machine in the list of supported machines.
*/
list = lookup_machine_type(nr); // 根据机器id号来获取该款机器的单板结构machine_desc
if (!list) {
printk("Machine configuration botched (nr %d), unable "
      "to continue.\n", nr);
while (1);
}


printk("Machine: %s\n", list->name);


return list;
}








=================================================================================================================


#define __tag __used __attribute__((__section__(".taglist.init")))   即__tagtable(tag, fn)宏定义个段属性为.taglist.init的一个结构体struct tagtable
#define __tagtable(tag, fn) \  
static struct tagtable __tagtable_##fn __tag = { tag, fn }


以下函数是解析每一个TAG
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t))
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x\n",
t->hdr.tag);
}


static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;


for (t = &__tagtable_begin; t < &__tagtable_end; t++)
if (tag->hdr.tag == t->tag) {
t->parse(tag);
break;
}


return t < &__tagtable_end;
}
 
parse_tags(tags)----->parse_tag()----->__tagtable()
                                            //即为每种TAG定义了相应的处理函数
__tagtable(ATAG_CORE, parse_tag_core);
__tagtable(ATAG_MEM, parse_tag_mem32);     //内存TAG
__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
__tagtable(ATAG_SERIAL, parse_tag_serialnr);
__tagtable(ATAG_REVISION, parse_tag_revision);
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);  //命令行TAG    


以上比较重要的两个,内存TAG和命令行TAG
parse_tag_mem32 该函数根据tag定义的起始地址长度和大小,在全局结构体变量meminfo中增加内存的描述信息,
以后内核就可以通过meminfo了解开发板的内存信息
parse_tag_cmdline -> strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE); 
它只是简单的将命令行字符串复制到default_command_line中保存下来,做后续处理














下面看两个非常重要的宏__setup(str, fn) 和 early_param(str, fn),他们是在include/linux/init.h中定义
__setup(str,fn) 定义了一个段属性为 .init.setup的结构体,成员分别是 str,fn,early(为0)


early_param(str,fn) 也同样定义了一个段属性为.init.setup的结构体,成员是 str,fn,early(为1)


在链接脚本vmlinux.lds定义了该段


_setup_start=.;
*(.init.setup)
_setup_end=.;
即所有的__setup(str,fn),early_param(str,fn)两个宏所定义的结构体都被链接进这个段中,
我们可以在源码中搜索_setup_start _setup_end这两个地址,以下会被用到


#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }


#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)




#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)


struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};


即_setup宏和early_param宏定义了一个段属性为.init.setup的结构体obs_kernel_param








/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);












                                                                   
 命令行参数中几个比较重要的_setup宏  _setup宏主要定义在init/do_mounts.c和init/main.c中 当我们遇到
 parse_args("Booting kernel", static_command_line, __start___param,__stop___param - __start___param,
&unknown_bootoption)------>unknown_bootoption------->
obsolete_checksetup(命令行参数指针)---->命令行字符串中有的,处理所有的_setup宏
 以下是在init/do_mounts.c文件中定义的  
 
 static char __initdata saved_root_name[64];  //定义一个saved_root_name的字符串数组
 
 static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}


__setup("root=", root_dev_setup);// 将命令行root=xxx中 xxx值赋给saved_root_name




     init/main.c中
static int __init init_setup(char *str)
{
unsigned int i;


execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);   命令行参数中出现init=


static int __init rdinit_setup(char *str)
{
unsigned int i;


ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);   命令行参数中出现rdinit=


那么是什么时候会调用这个root_dev_setup这个函数指针呢
有之前的分析可以知道,__setup宏定义个一个段属性为.init.setup的结构体,我们搜索_setup_start _setup_end
第一个obsolete_checksetup函数(init/main.c)
static int __init obsolete_checksetup(char *line)  该参数是uboot传过来的命令行参数的指针
{
const struct obs_kernel_param *p;
int had_early_param = 0;


p = __setup_start;      p所指向的结构是内核存储的所有的可能设计到的命令行参数
do {
int n = strlen(p->str);
if (!strncmp(line, p->str, n)) {
if (p->early) {                                                 处理early_param宏
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
if (line[n] == '\0' || line[n] == '=')
had_early_param = 1;       
} else if (!p->setup_func) {                            
printk(KERN_WARNING "Parameter %s is obsolete,"
      " ignored\n", p->str);
return 1;
} else if (p->setup_func(line + n))
//在这里解析了所有__setup宏定义的结构体,即该结构体包含的函数会被执行,比如__setup("root=", root_dev_setup) __setup("init=", init_setup) ,会依次执行root_dev_setup init_setup函数
//在这里调用了真正的root_dev_setup函数,参数是root=后面的值,这样我们的saved_root_name就获得了这个参数值
return 1;
}
p++;
} while (p < __setup_end);


return had_early_param;
}
而obsolete_checksetup函数是被同一个文件中的unknown_bootoption函数所调用,而unknown_bootoption函数
是在start_kernel中被调用 parse_args("Booting kernel", static_command_line, __start___param,
  __stop___param - __start___param,
  &unknown_bootoption);  这样我们在此处就获得了root=xxx的把值存放在saved_root_name




在arch/arm/kernel/中生成的vmlinux.lds中
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
 . = 0xC0000000 + 0x00008000;
 .init : { /* Init code and data */
  _stext = .;
  _sinittext = .;
   *(.head.text)
   *(.init.text) *(.cpuinit.text) *(.meminit.text)
  _einittext = .;
  __proc_info_begin = .;    所有的cpu结构proc_info_list 存放在该段
   *(.proc.info.init)
  __proc_info_end = .;
  
  __arch_info_begin = .;  所有的单板结构machine_desc 存放在该段   宏MECHINE_START和MACHINE_END结构表示 即所有的MECHINE_START和MACHINE_END宏定义的全部放在这里
   *(.arch.info.init)
  __arch_info_end = .;
  
  __tagtable_begin = .;  所有的解析tag结构tag_table存放在该段  用宏_tagtable来表示该结构 内核所有能涉及到的tag全部储存在这里,即所有的_tagtable宏全部放在这里
   *(.taglist.init)
  __tagtable_end = .;
  
  . = ALIGN(16);       
  __setup_start = .;    所有的解析命令行参数obs_kernel_param结构体存放在该段  用宏 _setup和early_param来表示  内核所有能处理的命令行字符串全部存储在这两个宏里,即_setup和early_param宏定义的全部放在这里
   *(.init.setup) 
   __setup_end = .;


//小结:parse_tags是解析所有的uboot传过来的TAG参数 执行所有的__tagtable()
        parse_args是解析命令行参数的  执行所有的__setup()
        parse_early_param是解析early_param(),实际是也会调用parse_args函数   




====================================================================================================================================================================


我们继续分析内核启动应用程序的流程
start_kernel函数快结束的时候调用了rest_init函数(在init/main.c中定义)
stat_kernel
          -->rest_init
                      -->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
                                       --->prepare_namespace() 
                                                   -->mount_root()
                                                                                                         
                                               挂接好根文件系统                      
                                                        ---->init_post()
                                                         run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");       //执行应用程序



在内核和 initrd映像被解压并拷贝到内存中之后,内核就会被调用了。它会执行不同的初始化操作,最终您会发现
自己到了init/main.c:init()(subdir/file:function)函数中。这个函数执行了大量的子系统初始化操作。此处会执行
一个对init/do_mounts.c:prepare_namespace() 的调用,这个函数用来准备名称空间(挂载 dev 文件系统、RAID或 md、
设备以及最后的 initrd)。加载 initrd 是通过调用init/do_mounts_initrd.c:initrd_load() 实现的。


initrd_load() 函数调用了init/do_mounts_rd.c:rd_load_image(),它通过调用init/do_mounts_rd.c:identify_ramdisk_image() 
来确定要加载哪个 RAM磁盘。这个函数会检查映像文件的 magic 号来确定它是 minux、etc2、romfs、cramfs 或 gzip 格式。
在返回到initrd_load_image 之前,它还会调用 init/do_mounts_rd:crd_load()。这个函数负责为 RAM磁盘分配空间,
并计算循环冗余校验码(CRC),然后对 RAM磁盘映像进行解压,并将其加载到内存中。现在,我们在一个适合挂载的
块设备中就有了这个 initrd 映像。


现在使用一个 init/do_mounts.c:mount_root()调用将这个块设备挂载到根文件系统上。它会创建根设备,并调用 
init/do_mounts.c:mount_block_root()。在这里调用init/do_mounts.c:do_mount_root(),后者又会
调用 fs/namespace.c:sys_mount()来真正挂载根文件系统,然后 chdir 到这个文件系统中。这就是我们在清单 6 中
所看到的熟悉消息 VFS: Mounted root(ext2 file system). 的地方。


最后,返回到 init 函数中,并调用init/main.c:run_init_process。这会导致调用 execve 来启动 init 进程
(在本例中是/linuxrc)。linuxrc 可以是一个可执行程序,也可以是一个脚本
  
                                                                                                      
void __init prepare_namespace(void)
{
int is_floppy;


if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
      root_delay);
ssleep(root_delay);
}


/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();


md_run_setup();


if (saved_root_name[0]) {
root_device_name = saved_root_name;   //注意 saved_root_name此时已经有值了
if (!strncmp(root_device_name, "mtd", 3) ||
   !strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);   //ROOT_DEV
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}


if (initrd_load())  //加载initrd 初始化内存盘文件系统
goto out;


/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}


is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;


if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;


mount_root();         //挂接根文件系统
out:
devtmpfs_mount("dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot((const char __user __force *)".");
}








挂接根文件系统之后,开始启动第一个进程init


static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();




current->signal->flags |= SIGNAL_UNKILLABLE;


if (ramdisk_execute_command) {       所以如果uboot传过来的命令行参数有rdinit=xxx,则会执行
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}


/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {         所以如果uboot传过来的命令行参数有init=xxx,则会执行
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s.  Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
//如果uboot传过来的命令行参数没有init=xxx或者rdinit=xxx,则会执行该进程,一去不复返,后面的就不会执行了
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");


panic("No init found.  Try passing init= option to kernel. "
     "See Linux Documentation/init.txt for guidance.");
}
                                                                                  
static int __init rdinit_setup(char *str)
{
unsigned int i;


ramdisk_execute_command = str;         //ramdisk_execute_command获取了rdinit= xxx 的值
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);


static int __init init_setup(char *str)
{
unsigned int i;


execute_command = str;                 //execute_command获取了init=xxx的值
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);








-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
do_basic_setup函数
    分析include/linux/init.h
                    
#define __init __section(.init.text) __cold notrace    即__init宏
#define __initdata __section(.init.data)
#define __initconst __section(.init.rodata)
#define __exitdata __section(.exit.data)
#define __exit_call __used __section(.exitcall.exit)   后面会用到 该宏也即定义了“丢弃段”


/* modpost check for section mismatches during the kernel build.
 * A section mismatch happens when there are references from a
 * code or data section to an init section (both code or data).
 * The init sections are (for most archs) discarded by the kernel
 * when early init has completed so all such references are potential bugs.
 * For exit sections the same issue exists.
 * The following markers are used for the cases where the reference to
 * the *init / *exit section (code or data) is valid and will teach
 * modpost not to issue a warning.
 * The markers follow same syntax rules as __init / __initdata. */
#define __ref            __section(.ref.text) noinline
#define __refdata        __section(.ref.data)
#define __refconst       __section(.ref.rodata)


/* compatibility defines */
#define __init_refok     __ref
#define __initdata_refok __refdata
#define __exit_refok     __ref




#ifdef MODULE
#define __exitused
#else
#define __exitused  __used
#endif


#define __exit          __section(.exit.text) __exitused __cold


/* Used for HOTPLUG */
#define __devinit        __section(.devinit.text) __cold
#define __devinitdata    __section(.devinit.data)
#define __devinitconst   __section(.devinit.rodata)
#define __devexit        __section(.devexit.text) __exitused __cold
#define __devexitdata    __section(.devexit.data)
#define __devexitconst   __section(.devexit.rodata)


/* Used for HOTPLUG_CPU */
#define __cpuinit        __section(.cpuinit.text) __cold
#define __cpuinitdata    __section(.cpuinit.data)
#define __cpuinitconst   __section(.cpuinit.rodata)
#define __cpuexit        __section(.cpuexit.text) __exitused __cold
#define __cpuexitdata    __section(.cpuexit.data)
#define __cpuexitconst   __section(.cpuexit.rodata)


/* Used for MEMORY_HOTPLUG */
#define __meminit        __section(.meminit.text) __cold
#define __meminitdata    __section(.meminit.data)
#define __meminitconst   __section(.meminit.rodata)
#define __memexit        __section(.memexit.text) __exitused __cold
#define __memexitdata    __section(.memexit.data)
#define __memexitconst   __section(.memexit.rodata)


/* For assembly routines */
#define __HEAD .section ".head.text","ax"
#define __INIT .section ".init.text","ax"
#define __FINIT .previous


#define __INITDATA .section ".init.data","aw"
#define __INITRODATA .section ".init.rodata","a"
#define __FINITDATA .previous


#define __DEVINIT        .section ".devinit.text", "ax"
#define __DEVINITDATA    .section ".devinit.data", "aw"
#define __DEVINITRODATA  .section ".devinit.rodata", "a"


#define __CPUINIT        .section ".cpuinit.text", "ax"
#define __CPUINITDATA    .section ".cpuinit.data", "aw"
#define __CPUINITRODATA  .section ".cpuinit.rodata", "a"


#define __MEMINIT        .section ".meminit.text", "ax"
#define __MEMINITDATA    .section ".meminit.data", "aw"
#define __MEMINITRODATA  .section ".meminit.rodata", "a"


/* silence warnings when references are OK */
#define __REF            .section       ".ref.text", "ax"
#define __REFDATA        .section       ".ref.data", "aw"
#define __REFCONST       .section       ".ref.rodata", "a"


#ifndef __ASSEMBLY__
/*
 * Used for initialization calls..
 */
typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);


extern initcall_t __con_initcall_start[], __con_initcall_end[];
extern initcall_t __security_initcall_start[], __security_initcall_end[];


/* Used for contructor calls. */
typedef void (*ctor_fn_t)(void);


/* Defined in init/main.c */
extern int do_one_initcall(initcall_t fn);
extern char __initdata boot_command_line[];
extern char *saved_command_line;
extern unsigned int reset_devices;


/* used by init/main.c */
void setup_arch(char **);
void prepare_namespace(void);


extern void (*late_time_init)(void);


extern int initcall_debug;


#endif


#ifndef MODULE      如果驱动模块静态编译进内核
#ifndef __ASSEMBLY__


/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */
                          
         typedef int (*initcall_t)(void); /*定义函数指针类型*/
         extern initcall_t __initcall_start, __initcall_end; /*申明外部变量,在ld的脚本文件中定义*/
         非常重要的宏  即该宏定义个段属性为.initcall" level ".init的一个initcall_t类型的函数指针
         
#define __define_initcall(level,fn,id) \                           
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn


/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn) __define_initcall("early",fn,early)


/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn) __define_initcall("0",fn,0)


#define core_initcall(fn) __define_initcall("1",fn,1)    定义了一个.initcall1.init的段属性的函数指针_initcall_##fn##id 即core_initcall定义的函数fn全部放在.initcall1.init段中
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)    定义了一个.initcall2.init的段属性
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3) // 重要 arch_initcall(customize_machine); 有的芯片在这个函数中进行设备的注册,执行machine_desc- >init_machine();
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) //重要 rootfs_initcall(populate_rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)   //此处初始化了静态编译的驱动模块 优先级排在第6位


#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7) //在mtk平台上mt6575_board.c文件中定义了late_initcall(board_init); 而board_init---mt6575_board_init 
                                                                      在该函数中对各平台设备进行注册,也即在执行此刻的时候,会执行各platform_driver的probe函数
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)


#define __initcall(fn) device_initcall(fn)   //此处初始化了静态编译的驱动模块




#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn


#define console_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.con_initcall.init) = fn


#define security_initcall(fn) \
static initcall_t __initcall_##fn \
__used __section(.security_initcall.init) = fn


struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};


/*
 * Only for really core code.  See moduleparam.h for the normal way.
 *
 * Force the alignment so the compiler doesn't space elements of the
 * obs_kernel_param "array" too far apart in .init.setup.
 */
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }


#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)


/* NOTE: fn is as per module_param, not __setup!  Emits warning if fn
 * returns non-zero. */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)


/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
#endif /* __ASSEMBLY__ */








/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x) __initcall(x);   //此处初始化了静态编译的驱动模块 在这里真正的确定了module_init
                                            即所有的模块入口函数,有module_init宏定义的各函数都全部被连接进
                                            .initcall6.init段内


/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 * 
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x) __exitcall(x);  
//此处所有模块的卸载出口函数,即module_exit宏定义的函数都放在“丢弃段”.exitcall.exit内


#else /* MODULE */            如果驱动模块动态加载入内核




/* Don't use these in modules, but some people do... */
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)


#define security_initcall(fn) module_init(fn)




/* Each module must use one module_init(). */
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));  
//insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
        //将动态驱动模块载入到内核空间
通过alias将initfn变名为init_module 通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,
在系统内部会调用sys_init_module()去找到init_module函数的入口地址。






/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));


#define __setup_param(str, unique_id, fn) /* nothing */
#define __setup(str, func) /* nothing */
#endif


/* Data marked not to be saved by software suspend */
#define __nosavedata __section(.data..nosave)


/* This means "can be init if no module support, otherwise module load
   may call it." */
#ifdef CONFIG_MODULES
#define __init_or_module
#define __initdata_or_module
#else
#define __init_or_module __init
#define __initdata_or_module __initdata
#endif /*CONFIG_MODULES*/


/* Functions marked as __devexit may be discarded at kernel link time, depending
   on config options.  Newer versions of binutils detect references from
   retained sections to discarded sections and flag an error.  Pointers to
   __devexit functions must use __devexit_p(function_name), the wrapper will
   insert either the function_name or NULL, depending on the config options.
 */
#if defined(MODULE) || defined(CONFIG_HOTPLUG)
#define __devexit_p(x) x
#else
#define __devexit_p(x) NULL
#endif


#ifdef MODULE
#define __exit_p(x) x
#else
#define __exit_p(x) NULL
#endif


#endif /* _LINUX_INIT_H */






-----------------------------------------------------------------------------
在arch/arm/kernel/vmlinux.lds
SECTIONS
{
 . = 0xC0000000 + 0x00008000;
 .init : { /* Init code and data */
  _stext = .;
  _sinittext = .;
   *(.head.text)
   *(.init.text) *(.cpuinit.text) *(.meminit.text)
  _einittext = .;
  __proc_info_begin = .;
   *(.proc.info.init)
  __proc_info_end = .;
  __arch_info_begin = .;
   *(.arch.info.init)
  __arch_info_end = .;
  __tagtable_begin = .;
   *(.taglist.init)
  __tagtable_end = .;
  . = ALIGN(16); __setup_start = .;
   *(.init.setup) 
   __setup_end = .;
  __initcall_start = .;    从这里开始存放所有的initcall_t类型的函数指针 一旦执行do_initcalls函数,则即执行__initcall_start到__initcall_end段所有的函数
  *(.initcallearly.init)
   __early_initcall_end = .;
    *(.initcall0.init) 
    *(.initcall0s.init) 
    *(.initcall1.init) 
    *(.initcall1s.init) 
    *(.initcall2.init) 
    *(.initcall2s.init) 
    *(.initcall3.init) 
    *(.initcall3s.init) 
    *(.initcall4.init) 
    *(.initcall4s.init) 
    *(.initcall5.init) 
    *(.initcall5s.init) 
    *(.initcallrootfs.init) 
    *(.initcall6.init) 
    *(.initcall6s.init) 
    *(.initcall7.init) 
    *(.initcall7s.init)
     __initcall_end = .;
  __con_initcall_start = .; *(.con_initcall.init) __con_initcall_end = .;
  __security_initcall_start = .; *(.security_initcall.init) __security_initcall_end = .;
  . = ALIGN((1 << 12)); 
  __initramfs_start = .;     三种initrd中(cpio-initrd,image-initrd,initramfs)的一种,即initramfs,它是将initramfs initrd连接进linux内核中的.init.ramfs段里
  *(.init.ramfs)
   __initramfs_end = .;
   
  __init_begin = _stext;






在init/main.c中函数   do_basic_setup---->do_initcalls
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];


static void __init do_initcalls(void)
{
initcall_t *fn;


for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);


/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}


/*
 * Ok, the machine is now initialized. None of the devices
 * have been touched yet, but the CPU subsystem is up and
 * running, and memory and process management works.
 *
 * Now we can finally start doing some real work..
 */
static void __init do_basic_setup(void)
{
init_workqueues();
cpuset_init_smp();
usermodehelper_init();
init_tmpfs();
driver_init();
init_irq_proc();
do_ctors();
do_initcalls(); 条用该函数
}


Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;
压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数 decompress_kernel()<./arch/arm/boot/compressed/misc.c>解压,打印 “Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."
然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:


do_initcalls函数执行到这里,调用两个非常重要的函数中的一个 rootfs_initcall(default_rootfs); 在noinitramfs.c中  没定义CONFIG_BLK_DEV_INITRD
                                                           rootfs_initcall(populate_rootfs);在initramfs.c中 定义CONFIG_BLK_DEV_INITRD
两个只执行一个,至于执行哪个,取决于linux/init目录下的makefile文件
要特别指出的是initramfs.c模块的入口函数populate_rootfs()是否执行取决于Kernel的编译选项。


# Makefile for the linux kernel. 





obj-y := main.o version.o mounts.o 


ifneq ($(CONFIG_BLK_DEV_INITRD),y)    没有定义该宏 执行此


obj-y += noinitramfs.o 


else                           定义该宏CONFIG_BLK_DEV_INITRD执行此


obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o 


endif 


obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o 


mounts-y := do_mounts.o 


mounts-$(CONFIG_BLK_DEV_RAM) += do_mounts_rd.o 


mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o 


mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o 
   ===========================================================================================


populate_rootfs主要完成Initrd的检测工作,检查出是CPIO Initrd还是Initramfs还是Image-Initrd
 static int __init populate_rootfs(void) 





[1] char *err = unpack_to_rootfs(__initramfs_start,  
第一个检测是否initramfs文件系统 若是的,将位于__initramfs_end - __initramfs_start的initramfs段释放到“/”目录下


__initramfs_end - __initramfs_start, 0); 


if (err) 


panic(err); 
      以下检测是cpio-initrd还是image-initrd 无论这两种格式,uboot都会把它加载到内存里的initrd_start地址处
[2] if (initrd_start) {    


#ifdef CONFIG_BLK_DEV_RAM  说明可能存在image-initrd


int fd; 


printk(KERN_INFO "checking if image is initramfs..."); 


[3] err = unpack_to_rootfs((char *)initrd_start, 


initrd_end - initrd_start, 1);   检测释放到地址initrd_start的包是否是cpio格式的


if (!err) {            如果err=0,说明是cpio格式的包 即解压到“/”目录下,


printk(" it is\n"); 


unpack_to_rootfs((char *)initrd_start, 


initrd_end - initrd_start, 0);  解压


free_initrd(); 


return 0; 





printk("it isn't (%s); looks like an initrd\n", err);如果执行到这里,就说明是image-initrd,在根文件系统中创建文件/initrd.image,也即将image-initrd内容保存到文件/initrd.image


[4] fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700); 


if (fd >= 0) { 


[5] sys_write(fd, (char *)initrd_start, 


initrd_end - initrd_start); 


sys_close(fd); 


[6] free_initrd(); 释放





#else   不存在image-initrd 说明此时是cpio-initrd文件


printk(KERN_INFO "Unpacking initramfs...");    


[7] err = unpack_to_rootfs((char *)initrd_start, 


initrd_end - initrd_start, 0); 


if (err) 


panic(err); 


printk(" done\n"); 


free_initrd(); 


#endif 





return 0; 





代码[1]:unpack_to_rootfs顾名思义,就是解压包到rootfs,其具有两个功能,一个是检测是否是属于cpio包,
另外一个就是解压cpio包,通过最后一个参数进行控制。1:检测,0:解压。其实,Initramfs也是压缩过后的CPIO文件。 


资料中提到,Linux2.5中开始引入initramfs,在Linux2.6中一定存在,而且编译的时候通过连接脚本
arch\arm\kernel\vmlinux.lds将其编译到__initramfs_start~__initramfs_end,执行完unpack_to_rootfs后将被拷贝到
根目录。 


代码[2]:判断是否加载了Initrd,无论对于那种格式的Initrd,即无论是CPIO-Initrd还是Image-Initrd,U-Boot都会
将其拷贝到initrd_start。当然了,如果是initramfs的情况下,该值肯定为空了。 


代码[3]:判断加载的是不是CPIO-Initrd。 


通过在这里主要用于检测,如果是编译到Linux Kernel的CPIO Initrd,__initramfs_end - __initramfs_start应该是
大于零的,否则为零,其实也就是通过这里来判断是否为CPIO Initrd。 


代码[4]:如果不是CPIO-Initrd,则就是Image-Initrd,将其内容保存到文件/initrd.image中。在根文件系统中
创建文件/initrd.image。 


代码[5]:这里是对Image-Initrd提供支持的,将内存中的initrd赋值到initrd.image中,以释放内存空间。 


代码[6]:释放Initrd所占用的内存空间。 


另外,如果要支持Image-Initrd的话,必须要配置CONFIG_BLK_DEV_RAM,配置的方法上面已经讲过。 
下面接着来分析函数kernel_init static int __init kernel_init(void * unused) 





… 


do_basic_setup(); --------------------->在这里执行了 populate_rootfs,即检测initrd的类型并且将其释放到目录中


/* 


* check if there is an early userspace init. If yes, let it do all 


* the work 


*/ 


if (!ramdisk_execute_command)   在命令行中一般不会出现 rdinit=


ramdisk_execute_command = "/init";  所以默认是/init, 即文件系统是initramfs或者cpio-initrd


[1] if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {  
//访问若指定的/init文件不存在,则prepare_namespace,即挂载真实的文件系统


ramdisk_execute_command = NULL; 


prepare_namespace(); 





/* 


* Ok, we have completed the initial bootup, and 


* we're essentially up and running. Get rid of the 


* initmem segments and start the user-mode stuff.. 


*/ 


init_post();     


return 0; 





代码[1]:前面在对函数populate_rootfs进行分析的时候已经知道,对于initramfs和cpio-initrd的情况,都会将
文件系统(其实是一个VFS)解压到根文件系统。如果虚拟文件系统中存在ramdisk_execute_command指定的文件
则直接转向init_post()来执行,否则执行函数prepare_namespace()。 


3. 根文件系统的挂载
从上面的代码分析中知道,对于Image-Initrd或者VFS(即InitRamfs或者CPIO-Initrd)中不存在文件
ramdisk_execute_command的情况,则执行prepare_namespace()。 


接下来看一下函数prepare_namespace()的代码: /* 


* Prepare the namespace - decide what/where to mount, load ramdisks, etc. 


*/ 


void __init prepare_namespace(void) 
//对于image-initrd,有两中挂载设备,一种是root=/dev/mtdblockxx 一种是root=/dev/ram设备





int is_floppy; 


[1] if (root_delay) { 


printk(KERN_INFO "Waiting %dsec before mounting root device...\n", 


root_delay); 


ssleep(root_delay); 





/* 


* wait for the known devices to complete their probing 





* Note: this is a potential source of long boot delays. 


* For example, it is not atypical to wait 5 seconds here 


* for the touchpad of a laptop to initialize. 


*/ 


[2] wait_for_device_probe(); 


md_run_setup(); 


[3] if (saved_root_name[0]) { 


root_device_name = saved_root_name; 


if (!strncmp(root_device_name, "mtd", 3) || 


!strncmp(root_device_name, "ubi", 3)) { 


[4] mount_block_root(root_device_name, root_mountflags); 


goto out; 





[5] ROOT_DEV = name_to_dev_t(root_device_name); 


if (strncmp(root_device_name, "/dev/", 5) == 0) 


root_device_name += 5; 





[6] if (initrd_load())      initrd_load()=1,则说明是root=/dev/mtdblockxx 否则是root=/dev/ram


goto out;  


[7] /* wait for any asynchronous scanning to complete */ 


if ((ROOT_DEV == 0) && root_wait) { 


printk(KERN_INFO "Waiting for root device %s...\n", 


saved_root_name); 


while (driver_probe_done() != 0 || 


(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0) 


msleep(100); 


async_synchronize_full(); 





is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;     


if (is_floppy && rd_doload && rd_load_disk(0)) 


ROOT_DEV = Root_RAM0;   在这里执行root=/dev/ram


mount_root(); 


out: 


[9] sys_mount(".", "/", NULL, MS_MOVE, NULL); 


[10] sys_chroot("."); 





代码[1]:资料中提到,对于将根文件系统存放到USB或者SCSI设备上的情况,Kernel需要等待这些耗费时间比较久的
        设备驱动加载完毕,所以这里存在一个Delay。 


代码[2]:从字面的意思来看,这里也是来等待根文件系统所在的设备探测函数的完成。 


代码[3]:参数saved_root_name存放的是Kernel参数root=所指定的设备文件,这点不再赘述,可以参照代码。 


代码[4]:按照资料中的解释,这里相当于将saved_root_nam指定的设备进行加载。如下面传递给
       内核的command line: CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2" 


实际上就是加载/dev/mtdblock2。 


代码[5]:参数ROOT_DEV存放设备节点号。 


代码[6]:挂载initrd,这里进行的操作相当的复杂,可以参照后续关于该函数的详细解释。 


代码[7]:如果指定mount_initrd为true,即没有指定在函数initrd_load中mount的话,则在这里重新realfs的mount操作。 


代码[9]:将挂载点从当前目录(实际当前的目录在mount_root中或者在mount_block_root中指定)移到根目录。
        对于上面的command line的话,当前的目录就是/dev/mtdblock2。 


代码[10]:将当前目录当作系统的根目录,至此虚拟系统根目录文件系统切换到了实际的根目录文件系统。 


接下来看一下函数initrd_load()的代码: int __init initrd_load(void) 





[1] if (mount_initrd) { 


[2] create_dev("/dev/ram", Root_RAM0); 


/* 


* Load the initrd data into /dev/ram0. Execute it as initrd 


* unless /dev/ram0 is supposed to be our actual root device, 


* in that case the ram disk is just set up here, and gets 


* mounted in the normal path. 


*/ 


[3] if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { 


sys_unlink("/initrd.image"); 


[4] handle_initrd(); 


return 1; 








sys_unlink("/initrd.image"); 


return 0; 







代码[1]:可以通过Kernel的参数“noinitrd“来配置mount_initrd的值,默认为1,很少看到有项目区配置该值,
        所以一般情况下,mount_initrd的值应该为1; 


代码[2]:创建一个Root_RAM0的设备节点/dev/ram; 


代码[3]:如果根文件设备号不是Root_RAM0则程序就会执行代码[4],换句话说,就是给内核指定的参数不是/dev/ram,
        例如上面指定的/dev/mtdblock2设备节点肯定就不是Root_RAM0。 


另外这行代码还将文件initrd.image释放到节点/dev/ram0,也就是对应image-initrd的操作。 


代码[4]:函数handle_initrd主要功能是执行Initrd中的linuxrc文件,并且将realfs的根目录设置为当前目录。
        其实前面也已经提到了,这些操作只对image-cpio的情况下才会去执行。 










                                                           
--------------------------------------------------------------------------------------------------------------------------
ZTEXTADDR:内核镜像加载到内存中的物理起始地址。
ZRELADDR:内核镜像解压后在内存中的起始地址。
TEXTADDR:内核启动的虚拟地址。
PHYS_OFFSET:内存中第一个bank的物理起始地址。
PAGE_OFFSET:第一个bank的虚拟起始地址。
TEXTOFFSET:内核镜像的偏移地址。


内核加载和解压
引导程序会把内核镜像加载到内存中的ZTEXTADDR位置,然后进行解压缩。解压后内核镜像的地址为ZRELADDR,也就是内核镜像启动的物理地址。ZRELADDR对应的虚拟地址为TEXTADDR。 


S3C2410/S3C2440平台中各个具体的地址值:
PHYS_OFFSET的值为30000000;
PAGE_OFFSET的值为c0000000;
TEXTOFFSET的值为8000;
ZRELADDR的值为PHYS_OFFSET加上TEXTOFFSET,即30008000;
TEXTADDR的值为PAGE_OFFSET加上TEXTOFFSET,即c0008000;


linux makefile文件决定了哪些文件需要被编译,是如何编译(编译选项如KBUILD_CFLAGS KBUILD_CPPFLAGS)的,是如何链接(链接选项LDFLAGS),链接顺序是怎么样子的
Kconfig用于配置内核,它就是各种配置界面的源文件 
 config条目:用于配置一个项,即经过make menuconfig后,会在auto.conf文件中(.config文件)生成一个CONFIG_XXX =y
 或者m或者没有该配置项
 如:config JFFS2_FS_POSIX_ACL
         BOOL "JFFS2 POSIX Access control lists"
 关于后续见笔记本    
 
     
         


















   
   


   




     

你可能感兴趣的:(kernel)