1. 程式人生 > >Linux核心初始化步驟(二)

Linux核心初始化步驟(二)

參考了http://www.360doc.com/content/16/0626/14/19351147_570866376.shtml和
https://blog.csdn.net/qing_ping/article/details/17351541的內容
setup_arch()是start_kernel階段的函式,它是體系結構相關的,具體編譯哪個體系的setup_arch()函式,由頂層Makefile中的ARCH變數決定:它首先通過檢測出來的處理器型別進行處理器核心的初始化,然後通過bootmem_init()函式根據系統定義的meminfo結構進行記憶體結構的初始化,最後呼叫paging_init()開啟MMU,建立核心頁表,對映所有的實體記憶體和IO空間。

__init setup_arch(char **cmdline_p)
{
	/*核心通過machine——desc結構體來控制系統體系結構相關部分的初始化。
	machine_desc結構體通過MACHINE_START巨集來初始化,在程式碼中,通過
	呼叫setup_machine_fdt來獲取。*/
	struct machine_desc *mdesc;
	/*首先從arm暫存器裡面取得cpu ID,然後呼叫lookup_processor_type來取得proc_info_list
	這個結構體。取得proc_info_list以後,將裡面的內容一個個賦值給相應的全域性變數,然後將
	cpu的資訊打印出來。然後它會從arm暫存器裡面獲得cache的資訊,並將cache的資訊打印出來*/
	setup_processor();
	
	/*__atags_pointer,定義在彙編檔案arch/arm/kernel/head-common.S中,由arm暫存器r2儲存要
	傳遞給核心的引數的地址,定義變數__atags_pointer儲存這個地址,即__atags_pointer儲存了
	uboot要傳遞給核心引數的地址*/
	
	mdesc = setup_machine_fdt(__atags_pointer);
	if (!mdesc)
		mdesc = setup_machine_tags(machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	
/*分配可以用於DMA的區域,這部分割槽域不通過MMU進行對映*/
#ifdef CONFIG_ZONE_DMA
	if (mdesc->dma_zone_size) {
		extern unsigned long arm_dma_zone_size;
		arm_dma_zone_size = mdesc->dma_zone_size;
	}
#endif

	/*通過struct machine_desc中的soft_reboot資料來設定重啟型別*/
	if (mdesc->restart_mode)
		reboot_setup(&mdesc->restart_mode);
		
	/*通過連結指令碼中得到的Linux程式碼位置資料來初始化一個mm_struct結構體init_mm中的
	部分資料,每一個任務都有一個mm_struct結構以管理記憶體空間,init_mm是核心自身的
	mm_struct*/
	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;

	/* populate cmd_line too for later use, preserving boot_command_line */
	/*同時填充cmd_line以備後用,保護boot_command_line資料*/
	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
	*cmdline_p = cmd_line;
	
	/*處理在struct obs_kernel_param中定義的early啟動引數(主要是記憶體配置部分的引數)
	其中就分析了
[email protected]
引數初始化了struct meminfo meminfo; 同時如果有vmalloc=size引數也會初始化vmalloc_min; 這裡需要注意的是核心的cmdline中引數按照其被需要的先後,分為early和非early的。 include/linux/init.h; struct obs_kernel_param{ const char *str;//在cmdline中相應引數名 int(*setup_func)(char*);//對於此引數的專用處理函式 int early;//是否為早期需要處理的函式 }; 兩種不同的引數在核心中用了不同的巨集來定義: early:#define early_param(star,fn) \ __setup_param(str,fn,fn,1) 非early:#define __setup(str,fn) \ __setup_param(str,fn,fn,0) */ parse_early_param(); sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL); sanity_check_meminfo(); arm_memblock_init(&meminfo, mdesc); paging_init(mdesc); request_standard_resources(mdesc); if (mdesc->restart) arm_pm_restart = mdesc->restart; unflatten_device_tree(); #ifdef CONFIG_SMP if (is_smp()) smp_init_cpus(); #endif /*用於核心崩潰時的保留核心。此功能通過核心command line 引數中的“crashkernel=”保留下 來的記憶體用於主記憶體崩潰時獲取核心資訊的匯出*/ reserve_crashkernel(); /*初始化arm內部的TCM(緊耦合記憶體)*/ 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(); }

這個函式的主要作用是uboot向核心傳遞引數,下面我們就來看看這個過程。先看看uboot給核心傳遞的的引數是什麼樣的東西,在arch/arm/include/asm/setup.h檔案中的struct tag 結構體:

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;
} u;

};
uboot傳遞給核心的引數,都是一個個對裝置引數的描述,用於核心進行相應的初始化,下面一步步看核心是怎麼接收uboot引數的,在setup_machine_tags函式中首先定義struct tag*型指標變數tags:
struct tag *tags = (struct tag *)&init_tags;
tags就是核心uboot引數的東西!
init_tags是個全域性靜態變數,在/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 },
	{ 0, ATAG_NONE }
};

