zoukankan      html  css  js  c++  java
  • Linux移植之tag参数列表解析过程分析

    Linux移植之内核启动过程start_kernel函数简析中已经指出了start_kernel函数的调用层次,这篇主要是对具体的tag参数列表进行解析。

    1、内存参数ATAG_MEM参数解析

    2、命令行参数ATAG_CMDLINE解析,以传入的命令参数bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0为列:

      1)、noinitrd参数解析过程,当你没有使用ramdisk启动系统的时候,你需要使用noinitrd这个参数,但是如果使用了的话,就需要指定initrd=r_addr,size, r_addr表示initrd在内存中的位置,size表示initrd的大小。

      2)、root=/dev/mtdblock3参数解析过程

      3)、init=/linuxrc参数解析过程

      4)、 console=ttySAC0参数解析过程

    start_kernel
        setup_arch                  //解析UBOOT传入的启动参数
        setup_command_line //解析UBOOT传入的启动参数
        do_early_param         //解析early参数,uboot中没传这个参数
        unknown_bootoption//解析到了命令行参数,saved_root_name在这块初始化
        console_init();//控制台初始化
        rest_init
            kernel_thread
                kernel_init
                    prepare_namespace   //解析命令行参数解析成功挂接在哪个分区
                        mount_root//挂接根文件系统
                    init_post
                        //执行应用程序

    1、内存参数ATAG_MEM参数解析

    看到archarmkernelSetup.c文件,在setup_arch函数里看到如下几行,首先根据内核启动时第一阶段得到的machine_arch_type,取得mdesc结构体,这个结构体在Linux移植之内核启动过程引导阶段分析已经介绍过,这里主要关心的是boot_params参数,里面存放的是tag参数列表的存放地址,然后将取得的物理地址转换为虚拟地址供后面使用tag。

    776    console_init();//控制台初始化
    777    archarmkernelSetup.c
    778
    779    setup_processor();//设置处理器相关的一些设置
    780    mdesc = setup_machine(machine_arch_type);//获得开发板的machine_desc结构
    781    machine_name = mdesc->name;//取得开发板的名称
    782
    783    if (mdesc->soft_reboot)
    784        reboot_setup("s");
    785
    786    if (mdesc->boot_params)//确定uboot传入的启动参数的地址
    787        tags = phys_to_virt(mdesc->boot_params);//将启动参数的物理地址转换为虚拟地址

    setup_arch函数继续往下看

    109    static struct meminfo meminfo __initdata = { 0, };
    
    
    798    if (tags->hdr.tag == ATAG_CORE) {//ATAG_CORE为tag标记列表的开始
    799        if (meminfo.nr_banks != 0)//如果已经在内核中定义了meminfo结构
    780            squash_mem_tags(tags);//则忽略内存tag
    781        parse_tags(tags);//解释每个tag
    782    }

    其中meminfo就是处理完ATAG_MEN参数后,将里面的内容放去meninfo中,它的结构定义在includeasm-armSetup.h 中

    207    struct meminfo {
    208        int nr_banks;
    209        struct membank bank[NR_BANKS];
    210    };

    接着继续看parse_tags函数,它也位于archarmkernelSetup.c中

    733    static void __init parse_tags(const struct tag *t)
    734    {
    735        for (; t->hdr.size; t = tag_next(t))//循环取出tag列表,然后处理
    736            if (!parse_tag(t))                //处理取出的tag列表
    737                printk(KERN_WARNING
    738                    "Ignoring unrecognised tag 0x%08x
    ",
    739                    t->hdr.tag);
    740    }

    接着分析parse_tag函数,它同样位于archarmkernelSetup.c中

    715    static int __init parse_tag(const struct tag *tag)
    716    {
    717        extern struct tagtable __tagtable_begin, __tagtable_end;
    718        struct tagtable *t;
    719
    720        for (t = &__tagtable_begin; t < &__tagtable_end; t++)//从.taglist.init段找出符合的处理tag列表的结构
    721            if (tag->hdr.tag == t->tag) {//找到符合的tag
    722                t->parse(tag);//调用相应的处理tag的函数处理
    723                break;
    724            }
    725    
    726        return t < &__tagtable_end;//t<&__tagtable_end说明找到了tag
    727    }

    parse_tag会从.taglist.init段找出符合的tag,然后调用相应的处理函数处理。tagtable 的结构如下,它位于includeasm-armSetup.h 中

    171    struct tagtable {
    172        __u32 tag;//处理的tag值
    173        int (*parse)(const struct tag *);//处理函数
    174    };

    我们需要的是处理ATAG_MEN参数的函数,搜搜ATAG_MEN,在archarmkernelSetup.c中找到了parse_tag_mem32处理ATAG_MEN参数的函数。它的功能就是取出内存的开始地址与大小信息后存放在meminfo结构中

    614    static int __init parse_tag_mem32(const struct tag *tag)
    615    {
    616        if (meminfo.nr_banks >= NR_BANKS) {
    617            printk(KERN_WARNING
    618                   "Ignoring memory bank 0x%08x size %dKB
    ",
    619                tag->u.mem.start, tag->u.mem.size / 1024);
    620            return -EINVAL;
    621        }
    622        arm_add_memory(tag->u.mem.start, tag->u.mem.size);//取出内存的开始地址与大小信息后存放在meminfo结构中
    623        return 0;
    624    }
    625
    626    __tagtable(ATAG_MEM, parse_tag_mem32);//解析ATAG_MEM列表,函数为parse_tag_mem32

    再看到__tagtable,同样位于includeasm-armSetup.h中。主要就是将tagtable 这个结构体放在.taglist.init段

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

    到这里就分析完了tag列表中ATAG_MEM参数的处理,接下去分析ATAG_CMDLINE参数的处理。

    2、命令行参数ATAG_CMDLINE解析

    找到与ATAG_CMDLINE参数的过程与前面ATAG_MEM参数一样的流程就不分析了,直接找到处理ATAG_CMDLINE参数的函数,它位于archarmkernelSetup.c中。它只是简单的将tag->u.cmdline.cmdline的内容复制到default_command_line中。

    702    static int __init parse_tag_cmdline(const struct tag *tag)
    703    {
    704        strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);//简单的将tag的内容复制到字符串default_command_line中
    705        return 0;
    706    }
    707
    708    __tagtable(ATAG_CMDLINE, parse_tag_cmdline);

    接着看到default_command_line,它定义在archarmkernelSetup.c中,它的大小为1024字节

    114    static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

    它初始化为CONFIG_CMDLINE,位于includelinuxAutoconf.h中

    374    #define CONFIG_CMDLINE "root=/dev/hda1 ro init=/bin/bash console=ttySAC0"

    所以拷贝之后

    default_command_line[] = "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"

    继续往下看default_command_line,在archarmkernelSetup.c下的setup_arch函数中:其中parse_cmdline是对位于.early_param.init段的内容进行前期的初始化。相应的命令有:cachepolicy=、nocache、nowb、ecc=、initrd=、mem=等等,我们的参数没有涉及到这类命令,所以不去细细的分析这个函数了。

    809    memcpy(boot_command_line, from, COMMAND_LINE_SIZE);//form指向default_command_line,将default_command_line中的内容拷贝到boot_command_line中
    810    boot_command_line[COMMAND_LINE_SIZE-1] = '';//以''结束字符串
    811    parse_cmdline(cmdline_p, from);//对位于.early_param.init段命令进行一些先期的处理
    812    paging_init(&meminfo, mdesc);//重新初始化页表
    813    request_standard_resources(&meminfo, mdesc);//资源的初始化

    接着看到paging_init这个函数,这个函数调用了meminfo这个从ATAG_MEM取得的参数以及mdesc我们按照以下调用层次分析

    paging_init
        devicemaps_init //设备maps初始化
            mdesc->map_io //调用map_io函数初始化

    在archarmmach-s3c2440Mach-smdk2440.c中找到mdesc这个结构

    339    MACHINE_START(S3C2440, "SMDK2440")
    340        /* Maintainer: Ben Dooks <ben@fluff.org> */
    341        .phys_io    = S3C2410_PA_UART,
    342        .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    343        .boot_params    = S3C2410_SDRAM_PA + 0x100,
    344
    345        .init_irq    = s3c24xx_init_irq,
    346        .map_io        = smdk2440_map_io,
    347        .init_machine    = smdk2440_machine_init,
    348        .timer        = &s3c24xx_timer,
    349    MACHINE_END

    其中smdk2440_map_io就等要调用的函数,它同样位于archarmmach-s3c2440Mach-smdk2440.c下,可以看到这里修改过晶振的值。

    324    static void __init smdk2440_map_io(void)
    325    {
    326        s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
    327        s3c24xx_init_clocks(12000000);//根据开发板合适的晶振配置
    328        s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
    329    }

    继续分析UBOOT传入的需要解析的参数:

    1)、noinitrd参数解析过程

    当没有使用ramdisk启动系统的时候,你需要使用noinitrd这个参数,但是如果使用了的话,就需要指定initrd=r_addr,size, r_addr表示initrd在内存中的位置,size表示initrd的大小。看到代码里面,位于initDo_mounts_initrd.c下,可以看到处理函数只是简单的将mount_initrd 置为0,说明不支持ramdisk启动。

    19    static int __init no_initrd(char *str)
    20    {
    21        mount_initrd = 0;
    22        return 1;
    23    }
    24
    25    __setup("noinitrd", no_initrd);

    接着分析一下__setup的定义,看到includelinuxInit.h里面有它的定义。

    160    #define __setup_param(str, unique_id, fn, early)            
    161        static char __setup_str_##unique_id[] __initdata = str;    
    162        static struct obs_kernel_param __setup_##unique_id    
    163            __attribute_used__                
    164            __attribute__((__section__(".init.setup")))    
    165            __attribute__((aligned((sizeof(long)))))    
    166            = { __setup_str_##unique_id, fn, early }
    167
    168    #define __setup_null_param(str, unique_id)            
    169        __setup_param(str, unique_id, NULL, 0)
    170
    171    #define __setup(str, fn)                    
    172        __setup_param(str, fn, fn, 0)

    先看__setup_param。它定义了两个参数,一个是char型的字符串__setup_str_##unique_id,另外一个为obs_kernel_param 结构体,它位于includelinuxInit.h。obs_kernel_param 结构体位于

    .init.setup段,它的str参数即为__setup_str_##unique_id。__setup宏调用__setup_param传入两个参数str与fn,代表命令行名字与处理函数。
    148    struct obs_kernel_param {
    149        const char *str;
    150        int (*setup_func)(char *);
    151        int early;
    152    };

    2)、root=/dev/mtdblock3参数解析过程

    回到initMain.c 中的start_kernel函数继续分析

    525    setup_arch(&command_line);//返回的command_line是还未处理的命令行参数存放的首地址
    526    setup_command_line(command_line);//static_command_line存放未处理的命令行参数,saved_command_line存放所有的命令行参数
    
    
    544    printk(KERN_NOTICE "Kernel command line: %s
    ", boot_command_line);//打印命令行参数
    545        parse_early_param();//一些前期代码的初始化
    546        parse_args("Booting kernel", static_command_line, __start___param,
    547               __stop___param - __start___param,
    548               &unknown_bootoption);//后续的命令处理

    其中parse_early_param函数是对一些early属性的命令后做解析,它位于.early_param.init段,包括:cachepolicy=、nocache、nowb、ecc=、initrd=、mem=等等,我们的参数没有涉及到这类命令,所以不去细细的分析这个函数了。

    重点关注parse_args函数,先分析函数的参数:

    static_command_line                         :存放未处理的命令行参数首地址

    __start___param                                 : 内核参数的存放地址,它处于__param段

    __stop___param - __start___param  : 内核参数大小

    unknown_bootoption                          : 处理函数

    接着看到parse_args函数内部阶段,它位于kernelParams.c 下,可以看到在这里会将所有命令行处理完成。

    144    while (*args) {//循环处理剩余的命令行,直到全部处理完成
    145        int ret;
    146        int irq_was_disabled;
    147
    148        args = next_arg(args, &param, &val);//找出下一个命令行参数*param为命令名称,*val为参数值
    149        irq_was_disabled = irqs_disabled();
    150        ret = parse_one(param, val, params, num, unknown);//处理

    接着看到处理函数parse_one,它位于kernelParams.c 下。这里面还判断了一个内核的参数,我们传入的参数没有内核参数,内核参数存在于__param段,有:nousb、block2mtd_setup等等,我们传入的命令行参数没有内核参数,所以不关心

    49    static int parse_one(char *param,
    50                 char *val,
    51                 struct kernel_param *params, 
    52                 unsigned num_params,
    53                 int (*handle_unknown)(char *param, char *val))
    54    {
    55        unsigned int i;
    56
    57        /* Find parameter */
    58        for (i = 0; i < num_params; i++) {//从__param段找出与命令行参数相同的名字
    59            if (parameq(param, params[i].name)) {
    60                DEBUGP("They are equal!  Calling %p
    ",
    61                       params[i].set);
    62                return params[i].set(val, &params[i]);//如果是内核的参数,那么直接传给内核参数,然后返回。
    63            }
    64        }
    65    
    66        if (handle_unknown) {//如果不是内核的参数,并且处理函数存在
    67            DEBUGP("Unknown argument: calling %p
    ", handle_unknown);
    68            return handle_unknown(param, val);//调用处理函数处理
    69        }
    70
    71        DEBUGP("Unknown argument `%s'
    ", param);
    72        return

    接着看到handle_unknown函数,即unknown_bootoption函数,它位于initMain.c中,截取其中的一段程序

    260        /* Change NUL term back to "=", to make "param" the whole string. */
    261        if (val) {//如果val不为空,做一些处理
    262            /* param=val or param="val"? */
    263            if (val == param+strlen(param)+1)
    264                val[-1] = '=';
    265            else if (val == param+strlen(param)+2) {
    266                val[-2] = '=';
    267                memmove(val-1, val, strlen(val)+1);
    268                val--;
    269            } else
    270                BUG();
    271        }
    272    
    273        /* Handle obsolete-style parameters */
    274        if (obsolete_checksetup(param))
    275            return 0;

    接着看到obsolete_checksetup函数,它同样位于initMain.c中,这个函数大致的意思就是在.init.setup中找到符合的命令行参数,如果不是前期已经处理的参数(即early值为0的参数,那么调用处理函数处理它。它由__setup宏定义或者__setup_null_param宏定义(这两个宏定义前面已经介绍过了),搜索一下这两个宏定义,发现了__setup("root=", root_dev_setup);、__setup("init=", init_setup);、__setup("console=", console_setup);都在这里面被处理。

    190    static int __init obsolete_checksetup(char *line)
    191    {
    192        struct obs_kernel_param *p;
    193        int had_early_param = 0;
    194    
    195        p = __setup_start;//.init.setup的首地址
    196        do {
    197            int n = strlen(p->str);
    198            if (!strncmp(line, p->str, n)) {//在.init.setup中寻找相符的命令行参数
    199                if (p->early) {//如果early大于0,那么这个参数在前面已经处理过了
    200                    /* Already done in parse_early_param?
    201                     * (Needs exact match on param part).
    202                     * Keep iterating, as we can have early
    203                     * params and __setups of same names 8( */
    204                    if (line[n] == '' || line[n] == '=')
    205                        had_early_param = 1;
    206                } else if (!p->setup_func) {//如果处理函数不存在,则报错
    207                    printk(KERN_WARNING "Parameter %s is obsolete,"
    208                           " ignored
    ", p->str);
    209                    return 1;
    210                } else if (p->setup_func(line + n))//调用处理函数处理
    211                    return 1;
    212            }
    213            p++;
    214        } while (p < __setup_end);
    215
    216        return had_early_param;
    217    }

    接着分析__setup("root=", root_dev_setup)宏,它位于kernelPrintk.c下,可以看到它调用的是root_dev_setup函数来处理root=参数,接着看root_dev_setup函数

    211    static int __init root_dev_setup(char *line)
    212    {
    213        strlcpy(saved_root_name, line, sizeof(saved_root_name));
    214        return 1;
    215    }
    216
    217    __setup("root=", root_dev_setup);

    可以看到它的处理函数直接将root命令行参数拷贝到saved_root_name里,接着搜索一下在哪里调用的saved_root_name。找到了在initDo_mounts.c 中的prepare_namespace函数用到了它,这个函数的作用是挂接根文件系统的。列出部分代码:如何挂接根文件系统后面说明

    430        if (saved_root_name[0]) {
    431            root_device_name = saved_root_name;//将saved_root_name赋给root_device_name
    432            if (!strncmp(root_device_name, "mtd", 3)) {
    433                mount_block_root(root_device_name, root_mountflags);
    434                goto out;
    435            }
    436            ROOT_DEV = name_to_dev_t(root_device_name);
    437            if (strncmp(root_device_name, "/dev/", 5) == 0)
    438                root_device_name += 5;
    439        }

    3)、init=/linuxrc参数解析过程

    前面已经分析了命令行参数的提取过程,这里直接看到宏定义__setup("init=", init_setup)。处理init=参数的是init_setup函数,来到Init_setup函数,它位于

    initMain.c 中

    315    static int __init init_setup(char *str)
    316    {
    317        unsigned int i;
    318
    319        execute_command = str;
    320        /*
    321         * In case LILO is going to boot us with default command line,
    322         * it prepends "auto" before the whole cmdline which makes
    323         * the shell think it should execute a script with such name.
    324         * So we ignore all arguments entered _before_ init=... [MJ]
    325         */
    326        for (i = 1; i < MAX_INIT_ARGS; i++)
    327            argv_init[i] = NULL;
    328        return 1;
    329    }
    330    __setup("init=", init_setup);

    可以看到init_setup函数直接将init=的命令行参数拷贝到execute_command 中,搜索execute_command ,在initMain.c函数下找到了init_post函数,这是start_kernel函数最后调用的一个函数rest_init建立的一个进程函数。取出部分内容,可以看到execute_command是内核运行的根文件系统上的第一个进程

    774        if (execute_command) {//如果存在execute_command进程
    775            run_init_process(execute_command);运行execute_command进程
    776            printk(KERN_WARNING "Failed to execute %s.  Attempting "
    777                        "defaults...
    ", execute_command);
    778        }

    4)、 console=ttySAC0参数解析过程

    看到宏定义__setup("console=", console_setup);它位于kernelPrintk.c 中,console=参数调用的是console_setup处理它

    655    static int __init console_setup(char *str)
    656    {
    657        char name[sizeof(console_cmdline[0].name)];
    658        char *s, *options;
    659        int idx;
    660
    661        /*
    662         * Decode str into name, index, options.
    663         */
    664        if (str[0] >= '0' && str[0] <= '9') {//如果以数字0-9开头
    665            strcpy(name, "ttyS");
    666            strncpy(name + 4, str, sizeof(name) - 5);
    667        } else {
    668            strncpy(name, str, sizeof(name) - 1);//将str拷贝到name中,去除结束符
    669        }
    670        name[sizeof(name) - 1] = 0;
    671        if ((options = strchr(str, ',')) != NULL)//如果参数中存在,的话。说明带波特率参数
    672            *(options++) = 0;
    673    #ifdef __sparc__
    674    if (!strcmp(str, "ttya"))
    675            strcpy(name, "ttyS0");
    676        if (!strcmp(str, "ttyb"))
    677            strcpy(name, "ttyS1");
    678    #endif
    679        for (s = name; *s; s++)
    680            if ((*s >= '0' && *s <= '9') || *s == ',')
    681                break;
    682        idx = simple_strtoul(s, NULL, 10);//取出波特率参数,转换成整形
    683        *s = 0;
    684
    685        add_preferred_console(name, idx, options);//将参数保存在console_cmdline中
    686        return 1;
    687    }
    688    __setup("console=", console_setup);
    struct console_cmdline
    {
        char    name[8];            /* Name of the driver        *///设备名称
        int    index;                /* Minor dev. to use        *///设备编号
        char    *options;            /* Options for the driver   *///设备选项
    };

    可以看到console_setup函数也只是将console=的参数解析后保存在console_cmdline而已,接着搜索console_cmdline。在start_kernel中有一个console_init函数,找到它的原型,在driverscharTty_io.c 中找到了它

    void __init console_init(void)
    {
        initcall_t *call;
    
        /* Setup the default TTY line discipline. */
        (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);//设置默认控制台
    
        /*
         * set up the console device so that later boot sequences can 
         * inform about problems etc..
         */
        call = __con_initcall_start;
        while (call < __con_initcall_end) {//在.con_initcall.init段,寻找存在的控制台
            (*call)();
            call++;
        }
    }

    可以看到这个函数的作用是调用.con_initcall.init中的所有存在函数,来注册控制台。在includelinuxInit.h 中找到了定义.con_initcall.init段的宏。

    140    #define console_initcall(fn) 
    141        static initcall_t __initcall_##fn 
    142        __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

    搜索console_initcall宏。在driversserialS3c2410.c 中找到了这个宏定义的函数

    1950    console_initcall(s3c24xx_serial_initconsole);

    截取s3c24xx_serial_initconsole函数内容

    1994        s3c24xx_serial_init_ports(info);//控制台端口初始化
    1995
    1996        register_console(&s3c24xx_serial_console);//注册控制台

    可以看到register_console函数中,s3c24xx_serial_console参数结构体的信息为

    1901    static struct console s3c24xx_serial_console =
    1902    {
    1903        .name        = S3C24XX_SERIAL_NAME,//控制台名称ttySAC
    1904        .device        = uart_console_device,//以后使用/dev/console时,用来构造设备节点
    1905        .flags        = CON_PRINTBUFFER,//控制台可以之前,printk已经在缓冲区打印了,CON_PRINTBUFFER表示可以打印以前的信息了
    1906        .index        = -1,                 //表示可以匹配任意序列号
    1907        .write        = s3c24xx_serial_console_write,//打印函数
    1908        .setup        = s3c24xx_serial_console_setup.//设置函数
    1909    };

    在看到register_console函数,它位于kernelPrintk.c 中,截取函数部分内容。

    964        for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
    965                i++) {
    966            if (strcmp(console_cmdline[i].name, console->name) != 0)//新注册的控制台与console_cmdline是否匹配ttySAC0
    967                continue;
    968            if (console->index >= 0 &&
    969                console->index != console_cmdline[i].index)
    970                continue;
    971            if (console->index < 0)//可以匹配任意的编号,比如是ttySAC0/1/2
    972                console->index = console_cmdline[i].index;
    973            if (console->setup &&
    974                console->setup(console, console_cmdline[i].options) != 0)
    975                break;
    976            console->flags |= CON_ENABLED;
    977            console->index = console_cmdline[i].index;
    978            if (i == selected_console) {
    979                console->flags |= CON_CONSDEV;
    980                preferred_console = selected_console;
    981            }
    982            break;
    983        }
    
    
    1007        console->next = console_drivers->next;//将控制台放入console_drivers链表
    1008        console_drivers->next = console;

    以上内容概括为将新注册的控制台s3c24xx_serial_console与console_cmdline比较。如果比较成功,则继续向下运行。到了后面1007行、1008行则是将

    s3c24xx_serial_console控制台放入console_drivers链表中。以后的prink信息就会从这个控制台输出。

    到这里uboot传入的tag列表参数全部解析完成。

  • 相关阅读:
    SharePoint Server 2007 Beta2 Technical Refresh 安装提示
    SharePoint Server安全工具:Forefront for SharePoint
    7月6日,深圳OTEC成员会议
    数据字典存储事件实例
    C#学习:事件
    发布符合 .NET Framework 准则的事件
    ASP.NET缓存:用户控件缓存
    C#中用ToString方法格式化时间
    C#学习:委托
    实现接口事件
  • 原文地址:https://www.cnblogs.com/andyfly/p/9415167.html
Copyright © 2011-2022 走看看