004@ kernel 的配置和编译总结 分析1

文章详细介绍了Linux内核配置与编译的流程,包括解压、打补丁、配置方法(使用默认配置或厂家提供配置)、编译步骤及生成vmlinux与uImage的过程。还深入解析了配置项意义、编译选项对内核的影响及编译时的makefile配置细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:

关于内核的配置和编译和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   宏定义。

内核究竟就如何检查是否是它支持的机器的呢?实际上每个机器都会在  /arch/arm/mach-xxxx/smdk-xxxx.c  文件中有个描述特定机器的数据结构,如下

    

MACHINE_START(S3C2440,"SMDK2440") 

       /* Maintainer: Ben Dooks<ben@fluff.org> */  
       .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  

   之后展开为
   static const 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进行比较,若相等,则返回machinene_desc结构的地址

四: linux内核的启动过程分析,第二阶段 。

现在我们来分析内核启动的第二阶段:在  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"
关于后续见笔记本   

    
        









  
  

  

 

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值