Windows XP Windows 7 Windows 2003 Windows Vista Windows教程綜合 Linux 系統教程
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟件教程
 Windows教程網 >> Linux系統教程 >> Linux教程 >> arm+linux啟動流程之進入內核

arm+linux啟動流程之進入內核

日期:2017/2/7 14:40:33      編輯:Linux教程
 

還是從編譯鏈接生成vmlinux的過程來看吧,由一大堆.o文件鏈接而成,第一個就是

kernel\arch\arm\kernel\head-armv.o ,而且我們還看到了

lds鏈接文件kernel\arch\arm\vmlinux.lds,先把它分析一下

ENTRY(stext) //入口點是stext 應該就在head-armv.s中了

SECTIONS

{

. = 0xC0008000; //基址,是內核開始的虛擬地址

.init : { /* Init code and data */

_stext = .;

__init_begin = .;

*(.text.init)

__proc_info_begin = .;

*(.proc.info)

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info)

__arch_info_end = .;

__tagtable_begin = .;

*(.taglist)

__tagtable_end = .;

*(.data.init)

. = ALIGN(16);

__setup_start = .;

*(.setup.init)

__setup_end = .;

__initcall_start = .;

*(.initcall.init)

__initcall_end = .;

. = ALIGN(4096);

__init_end = .;

}

關於虛擬地址和物理地址的:使用MMU後,系統就會使用虛擬地址,通過MMU來指向

實際物理地址而在這裡我們的0xC0008000實際物理地址就是0x30008000,

具體關於MMU的介紹參考《ARM體系結構與編程》。

到head-armv.s找到程序的入口

.section ".text.init",#alloc,#execinstr

.type stext, #function

ENTRY(stext)

mov r12, r0

mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode

msr cpsr_c, r0 @ and all irqs disabled

bl __lookup_processor_type

teq r10, #0 @ invalid processor?

moveq r0, #'p' @ yes, error 'p'

beq __error

bl __lookup_architecture_type

teq r7, #0 @ invalid architecture?

moveq r0, #'a' @ yes, error 'a'

beq __error

bl __create_page_tables

adr lr, __ret @ return address

add pc, r10, #12 @ initialise processor

來看看上一句跳到哪裡去了

去追尋r10的值,是在__lookup_processor_type子函數中賦的

__lookup_processor_type:

adr r5, 2f //r5 標號2的地址 基址是0x30008000

ldmia r5, {r7, r9, r10} //r7=__proc_info_end r9=__proc_info_begin

sub r5, r5, r10 //r10 標號2的鏈接地址 基址是0xc0008000

add r7, r7, r5 @ to our address space

add r10, r9, r5 //r10 變換為基址是0x30008000的__proc_info_begin

2: .long __proc_info_end

.long __proc_info_begin

.long 2b

這樣r10中存放的是__proc_info_begin的地址,因為現在我們還沒有打開MMU

所以還是需要把基址變換到0x30008000,接著我們就去找__proc_info_begin吧

注意到在上面的vmlinux.lds中有這個標號,下來鏈接的是.proc.info段,

在kernel\arch\arm\mm\proc-arm920.s的最後找到了這個段

.section ".proc.info", #alloc, #execinstr

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200

.long 0xff00fff0

.long 0x00000c1e @ mmuflags

b __arm920_setup

ok,這樣我們就知道add pc, r10, #12跳到哪裡去了,因為這個地址剛好放了條跳轉語句

注意了b語句用的都是相對地址,所以不需要變換地址,反正是跳到__arm920_setup,而且

上一條語句是adr lr, __ret,設定了__arm920_setup的返回地址是__ret,所以執行完

__arm920_setup後回到head-armv.s的__ret標號繼續執行.

__ret: ldr lr, __switch_data

mcr p15, 0, r0, c1, c0 //注意這裡了,在這裡打開了MMU

mov r0, r0

mov r0, r0

mov r0, r0

mov pc, lr //跳到__mmap_switched,這裡已經用了虛擬地址了吧

// 這條指令ldr lr, __switch_data加載的__mmap_switched地址就是虛擬地址啊

__switch_data: .long __mmap_switched

從__mmap_switched一路執行下來,就要調到C語言代碼中去了

b SYMBOL_NAME(start_kernel) //在kernel\init\main.c中

這個程序不是特別復雜,細心看看還是能大概看懂,我也不能去一一注釋

這裡有一個流程圖

到了C語言中就不是很難理解了

lock_kernel();

printk(linux_banner);

setup_arch(&command_line);

printk("Kernel command line: %s\n", saved_command_line);

parse_options(command_line);

trap_init();

init_IRQ();

sched_init();

softirq_init();

time_init();

就是一大堆初始化工作,追著每個函數去看好了

start_kernel最後調用的一個函數

static void rest_init(void)

{

kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);

unlock_kernel();

current->need_resched = 1;

cpu_idle();

}

用kernel_thread建立了一個init進程,執行的是main.c中的init函數

lock_kernel();

do_basic_setup();

在do_basic_setup中調用了do_initcalls函數

各種驅動都是在do_initcalls(void)中完成的

static void __init do_initcalls(void)

{

initcall_t *call;

call = &__initcall_start;

do {

(*call)();

call++;

} while (call < &__initcall_end);

flush_scheduled_tasks();

}

__initcall_start也是在vmlinux.lds中賦值的,那就需要找到.initcall.ini這個段

在kernel\include\linux\init.h中可以找到

#define __init_call __attribute__ ((unused,__section__ (".initcall.init")))

typedef int (*initcall_t)(void);

#define __initcall(fn) \

static initcall_t __initcall_##fn __init_call = fn

仔細研究下就發現這是把初始化函數的地址放到了.initcall.init段中

這樣就可以不斷調用驅動的初始化函數了

如果沒有定義MODULE,那麼#define module_init(x) __initcall(x);

所以如果要把驅動的編譯進內核就很簡單了吧

init的最後

if (execute_command)

execve(execute_command,argv_init,envp_init);

execute_command與ppcboot傳的命令行參數是有關的哦,就是init=/linuxrc

這樣就要去執行根目錄下的linuxrc腳本,這個腳本會去執行busybox

而busybox又去執行/etc/init.d/rcS腳本,這個腳本又去執行/usr/etc/rc.local

完了

Copyright © Windows教程網 All Rights Reserved