天天看點

linux 2.6 啟動流程分析

核心在啟動時可以傳遞一個字元串指令行,來控制核心啟動的過程,例如:

"console=ttyS2,115200 [email protected]"

這裡指定了控制台是序列槽2,波特率是115200,記憶體大小是64M,實體基位址是0xA0000000。

另外我們可以在核心中定義一些全局變量,使用這些全局變量控制核心的配置,例如usb驅動中定義了

static int nousb;

這個變量為1,則整個usb驅動不初始化,如果想将其置1,可在字元串指令行中添加nousb=1。

在操作該變量之前,還要讓系統知道該變量,方法是:

__module_param_call("",nousb,param_set_bool,param_get_bool,&nousb,0444);

__module_param_call這個宏定義在kernel/include/linux/moduleparam.h

原型如下:

#define __module_param_call(prefix, name, set, get, arg, perm)   /

static char __param_str_##name[] = prefix #name;   /

static struct kernel_param const __param_##name    /

__attribute_used__       /

     __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) /

= { __param_str_##name, perm, set, get, arg }

它定義了一個kernel_param類型的變量,這個變量被放到了段__param,

kernel_param結構體的定義是:

struct kernel_param {

const char *name;

unsigned int perm;

param_set_fn set;

param_get_fn get;

void *arg;

};

__param這個段的聲明有些平台是在arch/../../vmlinux.lds.S,而大多數平台是放到

kernel/include/asm-generic/vmlinux.lds.h中,定義如下:

__param : AT(ADDR(__param) - LOAD_OFFSET) {    /

   VMLINUX_SYMBOL(__start___param) = .;    /

   *(__param)       /

   VMLINUX_SYMBOL(__stop___param) = .;    /

}

核心啟動時就會對字元串指令進行解析,在kernel/init/main.c中,核心啟動函數start_kernel中

對外部數組進行了聲明:

extern struct kernel_param __start___param[], __stop___param[];

然後調用函數parse_args對數組進行解析:

parse_args("Booting kernel", command_line, __start___param,

      __stop___param - __start___param,

      &unknown_bootoption);

其中command_line就是要解析的字元串指令行,unknown_bootoption是函數指針,它用來擷取指定參數的=右邊的值。

parse_args就會在數組中找到和nousb名稱一樣的kernel_param變量,并調用它的set函數對其進行付值。

核心啟動位址的确定

核心編譯連結過程是依靠vmlinux.lds檔案,以arm為例vmlinux.lds檔案位于kernel/arch/arm/vmlinux.lds,

但是該檔案是由vmlinux-armv.lds.in生成的,根據編譯選項的不同源檔案還可以是vmlinux-armo.lds.in,

vmlinux-armv-xip.lds.in。

vmlinux-armv.lds的生成過程在kernel/arch/arm/Makefile中

LDSCRIPT      = arch/arm/vmlinux-armv.lds.in

arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) /

$(wildcard include/config/cpu/32.h) /

$(wildcard include/config/cpu/26.h) /

$(wildcard include/config/arch

         _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 = .;

     }

其中TEXTADDR就是核心啟動的虛拟位址,定義在kernel/arch/arm/Makefile中:

ifeq ($(CONFIG_CPU_32),y)

PROCESSOR     = armv

TEXTADDR      = 0xC0008000

LDSCRIPT      = arch/arm/vmlinux-armv.lds.in

endif

需要注意的是這裡是虛拟位址而不是實體位址。

一般情況下都在生成vmlinux後,再對核心進行壓縮成為zImage,壓縮的目錄是kernel/arch/arm/boot。

下載下傳到flash中的是壓縮後的zImage檔案,zImage是由壓縮後的vmlinux和解壓縮程式組成,如下圖所示:

             |-----------------|/     |-----------------|

             |                  | /    |                  |

             |                  |   /   | decompress code |

             |      vmlinux      |    / |-----------------|     zImage

             |                  |     /|                  |

             |                  |      |                  |

             |                  |      |                  |    

             |                  |      |                  |

             |                  |     /|-----------------|

             |                  |    /

             |                  |   /

             |                  | /

             |-----------------|/

zImage連結腳本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。

是由同一目錄下的vmlinux.lds.in檔案生成的,内容如下:

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS

