拋開uboot不談,先看看uboot給內(nèi)核傳遞的參數(shù)是什么樣的東西,在arch/arm/kernel/setup.h文件中的struct tag結(jié)構(gòu)體: struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; /* * Marvell specific */ struct tag_mv_uboot mv_uboot; // board info struct tag_board_info board_info; } u; }; Uboot傳遞給內(nèi)核的參數(shù),都是一些對(duì)一個(gè)個(gè)的設(shè)備參數(shù)的描述,用于對(duì)內(nèi)核進(jìn)行相應(yīng)的初始化,參數(shù)的具體內(nèi)容暫時(shí)無需關(guān)心,有個(gè)大致印象就行,下面一步步看內(nèi)核是怎么接收uboot參數(shù)的,在setup_arch函數(shù)中首先定義struct tag *型指針變量tags: struct tag *tags = (struct tag *)&init_tags; tags是重點(diǎn),它就是內(nèi)核接收uboot參數(shù)的東西! init_tags是個(gè)全局靜態(tài)變量,就在本文件(arch/arm/kernel/setup.c)定義如下: static struct init_tags { struct tag_header hdr1; struct tag_core core; struct tag_header hdr2; struct tag_mem32 mem; struct tag_header hdr3; } init_tags __initdata = { { tag_size(tag_core), ATAG_CORE }, { 1, PAGE_SIZE, 0xff }, { tag_size(tag_mem32), ATAG_MEM }, { MEM_SIZE, PHYS_OFFSET }, { 0, ATAG_NONE } }; 在定義結(jié)構(gòu)體init_tags的同時(shí)聲明了靜態(tài)全局變量init_tags,記住這個(gè)靜態(tài)全局變量不重要,繼續(xù)往下看,還在setup_arch函數(shù)中接下來幾行: if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); 這個(gè)是重點(diǎn),這里根據(jù)情況判斷tags接收uboot參數(shù)的來源,if中說明來源是uboot傳遞,else if說明是由內(nèi)核部分的代碼(即代碼寫死,不是從uboot),這個(gè)是重點(diǎn): 先看看這個(gè)__atags_pointer是什么: __atags_pointer,定義在匯編文件arch/arm/kernel/head-common.S的__switch_data子程序(可參考“ARM架構(gòu)內(nèi)核啟動(dòng)分析-head.S(1.4、stext分析之打開MMU并跳到start kernel”一文)),在內(nèi)核代碼源頭stext運(yùn)行前,由arm寄存器R2保存要傳遞給內(nèi)核的參數(shù)的地址,當(dāng)stext運(yùn)行到子程序__switch_data時(shí),定義變量__atags_pointer保存這個(gè)地址,即__atags_pointer保存了uboot要傳遞給內(nèi)核的參數(shù)的地址,所以這里讓tags獲取__atags_pointer的轉(zhuǎn)換后的虛擬地址,即可訪問。 有的時(shí)候,可能不需要從uboot傳遞參數(shù)到內(nèi)核,也就是說這些參數(shù)寫死在內(nèi)核里而不是在uboot里,那么就可以寫死,寫死是寫死在machine_desc變量的boot_params成員,可以把地址值賦給這個(gè)成員,即可訪問。 一般來說還是從__atags_pointer傳遞,即從uboot傳遞的幾率比較大,我手頭這個(gè)marvell設(shè)備就是如此,畢竟在內(nèi)核代碼的machine_desc變量寫死本質(zhì)還是uboot里存放這些參數(shù)的物理地址,一旦uboot里這些參數(shù)的物理地址變動(dòng),同時(shí)還要改machine_desc變量的這個(gè)值,不如自動(dòng)傳遞方便。 搞明白了參數(shù)的傳遞,下面看內(nèi)核代碼如何使用這些參數(shù),接著setup_arch函數(shù)往下看,如下: if (tags->hdr.tag != ATAG_CORE) convert_to_tag_list(tags); if (tags->hdr.tag != ATAG_CORE) tags = (struct tag *)&init_tags; 結(jié)合本文最前面的struct tags結(jié)構(gòu)體,它的第一個(gè)成員如下: struct tag_header hdr 記住,struct tag_header的成員tag,如果不等于宏ATAG_CORE的值(在arch/arm/kernel/setup.h定義),說明是舊式的參數(shù),需要轉(zhuǎn)換成新格式的參數(shù),所以調(diào)用函數(shù)convert_to_tag_list;如果轉(zhuǎn)換后依然是舊格式的,那么就沒法使用這個(gè)參數(shù)了,改為使用默認(rèn)參數(shù),就是本文開始時(shí)描述的那個(gè)不重要的init_tags靜態(tài)全局變量。 convert_to_tag_list這個(gè)函數(shù)內(nèi)容可以不看,因?yàn)橐粋€(gè)正常的uboot是不會(huì)傳遞舊格式的參數(shù),這里重在理解道理即可。 后面的fixup部分,其實(shí)可以不關(guān)注了,fixup用于內(nèi)核代碼固定的寫死meminfo,而不是由uboot傳遞參數(shù)配置meminfo,應(yīng)該說很少有使用fixup成員寫死meminfo的情況。 言歸正傳,看下面的代碼: if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags); } 首先全局變量meminfo在這時(shí)候還沒有被初始化,其用于指示物理內(nèi)存bank個(gè)數(shù)的成員nr_banks肯定為0,繼續(xù)往下看,save_atags將把tags里的內(nèi)容拷貝給全局變量atags_copy,重點(diǎn)是下面的parse_tags: 觀察parse_tags函數(shù)的實(shí)現(xiàn),這時(shí)必須要搞懂tags指針變量里面的內(nèi)容是什么了,tags指針變量實(shí)際上指向了多個(gè)的struct tags型變量,觀察struct tags結(jié)構(gòu)體即可發(fā)現(xiàn),它是一個(gè)struct tags_header加一個(gè)聯(lián)合的結(jié)構(gòu),這就很明確了,再看parse_tags的實(shí)現(xiàn),它就是對(duì)每一個(gè)它指向的struct tags型變量調(diào)用函數(shù)parse_tag,這個(gè)函數(shù)實(shí)際解析struct tags型變量,終于到重點(diǎn)了,看它的實(shí)現(xiàn): 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; } 先看“extern struct tagtable __tagtable_begin, __tagtable_end;”,可參考本人博客前面的描述內(nèi)核匯編啟動(dòng)階段的文章,可以立即感覺到這兩個(gè)東西是在鏈接腳本vmlinux.lds.S中定義的,并且是卡住某一代碼段便于給C函數(shù)調(diào)用; 首先看這兩個(gè)變量在哪里定義,卡住了哪部分內(nèi)容: __tagtable_begin = .; *(.taglist.init) __tagtable_end = .; 可見是卡住了“.taglist.init”段的全部?jī)?nèi)容,那這個(gè)段里是什么東西呢?在這里,arch/include/asm/setup.h文件中,有這么些內(nèi)容: #define __tag __used __attribute__((__section__(".taglist.init"))) #define __tagtable(tag, fn) \ static struct tagtable __tagtable_##fn __tag = { tag, fn } 第一行的意思是:宏__tag,定義為__used __attribute__((__section__(".taglist.init"))); 第二、三行的意思是:定義宏__tagtable(tag, fn)為static struct tagtable __tagtable_##fn __tag = { tag, fn },意思是:在".taglist.init"段中,創(chuàng)建struct tagtable的靜態(tài)變量__tagtable_##fn(fn是什么由參數(shù)指定,后面的__tag起變量描述符的作用,它真正指定了這個(gè)靜態(tài)變量是在".taglist.init"段中鏈接),并賦初值,賦的值就是宏函數(shù)定義時(shí)的參數(shù)tag和fn。 說白了就是,以__tagtable(tag, fn)形式定義的宏,實(shí)際是在".taglist.init"段中,創(chuàng)建struct tagtable的靜態(tài)變量,并賦初值給這個(gè)變量,賦的值就是這個(gè)宏的兩個(gè)參數(shù)tag和fn。 在arch/arm/mach-XXX/core.c、arch/arm/mm/init.c、arch/arm/kernel/setup.c文件中,定義了很多__tagtable(XXX,XXX)這樣的宏,這些宏干什么的?就是解析uboot傳遞給內(nèi)核的參數(shù)用的(當(dāng)然不僅它們解析,后面還有別的代碼解析),現(xiàn)在回到parse_tag函數(shù),應(yīng)該很好理解了,它對(duì)傳遞進(jìn)來的參數(shù),利用__tagtable_begin和__tagtable_end,使用所有定義的__tagtable(tag, fn)對(duì)其進(jìn)行遍歷,一旦發(fā)現(xiàn)__tagtable(tag, fn)的tag和傳遞進(jìn)來的參數(shù)的參數(shù)頭的tag(即struct tags_header的tag成員)一樣,就用該__tagtable(tag, fn)的fn即解析函數(shù)進(jìn)行解析,這里的marvell設(shè)備的實(shí)現(xiàn)一共定義了11個(gè)這樣的__tagtable(tag, fn),就不一列出了,這里具體描述兩個(gè)比較重要的: parse_tag_mem32: 這個(gè)是初始化meminfo即讓內(nèi)核了解設(shè)備的物理內(nèi)存情況的,它將調(diào)用函數(shù)arm_add_memory,參數(shù)就是uboot傳遞的相應(yīng)參數(shù)的mem結(jié)構(gòu),包括start成員和size成員即物理內(nèi)存起始地址和內(nèi)存大小,arm_add_memory這個(gè)函數(shù)比較簡(jiǎn)單,它把這兩個(gè)參數(shù)寫進(jìn)meminfo的bank中,并更新benk個(gè)數(shù)值nr_banks。對(duì)于很多小型arm嵌入式應(yīng)用,一般只有一個(gè)物理內(nèi)存,也就只調(diào)用該函數(shù)一次。重中之重的meminfo就是在這里初始化的! parse_tag_cmdline 它把uboot傳遞的“命令行參數(shù)”賦給靜態(tài)全局變量default_command_line,這個(gè)變量是干什么用的?它很重要,先看看這里的marvell設(shè)備的情況: ttyS0,115200 ubi.mtd=3 root=ubi0:rootfs rootfstype=ubifs mtdparts=nand_flash:0x200000@0x0(uboot)ro,0x200000@0x200000(env)rw,0x500000@0x400000(kernel0)ro,0x2800000@0x900000(rootfs0)ro,0x500000@0x3100000(kernel1)ro,0x2800000@0x3600000(rootfs1)ro,0x2200000@0x5e00000(config)rw rw) 想必會(huì)明白這個(gè)default_command_line是干什么的了,這里只是拷貝到這個(gè)變量中去,后面還有其他代碼進(jìn)一步解析它。 看完這一部分,接下來是另一部分的解析,接著setup_arch函數(shù)往下看: parse_cmdline(cmdline_p, from); form指針變量在setup_arch函數(shù)一開始就指向了default_command_line,到這應(yīng)該能感覺到現(xiàn)在要解析default_command_line了,看parse_cmdline的實(shí)現(xiàn): 無需仔細(xì)看里邊關(guān)于用空格定位每一個(gè)參數(shù)等細(xì)節(jié),重點(diǎn)是看它是由誰來解析的,可以看到兩個(gè)變量__early_begin和__early_end,和上面類似,故伎重演,在vmlinux.lds.S中它倆卡住了“.early_param.init”段,這個(gè)段所鏈接的內(nèi)容同樣是在arch/arm/kernel/setup.h中規(guī)定,道理完全一樣具體就不描述了,最終是由__early_param宏解析。具體都在解析些什么內(nèi)容即解析后如何操作,也都是一些和設(shè)備參數(shù)相關(guān)的諸如內(nèi)存、緩存等等的信息,在這里也不詳細(xì)描述了。 總之,對(duì)于uboot向內(nèi)核傳遞參數(shù),需要理解的一個(gè)是內(nèi)核對(duì)uboot所傳參數(shù)的接收、解析的機(jī)制和方法,另外需要了解下所解析和操作的內(nèi)容,尤其對(duì)于一些重要參數(shù)典型如內(nèi)存參數(shù)的解析和操作需要細(xì)致理解。 |
|