编译内核之启动流程分析

源码配置编译:

    本文主要是针对linux-2.6.22.6来进行配置编译和源码的分析,这里韦老师已经做好补丁文件和配置文件,直接在此基础上进行配置编译

解压缩

        tar xjf  压缩文件

打补丁

             patch -p1 <  补丁文件  (这里韦老师已经为我们做好了补丁文件)

            

配置

    linux有两种配置方式

      1. 通过make xxx_defconfig命令在默认的配置上进行修改,然后再输入make menuconfig配置菜单

            make s3c2410_defconfig (根据自己的板子选择)

            配置完后可以看到打印信息中最后出现“configuration written to .config”,表示将所有config配置都写入到.config文件中

            make menuconfig  (启动X-windows图形配置界面,选择一些内核的配置选项)

            

     2.使用厂家提供的配置config_ok文件 (我们这里使用的就是韦老师配置好的config_ok文件)

        在linux-2.6.22.6目录下,使用cp config_ok  .config命令将config_ok复制成新的.config文件(通过 ls -la 命令可以查看隐藏文件.config),

        然后执行make menuconfig 回去读取.config文件

问题:  make menuconfig时出现段错误         

             Makefile:14: recipe for target 'menuconfig' failed    make[1]: *** [menuconfig] Segmentation fault (core dumped)   

              Makefile:417: recipe for target 'menuconfig' failed       make: *** [menuconfig] Error 2

        解决办法:由于ubuntu的版本过新不能编译老的版本所以需要安装一些库

               sudo apt-get install libncurses*

               

        make menuconfig时提示makefile错误

            linux-2.6.22.6/Makefile:416: *** mixed implicit and normal rules: deprecated syntax

            linux-2.6.22.6/Makefile:1449: *** mixed implicit and normal rules: deprecated syntax

        解决办法:系统的make工具太新,make的旧版规则已经无法兼容新版

                config %config: scripts_basic outputmakefile FORCE   改为%config: scripts_basic outputmakefile FORCE

                / %/: prepare scripts FORCE   改为%/: prepare scripts FORCE

编译

        make uImage

 

 

Makefile分析:

    要想快速的分析源码,一般通过分析配置编译的过程快速切入以了解实际的启动流程,我们之前通过make s3c2410_defconfig命令对内核源码进行配置过后会生成一个.config文件,首先需要分析.config文件的内容。

    

       CONFIG_DM9000=y        配置项  =y表示选中,会被编译进内核

 

    以dm9000为例,在源码中搜索可以知道,在makefile中使用过CONFIG_DM9000,但是这个是由autoconf.h文件中定义的,然后我们平常的配置CONFIG_DM9000=y 就是生成了在autoconf.h中将CONFIG_DM9000定义了

drivers/net/dm9ks.c:445:#define CONFIG_DM9000_BASE            0x20000000
drivers/net/dm9ks.c:446:    iobase = ioremap(CONFIG_DM9000_BASE, 0x100000) + 0x300;
drivers/net/Makefile:197:obj-$(CONFIG_DM9000) += dm9dev9000c.o
drivers/net/Makefile:198:#obj-$(CONFIG_DM9000) += dm9000.o
drivers/net/Makefile:199:#obj-$(CONFIG_DM9000) += dm9ks.o
include/linux/autoconf.h:145:#define CONFIG_DM9000 1
include/config/auto.conf:144:CONFIG_DM9000=y

CONFIG_DM9000=m就会被编辑成模块,这些.o文件就会被连接成.ko文件

#obj-$(CONFIG_DM9000) += dm9000.o
#obj-$(CONFIG_DM9000) += dm9ks.o

   所以当我们执行make uImage时就执行了.config文件中的内容,然后就生成了include/linux/autoconf.h 和 include/config/auto.conf 这两个文件

 

对于所有的makefile,内核中有一个文件专门进行了讲解 linux-2.6.22.6\Documentation\kbuild\makefiles.txt    有时间可以仔细细看

然后我们方便分析,直接从编译命令 make uImage入手,我们发现在顶层的makefile中并没有发现uImage的踪影,相反在arch/arm/makefile文件中有

zImage Image xipImage bootpImage uImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

所以推测在顶层的makefile中肯定包含了arm目录下的makefile,然后查找可得

ARCH        ?= arm

include $(srctree)/arch/$(ARCH)/Makefile

 

然后我们从arm下可知uImage依赖于vmlinux,在顶层中,第一个目标就是vmlinux

# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules
# Defaults vmlinux but it is usually overridden in the arch makefile
all: vmlinux

 

我们再找到vmlinux的依赖

vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) $(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/$(ARCH)/kernel/vmlinux.lds

 

head-y在顶层里没有定义,在arm下有个定义

head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o    MMUEXT没有定义就变成了空的 arch/arm/kernel/head.o
init-y        := init/
init-y        := $(patsubst %/, %/built-in.o, $(init-y))     --->    init-y = init//built-in.o
core-y        := usr/
core-y        += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
core-y        := $(patsubst %/, %/built-in.o, $(core-y))        ---> core-y = usr/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 block/built-in.o    
libs-y        := lib/
libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y        := $(libs-y1) $(libs-y2)                        --->  libs-y = lib/lib.a   lib/built-in.o
drivers-y    := drivers/ sound/
drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))   --->  drivers-y    := drivers/ sound/built-in.o
net-y        := net/
net-y        := $(patsubst %/, %/built-in.o, $(net-y))   --->  net-y        := net/built-in.o

 

想知道上面的这些文件如何编译到内核,直接编译内核看它的编译过程

    make uImage v=1    (v=1 表示更详细的打印编译信息)然后查看编译流程,找到有用的信息:

  arm-linux-ld -EL  -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds    ->    $(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    ->    head-y
    arch/arm/mach-s3c2410/built-in.o  arch/arm/mach-s3c2400/built-in.o
        arch/arm/mach-s3c2412/built-in.o  arch/arm/mach-s3c2440/built-in.o  arch/arm/mach-s3c2442/built-in.o  arch/arm/mach-s3c2443/built-in.o  arch/arm/nwfpe/built-in.o
        arch/arm/plat-s3c24xx/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  block/built-in.o  
            arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  net/built-in.o --end-group .tmp_kallsyms2.o    -> libs-y

     然后通过上面的对比,就能直接对应起来,不需要再去找每个依赖的下一级依赖,从这里我们知道了我们的链接脚本位于arch/arm/kernel/vmlinux.lds ,第一个链接的文件arch/arm/kernel/head.s

 

 

 

启动流程分析:

    从head.s入手,

  .section ".text.head", "ax"
    .type    stext, %function
ENTRY(stext)
    msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//确保进入管理模式(设置    CPSR寄存器)
                        @ and irqs disabled  //禁止中断
    mrc    p15, 0, r9, c0, c0        @ get processor id  //读取CPU ID(CP15寄存器),存入r9寄存器
    bl    __lookup_processor_type        @ r5=procinfo r9=cpuid// 调用函数,输入参数r9=cpuid,返回值r5=procinfo 判断是否支持cpu
    movs    r10, r5                @ invalid processor (r5=0)?//如果不支持当前cpu,则返回值r5=0
    beq    __error_p            @ yes, error 'p' //如果r5=0,则打印错误
    bl    __lookup_machine_type        @ r5=machinfo // 调用函数,返回值r5=machinfo,判断是否支持单板
    movs    r8, r5                @ invalid machine (r5=0)?//如果不支持当前机器,则返回值r5 =0
    beq    __error_a            @ yes, error 'a' // 如果r5=0,则打印错误
    bl    __create_page_tables//创建页表      建立虚拟地址和物理地址的映射关系为了启动mmu

上面主要是实现几个流程

 

                   1. 判断是否支持处理器                                                    ->     bl    __lookup_processor_type

                   2. 判断是否支持单板(根据uboot传入的机器id)      ->     bl    __lookup_machine_type

__lookup_machine_type的实现:

= (0xc0000000) + 0x00008000;            
                                            
text.head : {                               
_stext = .;                                  
_sinittext = .;                             
*(.text.head)                                
                                        
                                        
init : { /* Init code and data    */         
*(.init.text)                            
_einittext = .;                          
__proc_info_begin = .;                   
*(.proc.info.init)                       
__proc_info_end = .;                     
__arch_info_begin = .;   /*链接脚本中__arch_info_begin的地址(虚拟地址)*/              
*(.arch.info.init)                       
__arch_info_end = .;         
3:    .long    .                    @表示当前这行代码编译链接后的虚拟地址
    .long    __arch_info_begin
    .long    __arch_info_end 


 __lookup_machine_type: 
    adr    r3, 3b            //r3等于3b的地址 物理地址       __arch_info_begin
    ldmia    r3, {r4, r5, r6} //从r3里读取内容到r4,r5,r6,r4 = "." r5 ="__arch_info_begin" r6 ="__arch_info_end"
    sub    r3, r3, r4            @ get offset between virt&phys//物理地址和虚拟地址的偏移量
    add    r5, r5, r3            @ convert virt addresses to//计算r5,r6对应的物理地址
    add    r6, r6, r3            @ physical address space
1:    ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine type    //这下面就是开始不断比较传入的机器id是否能够被改内核支持
    teq    r3, r1                @ matches loader number? //r1存放的就uboot存进来的机器id
    beq    2f                @ found     从*(.arch.info.init)段中对比id,找到就退出
    add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc    没找到继续往下找直到地址到末尾__arch_info_end
    cmp    r5, r6
    blo    1b
    mov    r5, #0                @ unknown machine
2:    mov    pc, lr    //比较完成就会返回直接调用处

这种将机器id如何存放到链接脚本中指定的段中,我们可以从他的实现来分析,搜索.arch.info.init可以知道

#define MACHINE_START(_type,_name)            \
static const struct machine_desc __mach_desc_##_type    \
__used                            \
__attribute__((__section__(".arch.info.init"))) = {    \
    .nr        = MACH_TYPE_##_type,        \
    .name        = _name,


#define MACHINE_END                \
};

然后找到调用MACHINE_START宏的函数,这里就是2440,可以看出来每种单板都会调用该函数

arch.h (E:\print\linux-2.6-100ask\linux-2.6.22.6\include\asm-arm\mach) line 50 : #define MACHINE_START(_type,_name)            \
mach-amlm5900.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 247 : MACHINE_START(AML_M5900, "AML_M5900")
mach-anubis.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 317 : MACHINE_START(ANUBIS, "Simtec-Anubis")
mach-bast.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 500 : MACHINE_START(BAST, "Simtec-BAST")
mach-h1940.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 239 : MACHINE_START(H1940, "IPAQ-H1940")
mach-n30.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 121 : MACHINE_START(N30, "Acer-N30")
mach-nexcoder.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 148 : MACHINE_START(NEXCODER_2440, "NexVision - Nexcoder 2440")
mach-osiris.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 343 : MACHINE_START(OSIRIS, "Simtec-OSIRIS")
mach-otom.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 115 : MACHINE_START(OTOM, "Nex Vision - Otom 1.1")
mach-qt2410.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 433 : MACHINE_START(QT2410, "QT2410")
mach-rx3715.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 231 : MACHINE_START(RX3715, "IPAQ-RX3715")
mach-smdk2410.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 198 : MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
mach-smdk2440.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2440) line 339 : MACHINE_START(S3C2440, "SMDK2440")
mach-vr1000.c (E:\print\linux-2.6-100ask\linux-2.6.22.6\arch\arm\mach-s3c2410) line 422 : MACHINE_START(VR1000, "Thorcom-VR1000")

我们将2440的宏展开来分析

#define MACHINE_START(_type,_name)            \                 MACHINE_START(S3C2440, "SMDK2440")
static const struct machine_desc __mach_desc_##_type    \           /* Maintainer: Ben Dooks  */
__used                            \                                 .phys_io    = S3C2410_PA_UART,
__attribute__((__section__(".arch.info.init"))) = {    \            .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .nr        = MACH_TYPE_##_type,        \                        .boot_params    = S3C2410_SDRAM_PA + 0x100,
    .name        = _name,                                       
                                                                    .init_irq    = s3c24xx_init_irq,
                                                                    .map_io        = smdk2440_map_io,
#define MACHINE_END                \                                .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,


                    };

     这样之后S3C2410所有的信息都存在了machine_desc结构体中,然后这个结构体被定义了一个属性,段强制设成了".arch.info.init",存放在链接脚本中的指定地址中,所以对于不同的开发板传入各自的machine_desc结构体就好了,然后将各自结构体的文件编进内核就能支持该单板

                                 3. 建立页表            ->          bl    __create_page_table                               

                                4. 使能MMU         ->               adr    lr, __enable_mmu

                                5. 复制数据段,清bss等      ->    __mmap_switched

__mmap_switched:
    adr    r3, __switch_data + 4


    ldmia    r3!, {r4, r5, r6, r7}
    cmp    r4, r5                @ Copy data segment if needed    //复制数据段
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b


    mov    fp, #0                @ Clear BSS (and zero fp)    //清除bss段
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b


    ldmia    r3, {r4, r5, r6, sp}    //设置栈指针
    str    r9, [r4]            @ Save processor ID        //保存CPU_ID到processor_id变量
    str    r1, [r5]            @ Save machine type        //保存机器id
    bic    r4, r0, #CR_A            @ Clear 'A' bit
    stmia    r6, {r0, r4}            @ Save control register values  //跳转到kernel
    b    start_kernel

 

                                   6. 跳转到start_kernel,进入内核启动的第二阶段     ->   b    start_kernel

 

第二阶段函数解析:

        start_kernel里主要是一系列的初始化函数,里面跟单板挂钩比较重要的是

                                   编译内核之启动流程分析_第1张图片

setup_arch(&command_line)函数分析:

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


    setup_processor(); // 进行处理器的相关一些设置 获得proc_info_list结构    就是cpu相关的信息
    mdesc = setup_machine(machine_arch_type);  // 获得开发板的machine_desc结构           就是单板相关的信息
    machine_name = mdesc->name;


    if (mdesc->soft_reboot)   // 定义了bootloader传入参数的地址
        reboot_setup("s");


    if (mdesc->boot_params)
        tags = phys_to_virt(mdesc->boot_params);  // 这个地址就是tag列表的首地址    0x30000100


    /*
     * 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)  //如果在内核中已经定义了meminfo结构
            squash_mem_tags(tags);  // 则忽略内存tag
        parse_tags(tags);             // 解释每个tag      调用tagtable里面的函数,对各种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;


    memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
    boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
    parse_cmdline(cmdline_p, from);//解析uboot传进来的命令行,如果没有传进来就使用默认的default_command_line
    paging_init(&meminfo, mdesc);  // 重新初始化页表

大致流程如下所示:

                                                 编译内核之启动流程分析_第2张图片

parse_tags(tags)函数的实现,主要是依赖于“.taglist.init”段预先已经存放了各个tag的函数,然后通过for循环逐步调用各个tag函数的实现,以parse_tag_mem32为例:

#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }

static int __init parse_tag_mem32(const struct tag *tag)
{
    if (meminfo.nr_banks >= NR_BANKS) {
        printk(KERN_WARNING
               "Ignoring memory bank 0x%08x size %dKB\n",
            tag->u.mem.start, tag->u.mem.size / 1024);
        return -EINVAL;
    }
    arm_add_memory(tag->u.mem.start, tag->u.mem.size);    //增加内存的描述信息
    return 0;
}

__tagtable(ATAG_MEM, parse_tag_mem32);

 __attribute__是一个特殊的GNU关键字,在这里的用法是:告诉编译器需要将其作用的函数或者数据放入”.taglist.init”这一段区域,也就是说由__tagtable定义的函数将会被放在section“.taglist.init” 这个区域,所以在parse_tag()做for循环调用的时候,必然会调用到parse_tag_mem32()。其中一点要注意的是,parse_tag_mem32()的TAG为ATAG_MEM, 所以在boot传过来的TAG参数如果是要定义为memory参数的话TAG一定要定义为ATAG_MEM,否则parse_tag_mem32()是无法解析到的!

 

console_init()函数分析:

    call = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }

这里只要是调用__con_initcall_start和__con_initcall_end之间定义的每个函数,这些函数都存在连接脚本中的 *(.con_initcall.init)中,搜索脚本文件

  __con_initcall_start = .;
   *(.con_initcall.init)
  __con_initcall_end = .;

 

rest_init()函数分析:

    rest_init()主要功能是挂接根文件系统然后执行应用程序,具体流程如下

rest_init()
    kernel_init();
        prepare_namespace();
          mount_root();  //挂接根文件系统

        init_post(); //打开dev/console,执行应用程序

 

启动参数的分析:

    以挂接根文件系统为例,分析内核是怎么处理这些命令行参数的。例如在命令行中有个参数为"root=/dev/mtdblock3",那么内核是如何将该参数记录并执行的呢?

    在rest_init()函数中有个prepare_namespace()主要的任务就是挂接根文件系统的,代码如下

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 */
    while (driver_probe_done() != 0)
        msleep(100);


    md_run_setup();


    if (saved_root_name[0]) {    //查看是否存在root这个参数(其实这个参数就是我们读取的命令行)
        root_device_name = saved_root_name;
        if (!strncmp(root_device_name, "mtd", 3)) {
            mount_block_root(root_device_name, root_mountflags);    //去该地址的地方去挂接
            goto out;
        }
        ROOT_DEV = name_to_dev_t(root_device_name);
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }


    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;


    if (initrd_load())
        goto out;


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


    mount_root();  //挂接根文件系统

     这里最终实现的是 mount_root()函数,实现挂接根文件系统,在这之前我们可以看到还有一些参数需要设置,例如从哪个地址挂接,这个就跟我们要分析的传入的命令行参数有关了,在mount_root()之前我们可以看到saved_root_name[0]就是我们之前保存的参数,然后对该参数判断再执行该参数,所以对别的参数来说肯定也是类型,先对保存的参数做判断,然后再执行,那么我们是如何实现对参数的保存的呢,我们可以搜索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参数时,__setup("root=", root_dev_setup)函数就会去执行root_dev_setup函数,然后这个root_dev_setup(char *line)函数就会保存这个参数到saved_root_name,__setup("root=", root_dev_setup)的实现跟之前proc_info_list结构体和machine_desc结构体一样,__setup("root=", root_dev_setup)肯定也是将该参数放进一个结构体中,来看下__setup的具体实现

#define __setup(str, fn)                    \                //存在include/linux/init.h
    __setup_param(str, fn, fn, 0)


#define __setup_param(str, unique_id, fn, early)            \
    static char __setup_str_##unique_id[] __initdata = str;    \
    static struct obs_kernel_param __setup_##unique_id    \
        __attribute_used__                \
        __attribute__((__section__(".init.setup")))    \        //强制段属性
        __attribute__((aligned((sizeof(long)))))    \
        = { __setup_str_##unique_id, fn, early }

——————>   __setup_##unique_id = {str,fn,0}

通过*(.init.setup),可以知道这个肯定又是链接脚本中定义的一个段,在链接脚本中搜索有

  __setup_start = .;
   *(.init.setup)
  __setup_end = .;

好了这些就是我们root参数的保存,在*(.init.setup)段中定义了一个obs_kernel_param类型的结构体,里面的参数就是{"root=",root_dev_setup,0},但是这里我们只是定义了,还没有执行root_dev_setup函数去保存我们的参数至saved_root_name中,所以搜索__setup_start我们可以来看到是如何调用*(.init.setup)段内的函数,我们可以搜到在start_kernel()中有:

    

   parse_early_param()

        do_early_param

        从setup_start到setup_end 调用early(1)函数

static int __init do_early_param(char *param, char *val)
{
    struct obs_kernel_param *p;


    for (p = __setup_start; p < __setup_end; p++) {
        if (p->early && strcmp(param, p->str) == 0) {
            if (p->setup_func(val) != 0)
                printk(KERN_WARNING
                       "Malformed early option '%s'\n", param);
        }
    }
    /* We accept everything at this stage. */
    return 0;
}

当early为1的时候才会去调用p->setup_func(val)函数,这里就是root_dev_setup函数,保存root参数

 

 unknown_bootoption

        obsolete_checksetup

        从setup_start到setup_end 调用非early(0)函数

static int __init obsolete_checksetup(char *line)
{
    struct obs_kernel_param *p;
    int had_early_param = 0;


    p = __setup_start;
    do {
        int n = strlen(p->str);
        if (!strncmp(line, p->str, n)) {
            if (p->early) {
                /* 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))
                return 1;
        }

当early为0的时候才会去调用p->setup_func函数,这里就是root_dev_setup函数,保存root参数

    调用完成后后面再去执行rest_init()函数去挂接根文件系统,此时root参数已经完全保存完毕,在执行prepare_namespace()函数时就可以直接调用saved_root_names数组里面的值了,其他环境参数的实现过程也是大致的过程

 

mtdblock3分区在代码中已经写死,就跟uboot传给内核的环境参数一样

你可能感兴趣的:(kernel)