本文分析基於mips架構,linux-3.3.8
一.kernel入口
首先通過連結指令碼找到kernel的入口
1.核心是壓縮過的
/arch/mips/boot/compressed/Makefile ,將$(obj)/head.o $(obj)/decompress.o $(obj)/dbg.o $(obj)/piggy.o 連結為vmlinuz,連結指令碼為ld.script vmlinuz: $(src)/ld.script $(vmlinuzobjs-y) $(obj)/calc_vmlinuz_load_addr $(call cmd,zld) $(call cmd,strip)
將vmlinux.bin.z採用objcopy工具,作為資料打包為piggy.o targets += piggy.o OBJCOPYFLAGS_piggy.o := --add-section=.image=$(obj)/vmlinux.bin.z \ --set-section-flags=.image=contents,alloc,load,readonly,data $(obj)/piggy.o: $(obj)/dummy.o $(obj)/vmlinux.bin.z FORCE $(call if_changed,objcopy)
將vmlinux.bin採用壓縮公用程式壓縮為vmlinux.bin.z tool_$(CONFIG_KERNEL_GZIP) = gzip tool_$(CONFIG_KERNEL_BZIP2) = bzip2 tool_$(CONFIG_KERNEL_LZMA) = lzma tool_$(CONFIG_KERNEL_LZO) = lzo
targets += vmlinux.bin.z $(obj)/vmlinux.bin.z: $(obj)/vmlinux.bin FORCE $(call if_changed,$(tool_y))
可見在壓縮是入口為/arch/mips/boot/compressed/head.s
2.核心是未壓縮的
定義makefile指定了vmlinux的連結指令碼:
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds, 由vmlinux.lds.S編譯得到 vmlinux.lds: OUTPUT_ARCH(mips) ENTRY(kernel_entry) PHDRS { text PT_LOAD FLAGS(7); /* RWX */ note PT_NOTE FLAGS(4); /* R__ */ } jiffies = jiffies_64; SECTIONS { . = 0x80001000; /* read-only */ _text = .; /* Text and read-only data */ .text : { . = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.cpuinit.text) *(.cpuexit.text) *(.text.unlikely) . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .; . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .; . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .; *(.text.*) *(.fixup) *(.gnu.warning) } :text = 0 _etext = .; /* End of text section */
可見入口為kernel_entry
這裡只講解未壓縮的情況,因為壓縮情況解壓之後的跳轉到核心執行過程是一樣的。
(arch/mips/kernel/head.s) NESTED(kernel_entry, 16, sp) # kernel entry point
kernel_entry_setup # cpu specific setup
setup_c0_status_pri
/* We might not get launched at the address the kernel is linked to, so we jump there. */ PTR_LA t0, 0f jr t0 0: PTR_LA t0, __bss_start # clear .bss //將核心的bss段清零 LONG_S zero, (t0) PTR_LA t1, __bss_stop - LONGSIZE 1: PTR_ADDIU t0, LONGSIZE LONG_S zero, (t0) bne t0, t1, 1b
LONG_S a0, fw_arg0 # firmware arguments LONG_S a1, fw_arg1 LONG_S a2, fw_arg2 LONG_S a3, fw_arg3
MTC0 zero, CP0_CONTEXT # clear context register PTR_LA $28, init_thread_union //union thread_union init_thread_union, $28寄存器指向 init_thread_union /* Set the SP after an empty pt_regs. */ PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE PTR_ADDU sp, $28 //sp寄存器指向 init_thread_union中的棧空間 back_to_back_c0_hazard //同步c0,防止c0冒險,見下文解釋 set_saved_sp sp, t0, t1 //將sp寄存器的值儲存到kernelsp變數中,即將進程核心態棧指標儲存在kernelsp變數中 PTR_SUBU sp, 4 * SZREG # init stack pointer
j start_kernel //啟動c代碼,init/main.c END(kernel_entry)
kernel_entry_setup的定義位於:
(arch/mips/include/asm/mach-brcmstb/kernel-entry-init.h) .macro kernel_entry_setup # save arguments for CFE callback sw a0, cfe_handle //將a0 寄存器的內容儲存至cfe_handle, 為了kernel能夠調用cfe中的函數,取得核心啟動函數和其他硬體設定參數,這些參數儲存在cfe的環境變數中 sw a2, cfe_entry //將a2 寄存器的內容儲存至cfe_entry sw a3, cfe_seal //將a3 寄存器的內容儲存至cfe_seal jal bmips_enable_xks01 .endm .macro smp_slave_setup .endm
(arch/mips/kernel/bmips_vec.s) /*********************************************************************** * XKS01 support * Certain CPUs support extending kseg0 to 1024MB. ***********************************************************************/ LEAF(bmips_enable_xks01)
#if defined(CONFIG_XKS01) mfc0 t0, $22, 3 li t1, 0x1ff0 li t2, (1 << 12) | (1 << 9) or t0, t1 xor t0, t1 or t0, t2 mtc0 t0, $22, 3 BARRIER #endif /* defined(CONFIG_XKS01) */
jr ra
END(bmips_enable_xks01)
setup_c0_status_pri的定義位於:
(arch/mips/kernel/head.s) .macro setup_c0_status_pri #ifdef CONFIG_64BIT setup_c0_status ST0_KX 0 #else setup_c0_status 0 0 //將c0 status寄存器清零 #endif .endm
arch/mips/include/asm/hazards.h: _ehb彙編指令,用於同步c0,防止c0冒險 ASMMACRO(back_to_back_c0_hazard, _ehb )
current表示當前進程,我們來看看它的定義: #define get_current() (current_thread_info()->task) #define current get_current()
register struct thread_info *__current_thread_info __asm__("$28"); #define current_thread_info() __current_thread_info
__current_thread_info是一個寄存器變數,$28表示gp寄存器
所以這裡PTR_LA $28, init_thread_union 實際上就是設定為0號進程,
PTR_LI sp, _THREAD_SIZE - 32 - PT_SIZE
PTR_ADDU sp, $28
sp= $28 + _THREAD_SIZE - 32 - PT_SIZE=$28 +8192-32-sizeof(struct pt_regs)
union thread_union {
#ifndef CONFIG_X86
struct thread_info thread_info;
#endif
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long tp_value; /* thread pointer */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
struct pt_regs *regs;
};
$28---------→+----------------+ low address
| *task |
| ... |
| *regs | ----------------------------------+
grow ↑ | xxxxxxxxx | |
| | xxxxxxxxx | |
sp---------> | xxxxxxxxx | |
| 32 byte | |
| sizeof(pt_regs)| ← ------------------------------ +
這樣sp就切換到了0號進程的核心棧上,設定好棧之後就可以執行c定義的函數了
二.進入start_kernel
asmlinkage void __init start_kernel(void)
{
boot_init_stack_canary();
local_irq_disable();
tick_init();
setup_arch(&command_line);
build_all_zonelists(NULL);
page_alloc_init();
trap_init();
mm_init();
sched_init();
init_IRQ();
prio_tree_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
local_irq_enable();
...
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
static noinline void __init_refok rest_init(void)
{
int pid;
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //建立init進程,即1號進程
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
preempt_enable_no_resched();
schedule();
/* Call into cpu_idle with preempt disabled */
preempt_disable();
cpu_idle(); //0號進程
}
static int __init kernel_init(void * unused)
{