新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > Arm linux kernel 啟動(dòng)之start_kernel

Arm linux kernel 啟動(dòng)之start_kernel

作者: 時(shí)間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
了解完kernel啟動(dòng)以前的匯編之后我們來看看正式的c語言啟動(dòng)代碼,也就是我們的start_kernel函數(shù)了。start_kernel相當(dāng)大,里面每一個(gè)調(diào)用到的函數(shù)都足夠我們傷腦筋了,我這里只是淺嘗輒止的描述一下函數(shù)的功能,從而對(duì)kernel啟動(dòng)的過程有一個(gè)比較直觀的了解。很多函數(shù)真正理解需要對(duì)linux相關(guān)體系有很深的了解,暫時(shí)沒有時(shí)間深入,留待以后了。

說實(shí)話啟動(dòng)的代碼看到現(xiàn)在唯一的感覺就是kernel的全局變量實(shí)在太多了,要了解一個(gè)過程跟蹤一個(gè)變量的值的變化相當(dāng)痛苦啊,不過耐心看下來,收獲還是比較豐富的,對(duì)很多概念都有了一個(gè)比較直觀的理解。閑話就不多說了,直接來上代碼~~

本文引用地址:http://butianyuan.cn/article/201611/317994.htm

smp_setup_processor_id();

//這個(gè)函數(shù)現(xiàn)在是空的;


lockdep_init();

//Runtime locking correctness validator, see Documentation/lockdep_design.txt

debug_objects_early_init();

cgroup_init_early();

//Control group, read Documentation/cgroup.txt

local_irq_disable();

//使用armcpsid i指令來禁止IRQ

early_boot_irqs_off();

early_init_irq_lock_class();

/* 基本上面幾個(gè)函數(shù)就是初始化lockdep和cgroup,然后禁止IRQ,為后續(xù)的運(yùn)行創(chuàng)造條件 */


lock_kernel();

/* 看這個(gè)函數(shù)的之前我們首先來了解一段知識(shí),linux kernel默認(rèn)是支持preemption(搶占)的。在SMP環(huán)境下為了實(shí)現(xiàn)kernel的鎖定,kernel使用了一個(gè)BKL(big kernel lock)的概念,在初始化的過程中先鎖定這個(gè)BKL,然后再繼續(xù)進(jìn)行其他啟動(dòng)或者初始化過程,這樣就可以防止啟動(dòng)過程中被中斷,執(zhí)行到res_init以后,kernel會(huì)釋放這個(gè)鎖,這樣就確保了整個(gè)start_kernel過程都不會(huì)被搶占或者中斷。由此我們可以看到這主要是針對(duì)多處理器而言的,實(shí)際上如果只有一個(gè)處理器的話它也不會(huì)被中斷或者被搶占。 */

/* 下面我們來看看這個(gè)函數(shù)的執(zhí)行過程,在這里面能學(xué)到很多東西的:

int depth = current->lock_depth+1;

這個(gè)current實(shí)際上是一個(gè)宏,定義在arch/arm/include/asm/current.h里面,它實(shí)際是一個(gè)task_struct的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體描述了這個(gè)task的一系列信息(task應(yīng)該是linux進(jìn)程調(diào)度的一個(gè)基本單位?)。linux下每個(gè)進(jìn)程都有一個(gè)相對(duì)應(yīng)的task_struct,這個(gè)task_struct有幾個(gè)我們經(jīng)常能看到的信息,一個(gè)就是PID,然后就是comm進(jìn)程的名字,然后就是mm_struct,它定義了跟這個(gè)進(jìn)程相關(guān)的所有申請(qǐng)的memory的管理。這個(gè)curren_thread_info()也是個(gè)很有意思的函數(shù),直接讀取SP來獲得當(dāng)前線程的結(jié)構(gòu)體信息thread_info。thread_info和task_struct這兩個(gè)結(jié)構(gòu)體應(yīng)該就代表了當(dāng)前線程的所有信息。

初始化的lock_depth是-1,只有init task的lock_depth是0。

if (likely(!depth))

__lock_kernel();

這里判斷是不是init task,如果是就會(huì)執(zhí)行__lock_kernel();這個(gè)__lock_kernel首先禁止搶占,然后試著獲得BKL,如果成功則直接返回,如果不成功首先判斷是否禁止搶占成功了,如果成功了就用自旋鎖去鎖BKL。如果禁止搶占沒有成功則在搶占有效的情況下去等待BKL,直到獲得BKL。因?yàn)镼C的片子不是SMP,所有這里第一次try的時(shí)候就直接成功了。

current->lock_depth = depth;

這個(gè)就沒什么好說的了 */