在定義結構體init_tags的同時聲明瞭靜態全域性變數init_tags,繼續往下看:

/*
	 * locate machine in the list of supported machines.
	 */
	for_each_machine_desc(p)
		if (nr == p->nr) {
			printk("Machine: %s\n", p->name);
			mdesc = p;
			break;
		}
	對應於啟動過程中打印出的這條語句:
	[    0.000000] Machine: DaVinci DA850/OMAP-L138/AM18x EVM

再往下看:

if (__atags_pointer)
		tags = phys_to_virt(__atags_pointer);
	else if (mdesc->atag_offset)
		tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

這裡根據情況判斷tags接收uboot引數的來源,if中說明來源是uboot傳遞,else if 說明是由核心部分的程式碼(即程式碼寫死,不是從uboot);
先看看這個__atags_pointer是什麼:
__atasg_pointer,定義在彙編檔案arch/arm/kernel/head-common.S的__switch_data子程式,在核心程式碼源頭.stext執行前,由arm暫存器r2儲存要傳遞給核心的引數的指標,當stext執行到子程式__switch_data時,定義變數__atags_pointer儲存這個地址,即__atags_pointer儲存了uboot要傳遞給核心的引數的地址,所以這裡讓tags獲取__atags_pointer轉換後的虛擬地址,即可訪問。
再接著看核心程式碼如何使用這些引數,如下:

/*
	 * 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) {
	#if defined(CONFIG_OF)
		/*
		 * If CONFIG_OF is set, then assume this is a reasonably
		 * modern system that should pass boot parameters
		 */
		early_print("Warning: Neither atags nor dtb found\n");
#endif
		tags = (struct tag *)&init_tags;
	}

結合本文最前面的struct tags結構體,它的第一個成員如下:
struct tag_header hdr;
struct tag_header hdr的成員tag,如果不等於巨集ATAG_CORE的值,說明是舊式的引數,需要轉換成新格式的引數,所以呼叫convert_to_tag_list;如果轉換後依然是舊格式的,那麼就設法使用這個引數了,改為使用預設引數,就是本文開始時描述的哪個init_tags靜態全域性變數。
因此這部分內容基本不需要看,因為一個正常的uboot是不會傳遞舊格式的引數。
後面的fixup部分,用於核心固定寫死的meminfo,而不是由uboot傳遞引數配置memeinfo,很少有使用fixup成員寫死meminfo的情況。
回到主線,看下面的程式碼:

if (tags->hdr.tag == ATAG_CORE) {
		if (meminfo.nr_banks != 0)
			squash_mem_tags(tags);
		save_atags(tags);
		parse_tags(tags);
	}

