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參數解析

看到arch\arm\kernel\Setup.c文件,在setup_arch函數里看到如下幾行,首先根據內核啟動時第一階段得到的machine_arch_type,取得mdesc結構體,這個結構體在Linux移植之內核啟動過程引導階段分析已經介紹過,這里主要關心的是boot_params參數,里面存放的是tag參數列表的存放地址,然后將取得的物理地址轉換為虛擬地址供后面使用tag。

776    console_init();//控制台初始化
777    arch\arm\kernel\Setup.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中,它的結構定義在include\asm-arm\Setup.h 中

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

接着繼續看parse_tags函數,它也位於arch\arm\kernel\Setup.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\n",
739                    t->hdr.tag);
740    }

接着分析parse_tag函數,它同樣位於arch\arm\kernel\Setup.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 的結構如下,它位於include\asm-arm\Setup.h 中

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

我們需要的是處理ATAG_MEN參數的函數,搜搜ATAG_MEN,在arch\arm\kernel\Setup.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\n",
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,同樣位於include\asm-arm\Setup.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參數的函數,它位於arch\arm\kernel\Setup.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,它定義在arch\arm\kernel\Setup.c中,它的大小為1024字節

114    static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;

它初始化為CONFIG_CMDLINE,位於include\linux\Autoconf.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,在arch\arm\kernel\Setup.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] = '\0';//以'\0'結束字符串
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函數初始化

在arch\arm\mach-s3c2440\Mach-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就等要調用的函數,它同樣位於arch\arm\mach-s3c2440\Mach-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的大小。看到代碼里面,位於init\Do_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的定義,看到include\linux\Init.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 結構體,它位於include\linux\Init.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參數解析過程

回到init\Main.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\n", 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函數內部階段,它位於kernel\Params.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,它位於kernel\Params.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\n",
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\n", handle_unknown);
68            return handle_unknown(param, val);//調用處理函數處理
69        }
70
71        DEBUGP("Unknown argument `%s'\n", param);
72        return

接着看到handle_unknown函數,即unknown_bootoption函數,它位於init\Main.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函數,它同樣位於init\Main.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] == '\0' || line[n] == '=')
205                        had_early_param = 1;
206                } else if (!p->setup_func) {//如果處理函數不存在,則報錯
207                    printk(KERN_WARNING "Parameter %s is obsolete,"
208                           " ignored\n", 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)宏,它位於kernel\Printk.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。找到了在init\Do_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函數,它位於

init\Main.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 ,在init\Main.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...\n", execute_command);
778        }

 

4)、 console=ttySAC0參數解析過程

看到宏定義__setup("console=", console_setup);它位於kernel\Printk.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函數,找到它的原型,在drivers\char\Tty_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中的所有存在函數,來注冊控制台。在include\linux\Init.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宏。在drivers\serial\S3c2410.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函數,它位於kernel\Printk.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列表參數全部解析完成。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM