了解了kernel啟動以前的匯編之后我們來看看正式的c語言啟動代碼,也就是我們的start_kernel函數了。start_kernel相當大,里面每一個調用到的函數都足夠我們傷腦筋了,這里只是淺嘗輒止的描述一下函數的功能,從而對kernel啟動的過程有一個比較直觀的了解。很多函數真正理解需要對linux相關體系有很深的了解。 說實話啟動的代碼看到現在唯一的感覺就是kernel的全局變量實在太多了,要了解一個過程跟蹤一個變量的值的變化相當痛苦啊,不過耐心看下來,收獲還是比較豐富的,對很多概念都有了一個比較直觀的理解。閑話就不多說了,直接來上代碼~~ smp_setup_processor_id(); //這個函數現在是空的; 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(); //使用arm cpsid i指令來禁止IRQ early_boot_irqs_off(); early_init_irq_lock_class(); /* 基本上面幾個函數就是初始化lockdep和cgroup,然后禁止IRQ,為后續的運行創造條件 */ lock_kernel(); /* 看這個函數的之前我們首先來了解一段知識,linux kernel默認是支持preemption(搶占)的。在SMP環境下為了實現kernel的鎖定,kernel使用了一個BKL(big kernel lock)的概念,在初始化的過程中先鎖定這個BKL,然后再繼續進行其他啟動或者初始化過程,這樣就可以防止啟動過程中被中斷,執行到res_init以后,kernel會釋放這個鎖,這樣就確保了整個start_kernel過程都不會被搶占或者中斷。由此我們可以看到這主要是針對多處理器而言的,實際上如果只有一個處理器的話它也不會被中斷或者被搶占。 */ /* 下面我們來看看這個函數的執行過程,在這里面能學到很多東西的: int depth = current->lock_depth+1; 這個current實際上是一個宏,定義在arch/arm/include/asm/current.h里面,它實際是一個task_struct的結構體,這個結構體描述了這個task的一系列信息(task應該是linux進程調度的一個基本單位?)。linux下每個進程都有一個相對應的task_struct,這個task_struct有幾個我們經常能看到的信息,一個就是PID,然后就是comm進程的名字,然后就是mm_struct,它定義了跟這個進程相關的所有申請的memory的管理。嵌入式物聯網等系統學習請加企鵝意義氣嗚嗚吧久零就易,這個curren_thread_info()也是個很有意思的函數,直接讀取SP來獲得當前線程的結構體信息thread_info。thread_info和task_struct這兩個結構體應該就代表了當前線程的所有信息。 初始化的lock_depth是-1,只有init task的lock_depth是0。 if (likely(!depth)) __lock_kernel(); 這里判斷是不是init task,如果是就會執行__lock_kernel();這個__lock_kernel首先禁止搶占,然后試著獲得BKL,如果成功則直接返回,如果不成功首先判斷是否禁止搶占成功了,如果成功了就用自旋鎖去鎖BKL。如果禁止搶占沒有成功則在搶占有效的情況下去等待BKL,直到獲得BKL。因為QC的片子不是SMP,所有這里第一次try的時候就直接成功了。 current->lock_depth = depth; 這個就沒什么好說的了 */ /* 基本上來說這個lock_kernel就是禁止搶占,然后獲得BKL,干了這么件事 */ tick_init(); //和時鐘相關的初始化,好像是注冊notify事件,沒有仔細研究過 boot_cpu_init(); //這個實際上是在SMP環境下選擇CPU,這里直接CPUID選擇的是0號cpu page_address_init(); //初始化high memory,在arm環境下實際上這個函數是空的,也就是說arm不支持high memory printk(KERN_NOTICE); printk(linux_banner); //這里的KER_NOTICE是字符串<5>,不太明白它的意思。。。后面的linux_banner定義在kernel/init/version.c里面,這里的printk是門高深的學問,以后看console的時候會仔細分析 setup_arch(&command_line); /* 這是一個重量級的函數了,會比較仔細地分析一下,主要完成了4個方面的工作,一個就是取得MACHINE和PROCESSOR的信息然或將他們賦值給kernel相應的全局變量,然后呢是對boot_command_line和tags接行解析,再然后呢就是memory、cach的初始化,最后是為kernel的后續運行請求資源。 */ /* 我們來仔細看看這個函數的實現: setup_processor(); 這個函數首先從arm寄存器里面取得cpu ID,然后調用lookup_processor_type來取得proc_info_list這個結構體。這個過程實際上和我們在head-common.S里面的過程是一樣的,不知道這里為什么不直接去讀switch_data里面已經保存好的數據反而又查詢一遍是為什么?取得proc_info_list以后,將里面的內容一個個賦值給相應的全局變量,然后將CPU的信息打印出來。然后它會從arm寄存器里面獲得cache的信息,并將cache的信息打印出來。最后它會調用cpu_proc_init()的函數,這個函數實際上定義在proc-v6.S里面,沒有做任何事情。 mdesc = setup_machine(machine_arch_type); 首先這個machine_arch_type定義在生成的./include/asm-arm/mach-types.h里面,這個setup_macine實際上也是和上面的processor類似,都是調用head-common.S里面的函數,根據machine ID來獲得Machine的信息,并將Machine name打印出來。 if (mdesc->soft_reboot) reboot_setup("s"); 設置reboot方式,默認是硬啟動; 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設置了初始化參數,將參數的物理地址轉化為虛擬地址,這里有一個覆蓋,就是說可以在Machine desc里面對初始化參數的物理地址重新定位。 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格式會首先將其轉換成tag格式,如果轉換以后還是不對,則使用默認的init_tags,這里判斷的過程都是根據結構體第一個值是不是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函數指針,這里一般為空,如果不為空就會地用fixup來重新修改memory map,meminfo這個結構體定義在arch/arm/include/asm/setup.h里面,描述了內存塊的信息,內存塊的個數,每個內存塊的起始地址和大小,如果修改了memory map則需要從atag參數里面去掉bootloader傳過來的的memory map信息,然后是保存一下atag,這個保存函數在這里實際上是空的,沒有做任何操作,最后是對atag參數進行解析。這里要說明一下這里的tags實際上是一個tag的數組或者說隊列,里面有多個tag結構體,每一個結構體都是一個header加一個參數,具體的結構我們可以看看setup.h。對ATAG參數的解析全部定義在arch/arm/kernel/setup.c里面,首先在setup.c里面定義了一個類似于這樣__tagtable(ATAG_CORE, parse_tag_core)的宏,這個宏實際上是聲明了一個放在__tagtable_begin和__tagtable_end段之間結構體,這個結構體定義了這個一個參數類型,和對這個參數類型進行解析的函數。所有的參數解析我們都可以從setup.c里面找到相對應的函數,比如說對boot_commad_line的解析,從config文件得到的default_commad_line就會被ATAG里面獲得commad_line給替換掉;再比如ramdisk,就會將ATAG里面的ramdisk的信息賦值給rd_image_start, rd_size等系統的全局變量。 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; 這就就是對init_mm結構體進行賦值,具體不了解這些東西咋用的,但是就是將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參數給覆蓋,獲得command_line以后對command_line進行解析。這個解析的過程也是在setup.c里面進行的,它首先查找.early_param.init段里面注冊的結構體,通過__early_param將early_param結構體注冊到這些段里面,實際這個early_param很簡單一個就是類似于"initrd="的字符串,用來與command_line里面的字符進行匹配,如果匹配到了就執行early_param里面的那個函數,并將匹配到的字符作為參數傳給函數。舉個例子比如說我們現在的commadline里面有一句initrd=0x11000000,然后我們首先在early_param.init段里面搜索時候有沒有early_param->arg和這個initrd=匹配,找到了就執行它里面的func然后將這個initrd=的值作為參數傳進去。 paging_init(mdesc); 這個函數是個大函數,具體內容沒有仔細看,需要對arm MMU了解比較深,這里只貼一下source里面關于這個函數的注釋: /* * 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); 這個函數用來申請一些應該是內存資源,具體的內容沒有仔細研究,看不大懂。。 cpu_init(); 初始化CPU,這里主要是對arm寄存器cpsr的操作 init_arch_irq = mdesc->init_irq; system_timer = mdesc->timer; init_machine = mdesc->init_machine; 這里將體系結構相關的幾個函數,中斷,初始化,定時器之類的賦值給kernel全局變量; conswitchp = &vga_con; 這里設置了關于console的一個變量,具體不知道怎么用的,以后看console的時候再仔細分析 early_trap_init(); 不知道這個函數具體做什么用的。。。 */ /* 基本上我們可以總結出setup_arch主要將一些體系結構的相關信息來賦值給kernel的全局變量,包括cpu啊,machine啊,memory,cahce啊,然后kernel再根據這些函數或者變量來做相應的工作,而且很明顯地可以看出來這個setup_arch和前面的head.S,head-common.S,proc-v6.S,board-msm7x27.c是緊密聯系在一起的 */ |