{

    . = LOAD_ADDR;

    _load_addr = .;

    . = TEXT_START;

    _text = .;

    .text : {

      _start = .;

其中LOAD_ADDR就是zImage中解壓縮代碼的ram偏移位址,TEXT_START是核心ram啟動的偏移位址,這個位址是實體位址。

在kernel/arch/arm/boot/Makefile檔案中定義了:

ZTEXTADDR    =0

ZRELADDR      = 0xa0008000

ZTEXTADDR就是解壓縮代碼的ram偏移位址,ZRELADDR是核心ram啟動的偏移位址,這裡看到指定ZTEXTADDR的位址為0,

明顯是不正确的,因為我的平台上的ram起始位址是0xa0000000,在Makefile檔案中看到了對該位址設定的幾行注釋:

# We now have a PIC decompressor implementation.   Decompressors running

# from RAM should not define ZTEXTADDR.   Decompressors running directly

# from ROM or Flash must define ZTEXTADDR (preferably via the config)

他的意識是如果是在ram中進行解壓縮時,不用指定它在ram中的運作位址,如果是在flash中就必須指定他的位址。是以

這裡将ZTEXTADDR指定為0,也就是沒有真正指定位址。

在kernel/arch/arm/boot/compressed/Makefile檔案有一行腳本:

SEDFLAGS     = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/

使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。

這樣vmlinux.lds的生成過程如下:

vmlinux.lds:     vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config

@sed "$(SEDFLAGS)" < vmlinux.lds.in > $@

以上就是我對核心啟動位址的分析,總結一下核心啟動位址的設定:

1、設定kernel/arch/arm/Makefile檔案中的

    TEXTADDR      = 0xC0008000

    核心啟動的虛拟位址

2、設定kernel/arch/arm/boot/Makefile檔案中的

    ZRELADDR      = 0xa0008000

    核心啟動的實體位址

    如果需要從flash中啟動還需要設定

    ZTEXTADDR位址。

核心解壓縮過程

核心壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,

編譯完成後将産生vmlinux、head.o、misc.o、head-xscale.o、piggy.o這幾個檔案,

head.o是核心的頭部檔案,負責初始設定;

misc.o将主要負責核心的解壓工作,它在head.o之後;

head-xscale.o檔案主要針對Xscale的初始化,将在連結時與head.o合并;

piggy.o是一個中間檔案,其實是一個壓縮的核心(kernel/vmlinux),隻不過沒有和初始化檔案及解壓檔案連結而已;

vmlinux是(沒有--lw:zImage是壓縮過的核心)壓縮過的核心,就是由piggy.o、head.o、misc.o、head-xscale.o組成的。

在BootLoader完成系統的引導以後并将Linux核心調入記憶體之後,調用bootLinux(),

這個函數将跳轉到kernel的起始位置。如果kernel沒有壓縮,就可以啟動了。

如果kernel壓縮過,則要進行解壓,在壓縮過的kernel頭部有解壓程式。

壓縮過得kernel入口第一個檔案源碼位置在arch/arm/boot/compressed/head.S。

它将調用函數decompress_kernel(),這個函數在檔案arch/arm/boot/compressed/misc.c中,

decompress_kernel()又調用proc_decomp_setup(),arch_decomp_setup()進行設定,

然後使用在列印出資訊“Uncompressing Linux...”後,調用gunzip()。将核心放于指定的位置。

以下分析head.S檔案:

(1)對于各種Arm CPU的DEBUG輸出設定,通過定義宏來統一操作。

(2)設定kernel開始和結束位址,儲存architecture ID。

(3)如果在ARM2以上的CPU中,用的是普通使用者模式,則升到超級使用者模式,然後關中斷。

(4)分析LC0結構delta offset,判斷是否需要重載核心位址(r0存入偏移量,判斷r0是否為零)。

    這裡是否需要重載核心位址,我以為主要分析arch/arm/boot/Makefile、arch/arm/boot/compressed/Makefile

    和arch/arm/boot/compressed/vmlinux.lds.in三個檔案,主要看vmlinux.lds.in連結檔案的主要段的位置,

    LOAD_ADDR(_load_addr)=0xA0008000,而對于TEXT_START(_text、_start)的位置隻設為0,BSS_START(__bss_start)=ALIGN(4)。

    對于這樣的結果依賴于,對核心解壓的運作方式,也就是說,核心解壓前是在記憶體(RAM)中還是在FLASH上,

    因為這裡,我們的BOOTLOADER将壓縮核心(zImage)移到了RAM的0xA0008000位置,我們的壓縮核心是在記憶體(RAM)從0xA0008000位址開始順序排列,

    是以我們的r0獲得的偏移量是載入位址(0xA0008000)。接下來的工作是要把核心鏡像的相對位址轉化為記憶體的實體位址,即重載核心位址。

(5)需要重載核心位址,将r0的偏移量加到BSS region和GOT table中。

(6)清空bss堆棧空間r2-r3。

(7)建立C程式運作需要的緩存,并賦于64K的棧空間。

(8)這時r2是緩存的結束位址,r4是kernel的最後執行位址,r5是kernel境象檔案的開始位址。檢查是否位址有沖突。

    将r5等于r2,使decompress後的kernel位址就在64K的棧之後。

(9)調用檔案misc.c的函數decompress_kernel(),解壓核心于緩存結束的地方(r2位址之後)。此時各寄存器值有如下變化:

    r0為解壓後kernel的大小

    r4為kernel執行時的位址

    r5為解壓後kernel的起始位址

    r6為CPU類型值(processor ID)

    r7為系統類型值(architecture ID)

(10)将reloc_start代碼拷貝之kernel之後(r5+r0之後),首先清除緩存,而後執行reloc_start。

(11)reloc_start将r5開始的kernel重載于r4位址處。

(12)清除cache内容,關閉cache,将r7中architecture ID賦于r1,執行r4開始的kernel代碼。

下面簡單介紹一下解壓縮過程,也就是函數decompress_kernel實作的功能:

解壓縮代碼位于kernel/lib/inflate.c,inflate.c是從gzip源程式中分離出來的。包含了一些對全局資料的直接引用。

在使用時需要直接嵌入到代碼中。gzip壓縮檔案時總是在前32K位元組的範圍内尋找重複的字元串進行編碼,

在解壓時需要一個至少為32K位元組的解壓緩沖區,它定義為window[WSIZE]。inflate.c使用get_byte()讀取輸入檔案,

它被定義成宏來提高效率。輸入緩沖區指針必須定義為inptr,inflate.c中對之有減量操作。inflate.c調用flush_window()

來輸出window緩沖區中的解壓出的位元組串,每次輸出長度用outcnt變量表示。在flush_window()中,還必

須對輸出位元組串計算CRC并且重新整理crc變量。在調用gunzip()開始解壓之前,調用makecrc()初始化CRC計算表。

最後gunzip()傳回0表示解壓成功。

我們在核心啟動的開始都會看到這樣的輸出:

Uncompressing Linux...done, booting the kernel.

這也是由decompress_kernel函數内部輸出的,它調用了puts()輸出字元串,

puts是在kernel/include/asm-arm/arch-pxa/uncompress.h中實作的。

執行完解壓過程,再傳回到head.S中,啟動核心:

call_kernel:     bl   cache_clean_flush

          bl   cache_off

          mov r0, #0

          mov r1, r7           @ restore architecture number

          mov pc, r4           @ call kernel

下面就開始真正的核心了。

彙編部分(1)

在網上參考很多高手的文章,又加入了自己的一點兒内容,整理了一下,裡面還有很多不明白的地方,而且也會有了解錯誤的地方,望高手指點,自己也會不斷進行修改

當進入linux核心後,arch/arm/kernel/head-armv.S是核心最先執行的一個檔案,包括從核心入口ENTRY(stext)到

start_kernel之間的初始化代碼,下面以我所是用的平台intel pxa270為例,說明一下他的彙編代碼:

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

2     .type    stext, #function

3 ENTRY(stext)

4     mov r12, r0

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

6     msr cpsr_c, r0           @ and all irqs disabled

7     bl   __lookup_processor_type

8     teq r10, #0              @ invalid processor?

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

10    beq __error

11    bl   __lookup_architecture_type

12    teq r7, #0               @ invalid architecture?

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

14    beq __error

15    bl   __create_page_tables

16    adr lr, __ret            @ return address

17    add pc, r10, #12             @ initialise processor

                               @ (return control reg)

第5行,準備進入SVC工作模式,同時關閉中斷(I_BIT)和快速中斷(F_BIT)

第7行,檢視處理器類型,主要是為了得到處理器的ID以及頁表的flags。

第11行,檢視一些體系結構的資訊。

第15行,建立頁表。

第17行,跳轉到處理器的初始化函數,其函數位址是從__lookup_processor_type中得到的,

需要注意的是第16行,當處理器初始化完成後,會直接跳轉到__ret去執行,

這是由于初始化函數最後的語句是mov pc, lr。

http://hi.baidu.com/anorchidwith/blog/item/cac6e30903fca5266a60fb1f.html 

繼續閱讀