内核启动的汇编阶段——head.S文件
以下內容源于朱有鵬嵌入式課程的學習,如有侵權,請告知刪除。
匯編階段主要是arch/arm/kernel/目錄下的head.S文件,主要完成以下內容:
(1)校驗啟動合法性;(CPU ID,機器碼,uboot給內核的傳參格式)
(2)建立段式映射的頁表并開啟MMU以方便使用內存;
(3)構建C運行環境,跳入C階段。
1、內核運行的物理地址與虛擬地址
(1)KERNEL_RAM_VADDR(VADDR就是virtual address),這個宏定義了內核運行時的虛擬地址,值為0xC0008000。
(2)KERNEL_RAM_PADDR(PADDR就是physical address),這個宏定義內核運行時的物理地址,值為0x30008000。
(3)因此,內核運行的物理地址是0x30008000,對應的虛擬地址是0xC0008000。
2、內核的真正入口
(1)__HEAD定義了段名為.head.text的段。
(2)ENTRY(stext)表明內核的真正入口。
(3)uboot啟動內核后,實際調用zImage前面的那段未經壓縮的解壓代碼,解壓代碼運行時先將zImage后面的部分解壓開,然后再去調用運行真正的內核入口(即這里)。
(4)內核啟動需要一定的先決條件,這個條件由啟動內核的bootloader(比如uboot)來構建保證。
(5)ARM體系中,函數調用時實際是通過寄存器傳參的(函數調用時傳參有兩種設計:一種是寄存器傳參,另一種是棧內存傳參)。
- uboot中最后theKernel (0, machid, bd->bi_boot_params);執行內核時,實際把0放入r0中,machid放入到了r1中,bd->bi_boot_params放入到了r2中。
- ARM的這種處理技巧剛好滿足了kernel啟動的條件和要求。
(6)此時MMU是關閉的,因此硬件上需要的是物理地址。
- 但是內核是一個整體(zImage)只能被連接到一個地址(不能分散加載),這個連接地址肯定是虛擬地址。
- 因此head.S文件中尚未開啟MMU之前的代碼必須是位置無關碼,而且其中涉及到操作硬件寄存器等時必須使用物理地址。
3、__lookup_processor_type與__lookup_machine_type
(1)cp15協處理器的c0寄存器中讀取出硬件的CPU ID號,然后調用__lookup_processor_type來進行合法性檢驗。
- 如果合法則繼續啟動,如果不合法則停止啟動,轉向__error_p啟動失敗。
(2)__lookup_processor_type檢驗cpu id合法性的方法
- 內核會維護一個本內核支持的CPU ID號碼的數組,然后該函數將從硬件中讀取到的cpu id號碼和數組中存儲的各個id號碼依次對比,如果沒有一個相等則不合法,如果有一個相等的則合法。
(3)內核啟動時設計這個校驗,也是為了內核啟動的安全性著想。
(4)__lookup_machine_type函數的設計理念和思路和上面校驗cpu id的函數一樣的,不同之處是本函數校驗的是機器碼。
4、__vet_atags
(1)該函數的設計理念和思路和上面2個一樣,用來校驗uboot給內核的傳參ATAGS格式是否正確。
- 這里說的傳參指的是uboot通過tag給內核傳的參數(主要是板子的內存分布memtag、uboot的bootargs)。
(2)uboot給內核傳參的部分如果不對,會導致內核啟動不起來。譬如uboot的bootargs設置不正確,則內核可能就會不啟動。
5、__create_page_tables
(1)此函數用來建立頁表。
(2)linux內核本身被連接在虛擬地址處,因此kernel希望盡快建立頁表并且啟動MMU進入虛擬地址工作狀態。
(3)kernel建立頁表分為2步。
- 第一步,kernel先建立一個段式頁表(和uboot中建立的頁表一樣,頁表以1MB為單位來區分)。此函數就是建立段式頁表的。段式頁表本身比較好建立(段式頁表1MB一個映射,4GB空間需要4096個頁表項,每個頁表項4字節,因此一共需要16KB內存來做頁表),但不能精細管理內存;
- 第二步,建立一個細頁表(4kb為單位的細頁表),然后啟用新的細頁表,并廢除第一步建立的段式映射頁表。
(4)內核啟動的早期建立段式頁表,并在內核啟動早期使用;內核啟動的后期再次建立細頁表并啟用。等內核工作起來后,就只有細頁表了。
6、__switch_data 函數指針數組
(1)建立段式頁表后進入__switch_data部分,它是一個函數指針數組。
(2)分析得知下一步要執行__mmap_switched函數。
- 復制數據段、清除bss段(目的是構建C語言運行環境)。
- 保存起來cpu id號、機器碼、tag傳參的首地址。
- b start_kernel跳轉到C語言運行階段。
補充
1、Makefile分析
(1)Makefile中剛開始定義了kernel的內核版本號。這個版本號在模塊化驅動安裝時會需要用到。
(2)在make編譯內核時,可以通過命令行給內核makefile傳參。譬如make O=xxx可以指定到另外一個單獨文件夾下編譯。
(3)kernel的頂層Makefile中定義了2個變量,一個是ARCH,一個是CROSS_COMPILE。
- ARCH決定當前配置編譯的路徑,譬如ARCH = arm的時候,將來在源碼目錄下去操作的arch/arm目錄。
- CROSS_COMPILE用來指定交叉編譯工具鏈的路徑和前綴。
- CROSS_COMPILE = xxx,ARCH = xxx,O=xxx,這些都可以在make時,通過命令行傳參的方式傳給頂層Makefile。
- 因此makefile可以什么都不改,而是在命令行里面輸入相應的參數(因為用了?=(如果定義了,則使用定義的,否則用默認的)),即輸入make O=/tmp/mykernel ARCH=arm CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
2、鏈接腳本分析
(1)分析連接腳本,找到整個程序的entry。
(2)kernel的連接腳本并不是直接提供的,而是提供了一個匯編文件vmlinux.lds.S,然后在編譯的時候再去編譯這個匯編文件得到真正的鏈接腳本vmlinux.lds。
(3)vmlinux.lds.S在arch/arm/kernel/目錄下。
(4)為什么linux kernel不直接提供vmlinux.lds?
- .lds文件中只能寫死,不能用條件編譯。
- 在kernel中鏈接腳本時,有條件編譯的需求(但是lds格式又不支持)。
- kernel工作者把vmlinux.lds寫成一個匯編文件,然后匯編器處理的時候順便把條件編譯給處理了,得到一個不需要條件編譯的vmlinux.lds。
- 從vmlinux.lds中ENTRY(stext)可以知道入口符號是stext。
- 在SI中搜索這個符號,發現arch/arm/kernel/目錄下的head.S和head-nommu.S中都有。
- head.S是啟用了MMU情況下的kernel啟動文件,相當于uboot中的start.S。head-nommu.S是未使用mmu情況下的kernel啟動文件。
總結
以上是生活随笔為你收集整理的内核启动的汇编阶段——head.S文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: abaqus结果名称
- 下一篇: docker配置容器运行jar包