首先全域性變數meminfo在這個時候還沒有被初始化,其用於指示實體記憶體bank個數的成員nr_banks肯定為0,繼續往下看,save_atags將把tags裡的內容拷貝給全域性變數atags_copy,重點是下面的parse_tags:
觀察parse_tags函式的實現,先要搞懂tags指標變數裡面的內容是什麼,tags指標變數實際指向了多個的struct tags型變數,觀察struct tags結構體即可發現,它是一個struct tags_header加一個聯合的結構,再看parse_tags的實現,它就是對每個它指向的struct tags型變數呼叫函式parse_tag,這個函式實際解析struct tags型變數,下面是它的實現:



/*
 * Scan the tag table for this tag, and call its parse function.
 * The tag table is built by the linker from all the __tagtable
 * declarations.
 */
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;”,這是在連結指令碼/arch/arm/kernel/vmlinux.lds中定義的,並且是卡住某一程式碼段便於給C函式呼叫:
首先看這兩個變數在哪裡定義,卡住了哪部分內容:
在這裡插入圖片描述

可見是卡住了“.taglist.init”段的全部內容,下面再來分析這個段裡面包含了哪些東西,下面的內容定義在arch/arm/include/asm/setup.h檔案中:


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

第一行的意思是:巨集__tag,定義為__used attribute((section(".taglist.init")));
第二三行的意思是:定義巨集 __tagtable(tag, fn)為static const struct tagtable _tagtable##fn _tag = { tag, fn },意思是:在".taglist.init"段中,建立struct tagtagble的靜態變數__tagtable##fn(fn是什麼由引數指定,後面的__tag起變數描述符的作用,它真正指定了這個靜態變數是在“.taglist.inti"段中連結),並賦初值,賦的值就是巨集函式定義時的引數tag和fn.
說白了,就是以__tagtable(tag,fn)形式定義的巨集,實際是在”.taglist.init”段中,建立struct tagtable的靜態變數,並賦初值給這個變數,賦的值就是這個巨集的兩個引數tag和fn。
在arch/arm/kernel/setup.c、arch/arm/mm/init.c檔案中,定義了很多__tagtable(XXX,XXX)這樣的巨集,這些巨集就是解析uboot傳遞給核心的引數用的(當然不僅它們解析,後面還有別的程式碼解析),現在回到parse_tag函式,應該很好理解了,它對傳遞進來的引數,利用__tagtable_begin和__tagtable_end,使用所有的__tagtable(tag,fn)對其進行遍歷,一旦發現__tagtable(tag,fn)的tag和傳遞進來的引數的引數頭tag(即struct tags_header的tag成員)一樣,就用該__tagtable(tag,fn)的fn即解析函式進行解析,我們的omap138(davinci)裝置的實現一共定義了9個這樣的__tagtable(tag,fn),列舉如下:
在這裡插入圖片描述

下面描述兩個比較重要的函式:
parse_tag_mem32:
這個是初始化meminfo即讓核心瞭解裝置的實體記憶體情況的,它將呼叫函式arm_add_memory,引數就是uboot傳遞的相應引數的mem結構,包括start成員和size成員即實體記憶體起始地址和記憶體大小,arm_add_memory把兩個引數寫進meminfo的bank中,並更新bank個數值nr_banks.


static int __init parse_tag_mem32(const struct tag *tag)
{
	return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}

parse_tag_cmdline
它把uboot傳遞的“命令列引數”賦給靜態全域性變數default_command_line,這個變數的作用可以從下面的分析中看到,先看看這裡的davinci裝置的情況:
console=ttyS1,115200n8 [email protected] [email protected] eth=02:02:30:30:97:81 da850-panel=TL070A da850-emac=mii root=ubi0_0 ubi.mtd=4,2048 rootfstype=ubifs
再來看看這個函式的內部所呼叫的函式:

strlcpy(default_command_line, tag->u.cmdline.cmdline,
		COMMAND_LINE_SIZE);

現在就明白這個default_command_line的作用了,這裡只是將從uboot傳來的cmdline拷貝到這個變數中去,後面還有其他程式碼進一步解析它。
總之,對於uboot向核心傳遞引數,需要理解的 一個是核心對uboot所傳引數的接收,解析的機制和方法,另外需要了解下所解析和操作的內容。