聊聊Linux 內核入口分析
本文轉載自微信公眾號「嵌入式Linux系統開發」,作者Jasonangel 。轉載本文請聯系嵌入式Linux系統開發公眾號。
從啟動引導程序 bootloader(uboot)跳轉到 Linux 內核后,Linux 內核開始啟動,今天我們分析一下 Linux 內核啟動入口。
跳轉過去初始化肯定是在匯編文件中,根據架構可以選擇不同的平臺,這里看一下鏈接匯編文件:
linux4.14/arch/arm/kernel/vmlinux.lds.S
這里可以看到鏈接時候 Linux 入口是 stext 段,這里是啟動引導程序跳轉過來的第一段Linux 代碼:
Linux入口地址
我們先看一下入口地址的確定,同一文件。
- SECTIONS
- {
- /*
- * XXX: The linker does not define how output sections are
- * assigned to input sections when there are multiple statements
- * matching the same input section name. There is no documented
- * order of matching.
- *
- * unwind exit sections must be discarded before the rest of the
- * unwind sections get included.
- */
- /DISCARD/ : {
- *(.ARM.exidx.exit.text)
- *(.ARM.extab.exit.text)
- ARM_CPU_DISCARD(*(.ARM.exidx.cpuexit.text))
- ARM_CPU_DISCARD(*(.ARM.extab.cpuexit.text))
- ARM_EXIT_DISCARD(EXIT_TEXT)
- ARM_EXIT_DISCARD(EXIT_DATA)
- EXIT_CALL
- #ifndef CONFIG_MMU
- *(.text.fixup)
- *(__ex_table)
- #endif
- #ifndef CONFIG_SMP_ON_UP
- *(.alt.smp.init)
- #endif
- *(.discard)
- *(.discard.*)
- }
- . = PAGE_OFFSET + TEXT_OFFSET;
- .head.text : {
- _text = .;
- HEAD_TEXT
- }
這個 SECTIONS 比較長,只放一部分。在這里有個比較重要的東西:
- . = PAGE_OFFSET + TEXT_OFFSET;
這一句表示了 Linux 系統真正的啟動地址。
PAGE_OFFSET 是 Linux 內核空間的虛擬起始地址,定義在:
linux4.14/arch/arm64/include/asm/memory.h
注意,這里的地址都很重要,很多地方會用到。當然,這里的地址可能會隨著 Linux 內核版本的不同和硬件的不同,會變化。這里沒有一個具體的數,因為 VA_BITS 中的數字是可選的,大家可以根據自己的平臺算一下。
TEXT_OFFSET 定義在:
linux4.14/arch/arm/Makefile 中:
這個值一般是 0x00008000 ,算出 PAGE_OFFSET 后加上這個值就是 Linux 內核的起始地址。
修改這個偏移量就可以使Linux內核拷貝到不同的地址,自己修改注意內存對齊。
stext 段
從上面的ENTRY(stext)可以知道,一開始是運行stext段,這個段內的代碼是 start_kernel 函數前匯編環境的初始化。
linux4.14/arch/arm64/kernel/head.S
preserve_boot_args 保存 bootloader 傳遞過來的參數。
el2_setup 是設置 Linux 啟動模式是 EL2。Linux 有 EL0、EL1、EL2、EL3 四種異常啟動模式,這里設置一開始是 EL2,EL2 支持虛擬內存技術,然后注釋說明后面又退回 EL1,在 EL1 啟動 kernel。EL3 一般是只在安全模式使用。
set_cpu_boot_mode_flag 保存上面 cpu 的啟動模式。
__create_page_tables 創建頁表。
__cpu_setup 初始化CPU,這里主要是初始化和 MMU 內存相關的 CPU 部分。
__primary_switch 這里會進行跳轉。
在同一個文件中,會跳轉到這里,739 行開啟了MMU。然后最重要的是跳轉到
__primary_switched 函數。先把 __primary_switched 地址放到 x8 寄存器中,再跳轉到 x8,也就是跳轉到 __primary_switched。
接下來分析 __primary_switched 函數:
324-327 初始化了 init 進程的內存信息,開辟了內存空間。
329-334 設置了向量表。
336-340 保存了FDT,也就是 flat device tree 。
342-348 清除了BSS 段,我們知道一般是內存四區:堆區、棧區、全局區、代碼區。其中全局區可以再分為 data 段和 BSS 段,BSS 段存儲了未初始化的變量,這里將BSS段進行清零操作,否則內存中的值是不確定的,這是一個傳統操作。