/* 基本上來說這個(gè)lock_kernel就是禁止搶占,然后獲得BKL,干了這么件事 */


tick_init();

//和時(shí)鐘相關(guān)的初始化,好像是注冊(cè)notify事件,沒有仔細(xì)研究過


boot_cpu_init();

//這個(gè)實(shí)際上是在SMP環(huán)境下選擇CPU,這里直接CPUID選擇的是0號(hào)cpu


page_address_init();

//初始化high memory,在arm環(huán)境下實(shí)際上這個(gè)函數(shù)是空的,也就是說arm不支持high memory


printk(KERN_NOTICE);

printk(linux_banner);

//這里的KER_NOTICE是字符串<5>,不太明白它的意思。。。后面的linux_banner定義在kernel/init/version.c里面,這里的printk是門高深的學(xué)問,以后看console的時(shí)候會(huì)仔細(xì)分析


setup_arch(&command_line);

/* 這是一個(gè)重量級(jí)的函數(shù)了,會(huì)比較仔細(xì)地分析一下,主要完成了4個(gè)方面的工作,一個(gè)就是取得MACHINE和PROCESSOR的信息然或?qū)⑺麄冑x值給kernel相應(yīng)的全局變量,然后呢是對(duì)boot_command_line和tags接行解析,再然后呢就是memory、cach的初始化,最后是為kernel的后續(xù)運(yùn)行請(qǐng)求資源。 */

/* 我們來仔細(xì)看看這個(gè)函數(shù)的實(shí)現(xiàn):

setup_processor();

這個(gè)函數(shù)首先從arm寄存器里面取得cpu ID,然后調(diào)用lookup_processor_type來取得proc_info_list這個(gè)結(jié)構(gòu)體。這個(gè)過程實(shí)際上和我們?cè)趆ead-common.S里面的過程是一樣的,不知道這里為什么不直接去讀switch_data里面已經(jīng)保存好的數(shù)據(jù)反而又查詢一遍是為什么?取得proc_info_list以后,將里面的內(nèi)容一個(gè)個(gè)賦值給相應(yīng)的全局變量,然后將CPU的信息打印出來。然后它會(huì)從arm寄存器里面獲得cache的信息,并將cache的信息打印出來。最后它會(huì)調(diào)用cpu_proc_init()的函數(shù),這個(gè)函數(shù)實(shí)際上定義在proc-v6.S里面,沒有做任何事情。

mdesc = setup_machine(machine_arch_type);

首先這個(gè)machine_arch_type定義在生成的./include/asm-arm/mach-types.h里面,這個(gè)setup_macine實(shí)際上也是和上面的processor類似,都是調(diào)用head-common.S里面的函數(shù),根據(jù)machine ID來獲得Machine的信息,并將Machine name打印出來。

if (mdesc->soft_reboot)

reboot_setup("s");

設(shè)置reboot方式,默認(rèn)是硬啟動(dòng);

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

else if (mdesc->boot_params)

tags = phys_to_virt(mdesc->boot_params);

這里首先判斷head-common.S里面定義的__atags_pointer是不是為空,不為空的話說明bootloader設(shè)置了初始化參數(shù),將參數(shù)的物理地址轉(zhuǎn)化為虛擬地址,這里有一個(gè)覆蓋,就是說可以在Machine desc里面對(duì)初始化參數(shù)的物理地址重新定位。

if (tags->hdr.tag != ATAG_CORE)

convert_to_tag_list(tags);

if (tags->hdr.tag != ATAG_CORE)

tags = (struct tag *)&init_tags;

首先判斷是不是正確的atag格式,如果是以前老版本的param_struct格式會(huì)首先將其轉(zhuǎn)換成tag格式,如果轉(zhuǎn)換以后還是不對(duì),則使用默認(rèn)的init_tags,這里判斷的過程都是根據(jù)結(jié)構(gòu)體第一個(gè)值是不是ATAG_CORE.

if (mdesc->fixup)

mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {

if (meminfo.nr_banks != 0)

squash_mem_tags(tags);

save_atags(tags);

parse_tags(tags);

這里首先判斷fixup函數(shù)指針,這里一般為空,如果不為空就會(huì)地用fixup來重新修改memory map,meminfo這個(gè)結(jié)構(gòu)體定義在arch/arm/include/asm/setup.h里面,描述了內(nèi)存塊的信息,內(nèi)存塊的個(gè)數(shù),每個(gè)內(nèi)存塊的起始地址和大小,如果修改了memory map則需要從atag參數(shù)里面去掉bootloader傳過來的的memory map信息,然后是保存一下atag,這個(gè)保存函數(shù)在這里實(shí)際上是空的,沒有做任何操作,最后是對(duì)atag參數(shù)進(jìn)行解析。這里要說明一下這里的tags實(shí)際上是一個(gè)tag的數(shù)組或者說隊(duì)列,里面有多個(gè)tag結(jié)構(gòu)體,每一個(gè)結(jié)構(gòu)體都是一個(gè)header加一個(gè)參數(shù),具體的結(jié)構(gòu)我們可以看看setup.h。對(duì)ATAG參數(shù)的解析全部定義在arch/arm/kernel/setup.c里面,首先在setup.c里面定義了一個(gè)類似于這樣__tagtable(ATAG_CORE, parse_tag_core)的宏,這個(gè)宏實(shí)際上是聲明了一個(gè)放在__tagtable_begin和__tagtable_end段之間結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體定義了這個(gè)一個(gè)參數(shù)類型,和對(duì)這個(gè)參數(shù)類型進(jìn)行解析的函數(shù)。所有的參數(shù)解析我們都可以從setup.c里面找到相對(duì)應(yīng)的函數(shù),比如說對(duì)boot_commad_line的解析,從config文件得到的default_commad_line就會(huì)被ATAG里面獲得commad_line給替換掉;再比如ramdisk,就會(huì)將ATAG里面的ramdisk的信息賦值給rd_image_start, rd_size等系統(tǒng)的全局變量。

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;

這就就是對(duì)init_mm結(jié)構(gòu)體進(jìn)行賦值,具體不了解這些東西咋用的,但是就是將text和data段給賦值了。

memcpy(boot_command_line, from, COMMAND_LINE_SIZE);

boot_command_line[COMMAND_LINE_SIZE-1] = /0;

parse_cmdline(cmdline_p, from);

這里的boot_command_line來自于與config文件里面的CONFIG_CMDLINE,也有可能被ATAG里面的boot參數(shù)給覆蓋,獲得command_line以后對(duì)command_line進(jìn)行解析。這個(gè)解析的過程也是在setup.c里面進(jìn)行的,它首先查找.early_param.init段里面注冊(cè)的結(jié)構(gòu)體,通過__early_param將early_param結(jié)構(gòu)體注冊(cè)到這些段里面,實(shí)際這個(gè)early_param很簡(jiǎn)單一個(gè)就是類似于"initrd="的字符串,用來與command_line里面的字符進(jìn)行匹配,如果匹配到了就執(zhí)行early_param里面的那個(gè)函數(shù),并將匹配到的字符作為參數(shù)傳給函數(shù)。舉個(gè)例子比如說我們現(xiàn)在的commadline里面有一句initrd=0x11000000,然后我們首先在early_param.init段里面搜索時(shí)候有沒有early_param->arg和這個(gè)initrd=匹配,找到了就執(zhí)行它里面的func然后將這個(gè)initrd=的值作為參數(shù)傳進(jìn)去。

paging_init(mdesc);

這個(gè)函數(shù)是個(gè)大函數(shù),具體內(nèi)容沒有仔細(xì)看,需要對(duì)arm MMU了解比較深,這里只貼一下source里面關(guān)于這個(gè)函數(shù)的注釋:

/*

* paging_init() sets up the page tables, initialises the zone memory

* maps, and sets up the zero page, bad page and bad page tables.

*/

