Linux啟動入口主要代碼在 arch/mips/kernel/head.S檔案中 kernel_entry函數以彙編形式出現
主要幹了以下幾件事:
1. BSS段清0
2. 從boot傳過來的參數賦值到全域變數
3. clear context register
4. 根據init_thread_union建立$gp寄存器 並設定
$sp 寄存器,堆棧指標 (PTR_LA $28, init_thread_union)
5. 做好上述準備後就跳轉到
/arch/mips/kernel/main.c 中的 start_kernel()初始化硬體平台相關的代碼
主要涉及的資料結構 在arch/mips/kernel/init_task.c
union thread_union init_thread_union
__init_task_data
__attribute__((__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
THREAD_SIZE在這裡為8K,__attribute__((__aligned__(THREAD_SIZE)))表示這個資料結構以8K對齊
struct task_struct
init_task = INIT_TASK(init_task);
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
套用linux設計與實現的圖表示Thread_info、stack、task_struct的關係
在mips的head.S的工作就是
這就是傳說中的0號進程
1. 進程0是所有其他進程的祖先, 也稱作idle進程或swapper進程。
2. 進程0是在系統初始化時由kernel自身從無到有建立。(過程集中在start_kernel函數內)
3. 進程0的資料成員大部分是靜態定義的,即由預先定義好的(如上)INIT_TASK, INIT_MM等宏初始化。
進程0的描述符init_task定義在arch/arm/kernel/init_task.c,由INIT_TASK宏初始化。 init_mm等結構體定義在include/linux/init_task.h內,為init_task成員的初始值,分別由對應的初始化宏如INIT_MM等初始化
下面是轉載網上大蝦的詳細分析
系統加電起動後,MIPS 處理器預設的程式入口是0xBFC00000,此地址在無緩衝的KSEG1的地址地區內,對應的物理地址是 0x1FC00000,即CPU從0x1FC00000開始取第一條指令,這個地址在硬體上已經確定為FLASH的位置,Bootloader將 Linux 核心映像拷貝到 RAM 中某個空閑地址處,然後一般有個記憶體移動操作,目的地址在 arch/mips/Makefile 內指定:
load-$(CONFIG_MIPS_PB1550) += 0xFFFFFFFF80100000,
則最終bootloader定會將核心移到物理地址 0x00100000 處。
上面Makefile 裡指定的的 load 地址,最後會被編譯系統寫入到 arch/mips/kernel/vmlinux.lds 中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
這個檔案最終會以參數 -Xlinker --script -Xlinker vmlinux.lds 的形式傳給 gcc,並最終傳給連結器 ld 來控制其行為。
ld 會將 .text 節的地址連結到 0xFFFFFFFF80100000 處。
關於核心 ELF 檔案的入口地址(Entry point),即 bootloader 移動完核心後,直接跳轉到的地址,由ld 寫入 ELF的頭中,其會依次用下面的方法嘗試設定進入點,當遇到成功時則停止:
a. 命令列選項 -e entry
b. 指令碼中的 ENTRY(symbol)
c. 如果有定義 start 符號,則使用start符號(symbol)
d. 如果存在 .text 節,則使用第一個位元組的地址。
e. 地址0
注意到上面的 ld script 中,用 ENTRY 宏設定了核心的 entry point 是 kernel_entry,因此核心取得控制權後執行的第一條指令是在 kernel_entry 處。
*********************************************
linux 核心啟動的第一個階段是從 /arch/mips/kernel/head.s檔案開始的。
而此處正是核心入口函數kernel_entry(),該函數定義在 /arch/mips/kernel/head.s檔案裡。
kernel_entry()函數是體繫結構相關的組合語言,它首先初始化核心堆棧段,來為建立系統中的第一個進程進行準備,
接著用一段迴圈將核心映像的未初始化資料區段(bss段,在_edata和_end之間)清零,
最後跳轉到 /init/main.c 中的 start_kernel()初始化硬體平台相關的代碼。
*********************************************
NESTED(kernel_entry, 16,sp) # kernelentry point
聲明函數 kernel_entry,函數的堆棧為 16byte,返回地址儲存在 $sp 寄存器中。
-----------------------------
聲明函數入口
#define NESTED(symbol, framesize,rpc) \
.globl symbol; \
.align 2; \
.type symbol,@function; \
.ent symbol,0; \
symbol: .frame sp, framesize, rpc
彙編偽指令 frame 用來聲明堆棧布局。
它有三個參數:
1)第一個參數 framereg:聲明用於訪問局部堆棧的寄存器,一般為 $sp。
2)第二個參數 framesize:申明該函數已指派堆棧的大小,應該符合 $sp + framesize = 原來的 $sp。
3)第三個參數 returnreg:這個寄存器用來儲存返回地址。
----------------------------
kernel_entry_setup # cpuspecific setup
----------------------------
這個宏一般為空白的,在 include/asm-mips/mach-generic/kernel-entry-init.h 檔案中定義。
某些MIPS CPU需要額外的設定一些控制寄
存器,和具體的平台相關,一般為空白宏;某些多核MIPS,啟動時所
有的core的入口一起指向 kernel_entry,然後在該宏裡分叉,
boot core 繼續往下,其它的則不停的判斷迴圈,直到boot core 喚醒之
----------------------------
setup_c0_status_pri
設定 cp0_status 寄存器
----------------------------
.macro setup_c0_status_pri
#ifdef CONFIG_64BIT
setup_c0_status ST0_KX 0
#else
setup_c0_status 0 0
#endif
.endm
----------------------------
ARC64_TWIDDLE_PC
除非 CONFIG_ARC64,否則為空白操作
-----------------------------
#ifdef CONFIG_MIPS_MT_SMTC
mtc0 zero, CP0_TCCONTEXT__bss_start
mfc0 t0, CP0_STATUS
ori t0, t0, 0xff1f
xori t0, t0, 0x001e
mtc0 t0, CP0_STATUS
#endif /* CONFIG_MIPS_MT_SMTC */
宏定義 CONFIG_MIPS_MT_SMTC 是使用多核的 SMTC Linux 時定義的。一般情況下不考慮。
MIPS已經開發出 SMP Linux的改進版,叫做SMTC(線程上下文對稱式多處理) Linux。
SMTC Linux能理解輕量級 TC 的概念,並能因此減少某些與SMP Linux相關的開銷。
----------------------------
PTR_LA t0,__bss_start # clear .bss
LONG_S zero, (t0)
PTR_LA t1, __bss_stop -LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
清除 BSS 段,清 0。
變數 __bss_start 和 __bss_stop 在串連檔案arch/mips/kernel/vmlinux.lds 中定義。
--------------------------------
LONG_S a0,fw_arg0 # firmware arguments
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
把 bootloader 傳遞給核心的啟動參數儲存在 fw_arg0,fw_arg1,fw_arg2,fw_arg3 變數中。
變數 fw_arg0 為核心參數的個數,其餘分別為字串指標,為 *** = XXXX 的格式。
----------------------------------
MTC0 zero,CP0_CONTEXT # clear context register
清除 CP0 的 context register,這個寄存器用來儲存頁表的起始地址。
----------------------------------
PTR_LA $28, init_thread_union
初始化 $gp 寄存器,這個寄存器的地址指向一個 union,
THREAD_SIZE 大小,最低處是一個thread_info 結構
---------------------------------
PTR_LI sp, _THREAD_SIZE - 32
PTR_ADDU sp, $28
設定 $sp 寄存器,堆棧指標。 $sp = (init_thread_union 的地址) + _THREAD_SIZE- 32
的得出 $sp 指向這個 union 結構的結尾地址 - 32 位元組地址。
-----------------------------------
set_saved_sp sp, t0, t1
把 這個 CPU 核的堆棧地址 $sp 儲存到 kernelsp[NR_CPUS] 數組。
---------------------------------
如果定義了 CONFIG_SMP 宏,即多 CPU 核。
.macro set_saved_sp stackp temptemp2
#ifdef CONFIG_MIPS_MT_SMTC
mfc0 \temp,CP0_TCBIND
#else
MFC0 \temp,CP0_CONTEXT
#endif
LONG_SRL \temp,PTEBASE_SHIFT
LONG_S \stackp,kernelsp(\temp)
.endm
如果沒有定義 CONFIG_SMP 宏,單 CPU 核。
.macro set_saved_sp stackptemp temp2
LONG_S \stackp, kernelsp
.endm
變數 kernelsp 的定義,在 arch/mips/kernel/setup.c 檔案中。
unsigned long kernelsp[NR_CPUS];
把 這個 CPU 核的堆棧地址 $sp 儲存到 kernelsp[NR_CPUS] 數組。
---------------------------------
PTR_SUBU sp, 4 *SZREG # init stack pointer
---------------------------------
j start_kernel
END(kernel_entry)
最後跳轉到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬體平台相關的代碼。
----------------------------------
******************************************
這個 init_thread_union 變數在 arch/mips/kernel/init_task.c 檔案中定義。
union thread_union init_thread_union
__attribute__((__section__(".data.init_task"),
__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
******************************************
問題:
1)這個 init_thread_union 結構體指標是怎麼初始化的?
******************************************