系統(tǒng)加電起動后,MIPS處理器默認(rèn)的程序入口是0xBFC00000,此地址在無緩存的KSEG1的地址區(qū)域內(nèi),對應(yīng)的物理地址是0x1FC00000,即CPU從0x1FC00000開始取第一條指令,這個地址在硬件上已經(jīng)確定為FLASH的位置,Bootloader將Linux內(nèi)核映像拷貝到 RAM 中某個空閑地址處,然后一般有個內(nèi)存移動操作,目的地址在arch/mips/Makefile內(nèi)指定:
core-$(CONFIG_MIPS_ADM5120)+= arch/mips/adm5120/
load-$(CONFIG_MIPS_ADM5120)+= 0xffffffff80002000
則最終bootloader定會將內(nèi)核移到物理地址 0x002000 處
上面Makefile里指定的的load地址,最后會被編譯系統(tǒng)寫入到arch/mips/kernel/vmlinux.lds中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80002000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
這個文件最終會以參數(shù)-Xlinker --script -Xlinkervmlinux.lds的形式傳給gcc,并最終傳給鏈接器ld來控制其行為。ld會將.text節(jié)的地址鏈接到0xFFFFFFFF80002000處。
關(guān)于內(nèi)核ELF文件的入口地址(Entrypoint),即bootloader移動完內(nèi)核后,直接跳轉(zhuǎn)到的地址,由ld寫入ELF的頭中,其會依次用下面的方法嘗試設(shè)置入口點,當(dāng)遇到成功時則停止:
a.命令行選項-e entry
b.腳本中的ENTRY(symbol)
c.如果有定義start符號,則使用start符號(symbol)
d.如果存在.text節(jié),則使用第一個字節(jié)的地址。
e.地址0
注意到上面的ld script中,用ENTRY宏設(shè)置了內(nèi)核的entrypoint是kernel_entry,因此內(nèi)核取得控制權(quán)后執(zhí)行的第一條指令是在kernel_entry處。
linux內(nèi)核啟動的第一個階段是從 /arch/mips/kernel/head.s文件開始的。而此處正是內(nèi)核入口函數(shù)kernel_entry(),該函數(shù)定義在/arch/mips/kernel/head.s文件里。kernel_entry()函數(shù)是體系結(jié)構(gòu)相關(guān)的匯編語言,它首先初始化內(nèi)核堆棧段,來為創(chuàng)建系統(tǒng)中的第一個進程進行準(zhǔn)備,接著用一段循環(huán)將內(nèi)核映像的未初始化數(shù)據(jù)段(bss段,在_edata和_end之間)清零,最后跳轉(zhuǎn)到 /init/main.c中的start_kernel()初始化硬件平臺相關(guān)的代碼。
*********************************************
NESTED(kernel_entry, 16,sp) # kernel entry point
聲明函數(shù) kernel_entry,函數(shù)的堆棧為16 byte,返回地址保存在 $sp寄存器中。
-----------------------------
聲明函數(shù)入口
#define NESTED(symbol, framesize,rpc) \
.globl symbol; \
.align 2; \
.type symbol,@function; \
.ent symbol,0; \
symbol: .frame sp, framesize, rpc
匯編偽指令 frame用來聲明堆棧布局。
它有三個參數(shù):
1)第一個參數(shù) framereg:聲明用于訪問局部堆棧的寄存器,一般為 $sp。
2)第二個參數(shù) framesize:申明該函數(shù)已分配堆棧的大小,應(yīng)該符合
$sp+framesize= 原來的 $sp。
3)第三個參數(shù) returnreg:這個寄存器用來保存返回地址。
----------------------------
kernel_entry_setup # cpu specific setup
----------------------------
這個宏一般為空的,在include/asm-mips/mach-generic/kernel-entry-init.h文件中定義。
某些MIPSCPU需要額外的設(shè)置一些控制寄存器,和具體的平臺相關(guān),一般為空宏;某些多核MIPS,啟動時所有的core的入口一起指向 kernel_entry,然后在該宏里分叉,boot core繼續(xù)往下,其它的則不停的判斷循環(huán),直到boot core喚醒之。
----------------------------
setup_c0_status_pri
設(shè)置 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是使用多核的 SMTCLinux時定義的。一般情況下不考慮。
MIPS已經(jīng)開發(fā)出 SMP Linux的改進版,叫做SMTC(線程上下文對稱多處理) Linux。
SMTC Linux能理解輕量級 TC的概念,并能因此減少某些與SMP Linux相關(guān)的開銷。
----------------------------
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傳遞給內(nèi)核的啟動參數(shù)保存在fw_arg0,fw_arg1,fw_arg2,fw_arg3變量中。
變量 fw_arg0為內(nèi)核參數(shù)的個數(shù),其余分別為字符串指針,為 *** =XXXX 的格式。
----------------------------------
MTC0 zero,CP0_CONTEXT # clear context register
清除 CP0的context register,這個寄存器用來保存頁表的起始地址。
----------------------------------
PTR_LA $28, init_thread_union
初始化 $gp寄存器,這個寄存器的地址指向一個 union,
THREAD_SIZE 大小,最低處是一個thread_info結(jié)構(gòu)
---------------------------------
PTR_LI sp, _THREAD_SIZE - 32
PTR_ADDU sp, $28
設(shè)置 $sp寄存器,堆棧指針。 $sp = (init_thread_union的地址)+ _THREAD_SIZE - 32
的得出 $sp指向這個 union 結(jié)構(gòu)的結(jié)尾地址 -32字節(jié)地址。
-----------------------------------
set_saved_sp sp, t0, t1
把 這個CPU核的堆棧地址 $sp保存到 kernelsp[NR_CPUS]數(shù)組。
---------------------------------
如果定義了 CONFIG_SMP宏,即多 CPU核。
.macro set_saved_sp stackp temp temp2
#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 stackp temp temp2
LONG_S \stackp,kernelsp
.endm
變量 kernelsp的定義,在arch/mips/kernel/setup.c文件中。
unsigned long kernelsp[NR_CPUS];
把 這個CPU核的堆棧地址 $sp保存到 kernelsp[NR_CPUS]數(shù)組。
---------------------------------
PTR_SUBU sp, 4 *SZREG # init stack pointer
---------------------------------
j start_kernel
END(kernel_entry)
最后跳轉(zhuǎn)到 /init/main.c中的start_kernel()初始化硬件平臺相關(guān)的代碼。
----------------------------------
**********************************************
這個 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) };
linux 內(nèi)核啟動的第一個階段是從 /arch/mips/kernel/head.s文件開始的。
而此處正是內(nèi)核入口函數(shù)kernel_entry(),該函數(shù)定義在/arch/mips/kernel/head.s文件里。
kernel_entry()函數(shù)是體系結(jié)構(gòu)相關(guān)的匯編語言,它首先初始化內(nèi)核堆棧段,來為創(chuàng)建系統(tǒng)中的第一個進程進行準(zhǔn)備,接著用一段循環(huán)將內(nèi)核映像的未初始化數(shù)據(jù)段(bss段,在_edata和_end之間)清零,
最后跳轉(zhuǎn)到 /arch/mips/kernel/main.c中的start_kernel()初始化硬件平臺相關(guān)的代碼。
下面講述 start_kernel()函數(shù)。
*******************************************
asmlinkage void __init start_kernel(void)
{
---------------------------------
char * command_line;
extern struct kernel_param __start___param[],__stop___param[];
定義了核的參數(shù)數(shù)據(jù)結(jié)構(gòu)
---------------------------------
smp_setup_processor_id();
設(shè)置 SMP多核的 CPU核的ID號,單核不進行任何操作,我們不關(guān)心。
---------------------------------
unwind_init();
在 MIPS體系結(jié)構(gòu)中,這個函數(shù)是個空函數(shù)(可能調(diào)用setup_arch,配置核的相關(guān)函數(shù))
---------------------------------
lockdep_init();
初始化核依賴關(guān)系哈希表。
---------------------------------
local_irq_disable();
關(guān)閉當(dāng)前 CPU核的中斷
---------------------------------
early_boot_irqs_off();
通過一個靜態(tài)全局變量 early_boot_irqs_enabled來幫助我們調(diào)試代碼,
通過這個標(biāo)記可以幫助我們知道是否在“early bootup code”,
也可以通過這個標(biāo)志警告是否有無效的中斷打開。
和 early_boot_irqs_on()函數(shù)配置使用,參考下面。
---------------------------------
early_init_irq_lock_class();
每一個中斷都有一個 IRQ描述符 (structirq_desc)來進行描述。
這個函數(shù)的主要作用是設(shè)置所有的 IRQ描述符 (structirq_desc)的鎖是統(tǒng)一的鎖,
還是每一個 IRQ描述符 (structirq_desc)都有一個小鎖。
---------------------------------
lock_kernel();
獲取大內(nèi)核鎖,這種大內(nèi)核鎖鎖定整個內(nèi)核。
---------------------------------
tick_init();
如果沒有定義 CONFIG_GENERIC_CLOCKEVENTS宏定義,則這個函數(shù)為空函數(shù),
如果定義了這個宏,這執(zhí)行初始化 tick控制功能,注冊clockevents的框架。
---------------------------------
boot_cpu_init();
對于 CPU核的系統(tǒng)來說,設(shè)置第一個 CPU核為活躍 CPU核。
對于單 CPU核系統(tǒng)來說,設(shè)置CPU核為活躍 CPU核。
參考《linux-mips啟動分析(2-1)》。
---------------------------------
page_address_init();
當(dāng)定義了CONFIG_HIGHMEM 宏,并且沒有定義 WANT_PAGE_VIRTUAL 宏時,非空函數(shù)。
其他情況為空函數(shù)。
---------------------------------
printk(KERN_NOTICE);
printk(linux_banner);
輸出打印版本信息。
---------------------------------
setup_arch(&command_line);
每種體系結(jié)構(gòu)都有自己的 setup_arch()函數(shù),這些是體系結(jié)構(gòu)相關(guān)的。
如何確定編譯那個體系結(jié)構(gòu)的 setup_arch()函數(shù)呢?
主要由 linux源碼樹頂層 Makefile中 ARCH變量來決定的。
例如: MIPS體系結(jié)構(gòu)的。
SUBARCH := mips
ARCH ?= $(SUBARCH)
---------------------------------
setup_command_line(command_line);
保存未改變的 comand_line 到字符數(shù)組 static_command_line[] 中。
保存 boot_command_line到字符數(shù)組 saved_command_line[]中。
---------------------------------
unwind_setup();
空函數(shù)。
---------------------------------
setup_per_cpu_areas();
如果沒有定義 CONFIG_SMP宏,則這個函數(shù)為空函數(shù)。
如果定義了 CONFIG_SMP宏,
則這個 setup_per_cpu_areas()函數(shù)給每個CPU分配內(nèi)存,并拷貝 .data.percpu段的數(shù)據(jù)。
---------------------------------
如果沒有定義 CONFIG_SMP宏,則這個函數(shù)為空函數(shù)。
如果定義了 CONFIG_SMP宏,這個函數(shù)
smp_prepare_boot_cpu();
---------------------------------
sched_init();
核心進程調(diào)度器初始化,調(diào)度器的初始化優(yōu)先于任何中斷的建立(包括 timer中斷)。
并且初始化進程0,即 idle進程,但是并沒有設(shè)置idle進程的 NEED_RESCHED標(biāo)志,
以完成內(nèi)核剩余的啟動部分。
---------------------------------
preempt_disable();
進制內(nèi)核的搶占。使當(dāng)前進程的 structthread_info結(jié)構(gòu) preempt_count成員的值增加1。
---------------------------------
建立各個節(jié)點的管理區(qū)的 zonelist,便于分配內(nèi)存的 fallback使用。
這個鏈表的作用: 這個鏈表是為了在一個分配不能夠滿足時可以考察下一個管理區(qū)來設(shè)置了。
在考察結(jié)束時,分配將從 ZONE_HIGHMEM回退到 ZONE_NORMAL,
在分配時從 ZONE_NORMAL退回到 ZONE_DMA就不會回退了。
build_all_zonelists();
---------------------------------
page_alloc_init();
---------------------------------
在 MIPS體系結(jié)構(gòu)下,這個函數(shù)已經(jīng)在 arch_mem_init() 函數(shù)中調(diào)用了一次。
這個函數(shù)的具體分析詳細(xì)分析,請看《linux-mips啟動分析(4)》。
所以這個函數(shù)直接返回。
parse_early_param();
---------------------------------
打印 linux啟動命令行參數(shù)。
printk(KERN_NOTICE "Kernel command line: %s\n",boot_command_line);
---------------------------------
這個函數(shù)的意思對 linux啟動命令行參數(shù)進行再分析和處理。
這兩個變量 __start___param和 __stop___param在
鏈接腳本 arch/mips/kernel/vmlinux.lds中定義。
最后一個參數(shù)為,當(dāng)不能夠識別 linux啟動命令行參數(shù)時,調(diào)用的函數(shù)。
parse_args("Booting kernel",static_command_line, __start___param,
__stop___param - __start___param, &unknown_bootoption);
---------------------------------
檢查中斷是否已經(jīng)打開了,如果已將打開了,關(guān)閉中斷。
if (!irqs_disabled()) {
local_irq_disable();
}
---------------------------------
sort_main_extable();
這個函數(shù)對內(nèi)核建立的異常處理調(diào)用函數(shù)表(exception table)
根據(jù)異常的向量號進行堆排序。
---------------------------------
設(shè)置 CPU的異常處理函數(shù),TLB重填,cache出錯,還有通用異常處理表的初始化。
trap_init();
---------------------------------
初始化 RCU機制,這個步驟必須比本地 timer的初始化早。
rcu_init();
---------------------------------
用來初始化中斷處理硬件相關(guān)的寄存器和中斷描述符數(shù)組 irq_desc[] 數(shù)組,
每個中斷號都有一個對應(yīng)的中斷描述符。
參考《linux-mips啟動分析(11)》。
init_IRQ();
--------------------------------
系統(tǒng)在初始化階段動態(tài)的分配了 4 個 hashtable,并把它們的地址存入 pid_hash[] 數(shù)組。
便于從PID查找 進程描述符地址。