request_standard_resources(&meminfo, mdesc);

這個(gè)函數(shù)用來申請(qǐng)一些應(yīng)該是內(nèi)存資源,具體的內(nèi)容沒有仔細(xì)研究,看不大懂。。

cpu_init();

初始化CPU,這里主要是對(duì)arm寄存器cpsr的操作

init_arch_irq = mdesc->init_irq;

system_timer = mdesc->timer;

init_machine = mdesc->init_machine;

這里將體系結(jié)構(gòu)相關(guān)的幾個(gè)函數(shù),中斷,初始化,定時(shí)器之類的賦值給kernel全局變量;

conswitchp = &vga_con;

這里設(shè)置了關(guān)于console的一個(gè)變量,具體不知道怎么用的,以后看console的時(shí)候再仔細(xì)分析

early_trap_init();

不知道這個(gè)函數(shù)具體做什么用的。。。 */

/* 基本上我們可以總結(jié)出setup_arch主要將一些體系結(jié)構(gòu)的相關(guān)信息來賦值給kernel的全局變量,包括cpu啊,machine啊,memory,cahce啊,然后kernel再根據(jù)這些函數(shù)或者變量來做相應(yīng)的工作,而且很明顯地可以看出來這個(gè)setup_arch和前面的head.S,head-common.S,proc-v6.S,board-msm7x27.c是緊密聯(lián)系在一起的 */



關(guān)鍵詞: Armlinuxkerne

評(píng)論


技術(shù)專區(qū)

關(guān)閉