天天看點

bzImage的概要生成過程

1 找到執行目标bzImage

A make bzImage → /top/Makefile

ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/)

include arch/$(ARCH)/Makefile

注解:對于386架構而言,ARCH将會被展開成i386,由于bzImage目标在目前的Makefile中并未找到,是以會到該Makefile中包含的子Makefile中尋找,而/top/arch/i386/Makefile中包含了bzImage目标,故最終會跳到那裡去執行。

B make bzImage → /top/Makefile → /top/arch/i386/Makefile

MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot

vmlinux: arch/i386/vmlinux.lds

bzImage: vmlinux

@$(MAKEBOOT) bzImage

注解:在這裡make bzImage才得以被執行,注意這裡表要依靠目标vmlinux,同時給目标vmlinux增加ld腳本arch/i386/vmlinux.lds,而vmlinux定義在/top/Makefile中,當vmlinux完全生成後,才會執行下面的@$(MAKEBOOT) bzImage。

2 vmlinux的生成

A make bzImage → /top/Makefile

vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs

$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o /

--start-group /

$(CORE_FILES) /

$(DRIVERS) /

$(NETWORKS) /

$(LIBS) /

--end-group /

-o vmlinux

注解:這裡CONFIGURATION沒有用,在使用.config目前條件下可以認為未定義,make在碰到該未定義關鍵字時自動略過。而回到這裡的時候,vmlinux的依賴已經變成了$(CONFIGURATION) init/main.o init/version.o linuxsubdirs arch/i386/vmlinux.lds 。至于其他的定義,分别如下:

CROSS_COMPILE =

TOPDIR := $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)

LD = $(CROSS_COMPILE)ld

CC = $(CROSS_COMPILE)gcc

CORE_FILES =kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o(注意和下面對CORE_FILES的擴充)

SUBDIRS =kernel drivers mm fs net ipc lib

另外在經過/top/arch/i386/Makefile之後:

LD=$(CROSS_COMPILE)ld -m elf_i386

OBJCOPY=$(CROSS_COMPILE)objcopy -O binary -R .note -R .comment -S

LDFLAGS=-e stext

LINKFLAGS =-T $(TOPDIR)/arch/i386/vmlinux.lds $(LDFLAGS)

vmlinux: arch/i386/vmlinux.lds

HEAD := arch/i386/kernel/head.o arch/i386/kernel/init_task.o

SUBDIRS += arch/i386/kernel arch/i386/mm arch/i386/lib

可見這些變量定義已經被找到,同時LD以及OBJCOPY還被重新定義,vmlinux增加了依賴。

init/version.o: init/version.c include/linux/compile.h include/config/MARKER

$(CC) $(CFLAGS) $(CFLAGS_KERNEL) -DUTS_MACHINE='"$(ARCH)"' -c -o init/version.o init/version.c

init/main.o: init/main.c include/config/MARKER

$(CC) $(CFLAGS) $(CFLAGS_KERNEL) $(PROFILING) -c -o $*.o $<

關鍵點一:DRIVERS

這裡關于DRIVERS需要特殊說明下,從/top/Makefile中可知:

ifeq (.config,$(wildcard .config))

include .config

endif

DRIVERS-y :=

DRIVERS =drivers/block/block.o /

drivers/char/char.o /

drivers/misc/misc.o /

drivers/net/net.o /

drivers/media/media.o

DRIVERS-$(CONFIG_PARPORT) += drivers/parport/driver.o

…….

DRIVERS-$(CONFIG_DRM) += drivers/char/drm/drm.o

DRIVERS += $(DRIVERS-y)

這裡的首先解釋下ifeq (.config,$(wildcard .config)) ,$(wildcard .config)表示wildcard函數,而.config是該函數的參數,這個函數表示目前目錄下是否存在.config檔案,如果存在就傳回.config,如果不存在就傳回空,一般情況下,我們在編譯核心的時候,都會首先執行make menuconfig然後save,這時候就會在/top目錄下生成一個.config檔案,是以當make走到此處的時候,該條件為真,也就是将會include .config 。.config檔案中的内容一般如下:

…..

CONFIG_PARPORT =y

CONFIG_DRM =y

…..

也就是我們在make menuconfig中選擇某個選項的時候,相應的變量例如CONFIG_X86_BSWAP的值就被設定成y,而在前面/top/Makefile中DRIVERS-y:則會依據這些宏定義而不斷地增加需要編譯的對象,例如DRIVERS-$(CONFIG_PARPORT) += drivers/parport/driver.o就表示,如果CONFIG_PARPORT=y,也就是我們在make menuconfig的時候,選擇了parport,則.config将會設定CONFIG_PARPORT =y,而/top/Makefile中又包含了.config,是以DRIVERS-$(CONFIG_PARPORT) += drivers/parport/driver.o就被擴充成: DRIVERS-y += drivers/parport/driver.o。如果沒選擇parport,則就被擴充成DRIVERS- += drivers/parport/driver.o,而DRIVERS-在Makefile中時不會用到的,但是從DRIVERS += $(DRIVERS-y)可知,DRIVER-y是被合并到DRIVERS中的,DRIVERS又被vmlinux所使用,故最終将會将drivers/parport/driver.o編譯到vmlinux中去的。至于NETWORKS、LIBS和DRIVERS類似,這裡就不再詳叙。

關鍵點二:linuxsubdirs

關于linuxsubdirs其最後的展開就是make -C 所有的子目錄SUBDIRS=kernel drivers mm fs net ipc lib(也是在/top/Makefile中定義的),而其在編譯每個子目錄的時候,子目錄決定是否編譯相應的對象檔案也是依賴于.config中的類似CONFIG_PARPORT =y的定義。

關鍵點三:關于--start-group ARCHIVES --end-group

ARCHIVES為一系列的對象檔案,所有的這些對象中的符号引用将會共享。

The ARCHIVES should be a list of archive files. They may be either explicit file names, or `-l' options. The specified archives are searched repeatedly until no new undefined references are created. Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference. By grouping the archives, they all be searched repeatedly until all possible references are resolved. Using this option has a significant performance cost. It is best to use it only when there are unavoidable circular references between two or more archives.

關鍵點四:關于Rules.make

在所有的linuxsubdirs中的Makefile中都有include Rules.make語句,而Rules.make中包含了一些通用的編譯規則。

最終展開為:

gcc -D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 -c -o init/main.o init/main.c

gcc -D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 -DUTS_MACHINE='"i386"' -c -o init/version.o init/version.c

這裡解釋下各個符号參數的含義:

&#61548; -DXX,将源碼中所有的宏XX 替代為1;

&#61548; -I,增加頭檔案搜尋目錄;

&#61548; -Wall,打開所有的可選警告;

&#61548; -Wstrict-prototypes,如果定義或者聲明的函數沒有指定參數類型,則警告;

&#61548; -O2,最佳優化

&#61548; -pipe,使用管道在編譯stage之間通信,而不使用臨時檔案;

&#61548; -march,産生指定架構類型的代碼;

&#61548; -c,編譯/彙編源檔案,但是不連接配接,這裡這樣是因為最後我們會自己調用ld來連接配接;

&#61548; -o ‘FILE’,指定輸出檔案名,如果沒有指定,則将使用預設的檔案名;

// 編譯kernel目錄下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C kernel

make[1]: Entering directory `/root/linux/kernel'

make all_targets

make[2]: Entering directory `/root/linux/kernel'

make[2]: Nothing to be done for `all_targets'.

make[2]: Leaving directory `/root/linux/kernel'

make[1]: Leaving directory `/root/linux/kernel'

// 編譯drivers目錄下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C drivers

make[1]: Entering directory `/root/linux/drivers'

make -C block

make[2]: Entering directory `/root/linux/drivers/block'

make all_targets

……

make all_targets

make[2]: Entering directory `/root/linux/drivers'

make[2]: Nothing to be done for `all_targets'.

make[2]: Leaving directory `/root/linux/drivers'

make[1]: Leaving directory `/root/linux/drivers'

// 編譯mm目錄下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C mm

make[1]: Entering directory `/root/linux/mm'

……

// 編譯arch/i386/kernel下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C arch/i386/kernel

make[1]: Entering directory `/root/linux/arch/i386/kernel'

make[1]: Nothing to be done for `all'.

make[1]: Leaving directory `/root/linux/arch/i386/kernel'

// 編譯arch/i386/mm下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C arch/i386/mm

make[1]: Entering directory `/root/linux/arch/i386/mm'

make all_targets

make[2]: Entering directory `/root/linux/arch/i386/mm'

make[2]: Nothing to be done for `all_targets'.

make[2]: Leaving directory `/root/linux/arch/i386/mm'

make[1]: Leaving directory `/root/linux/arch/i386/mm'

// 編譯arch/i386/lib下的檔案

make CFLAGS="-D__KERNEL__ -I/root/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strict-aliasing -pipe -mpreferred-stack-boundary=2 -march=i686 " -C arch/i386/lib

make[1]: Entering directory `/root/linux/arch/i386/lib'

make all_targets

make[2]: Entering directory `/root/linux/arch/i386/lib'

make[2]: Nothing to be done for `all_targets'.

make[2]: Leaving directory `/root/linux/arch/i386/lib'

make[1]: Leaving directory `/root/linux/arch/i386/lib'

// 好,到此為止,所有涉及到核心的子目錄都已經編譯完畢,将他們連結起來

ld -m elf_i386 -T /root/linux/arch/i386/vmlinux.lds -e stext arch/i386/kernel/head.o arch/i386/kernel/init_task.o init/main.o init/version.o /

--start-group /

arch/i386/kernel/kernel.o arch/i386/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o /

drivers/block/block.o drivers/char/char.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/char/drm/drm.o drivers/ide/idedriver.o drivers/scsi/scsidrv.o drivers/cdrom/driver.o drivers/sound/sounddrivers.o drivers/pci/driver.o drivers/pcmcia/pcmcia.o drivers/net/pcmcia/pcmcia_net.o drivers/pnp/pnp.o drivers/video/video.o drivers/usb/usbdrv.o /

net/network.o /

/root/linux/arch/i386/lib/lib.a /root/linux/lib/lib.a /root/linux/arch/i386/lib/lib.a /

--end-group /

-o vmlinux

這裡解釋下各個符号參數的含義:

&#61548; -mEMULATION,仿效EMULATION連結器,例如-m elf_i386。ld –verbose用來顯示目前ld所支援的連接配接器,同時還會顯示該連結器涉及到的環境變量定義腳本;

&#61548; -T,指定連結腳本檔案;

&#61548; -e ENTRY,使用ENTRY符号作為程式開始執行點;

至此,包含核心所有相關子產品的檔案vmlinux已經完全生成,注意,我們在腳本檔案vmlinux.lds中将其實位址設定成0xC0100000,這樣核心中所有的符号絕對位址都以0xC0100000加上其相對于檔案0位置的偏移位址構成,0xC0100000該位址經過Linux+CPU頁式影射後就是實體位址0x100000。

3 vmlinux.lds

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")

OUTPUT_ARCH(i386)

ENTRY(_start)

SECTIONS

{

. = 0xC0000000 + 0x100000;

_text = .;

.text : {

*(.text)

*(.fixup)

*(.gnu.warning)

} = 0x9090

.text.lock : { *(.text.lock) }

_etext = .;

.rodata : { *(.rodata) }

.kstrtab : { *(.kstrtab) }

. = ALIGN(16);

__start___ex_table = .;

__ex_table : { *(__ex_table) }

__stop___ex_table = .;

__start___ksymtab = .;

__ksymtab : { *(__ksymtab) }

__stop___ksymtab = .;

.data : {

*(.data)

CONSTRUCTORS

}

_edata = .;

. = ALIGN(8192);

.data.init_task : { *(.data.init_task) }

. = ALIGN(4096);

__init_begin = .;

.text.init : { *(.text.init) }

.data.init : { *(.data.init) }

. = ALIGN(16);

__setup_start = .;

.setup.init : { *(.setup.init) }

__setup_end = .;

__initcall_start = .;

.initcall.init : { *(.initcall.init) }

__initcall_end = .;

. = ALIGN(4096);

__init_end = .;

. = ALIGN(4096);

.data.page_aligned : { *(.data.idt) }

. = ALIGN(32);

.data.cacheline_aligned : { *(.data.cacheline_aligned) }

__bss_start = .;

.bss : {

*(.bss)

}

_end = . ;

/DISCARD/ : {

*(.text.exit)

*(.data.exit)

*(.exitcall.exit)

}

.stab 0 : { *(.stab) }

.stabstr 0 : { *(.stabstr) }

.stab.excl 0 : { *(.stab.excl) }

.stab.exclstr 0 : { *(.stab.exclstr) }

.stab.index 0 : { *(.stab.index) }

.stab.indexstr 0 : { *(.stab.indexstr) }

.comment 0 : { *(.comment) }

}

關于程式中.text,.data,.bss等段的說明:

由于曆史原因,C程式一直由下列幾部分組成:

1、 正文段。這是由CPU執行的機器指令部分。通常,正文段是可共享的,是以即使是經常執行的程式(如文本編輯程式、C編譯程式、shell等)在存儲器中也隻需有一個副本,另外,正文段常常是隻讀的,以防止程式由于意外事故而修改其自身的指令。

2、 初始化資料段。通常将此段稱為資料段,它包含了程式中需賦初值的變量。例如, C程式中任何函數之外的說明:

int maxcount = 99; 使此變量以初值存放在初始化資料段中。

3、 非初始化資料段。通常将此段稱為bss 段,在程式開始執行之前,核心将此段初始化為0。函數外的說明:

long sum[1000] ; 使此變量存放在非初始化資料段中。

4、 棧。自動變量以及每次函數調用時所需儲存的資訊都存放在此段中。每次函數調用時,其傳回位址、以及調用者的環境資訊(例如某些機器寄存器)都存放在棧中。然後,新被調用的函數在棧上為其自動和臨時變量配置設定存儲空間。通過以這種方式使用棧, C函數可以遞歸調用。

5、 堆。通常在堆中進行動态存儲配置設定。由于曆史上形成的慣例,堆位于非初始化資料段頂和棧底之間。

|--------------------|

| 指令行參數和 |

| 環境變量 |

|--------------------|

| 棧 |

|--------------------|

| ↓ |

| |

| |

| |

| |

| ↑ |

|--------------------|

| 堆 |

|--------------------|+--

| | |

| 未初始化的資料 | ++ 由exec賦初值0

| | |

|--------------------|+--

| 初始化的資料 | |

|--------------------| ++ exec從程式檔案中讀取

| 正文 | |

|--------------------|+--

從圖中可以看到末初始化資料段的内容并不存放在磁盤程式檔案中。需要存放在磁盤程式檔案中的段隻有正文段和初始化資料段。

關于ld主要功能簡介

1、 ld用來組合對象檔案和檔案檔案,重新部署其中的資料以及綁定符号引用。編譯程式的最後一步通常就是ld, ld通常使用BFD庫來操作對象檔案,這使得ld可以按照多種不同的格式例如COFF、a.out,來讀取、組合以及寫對象檔案。不同的格式可以組合在一起産生任何可用類型的目标檔案;

2、 ld 接受按照AT&T連結指令語言文法編寫的連結指令語言檔案,進而對連結過程實施明确的整體上的控制;

3、 每個連結過程都是通過連結腳本來控制的,該腳本按照連結器指令語言編寫。連結腳本的主要目的就是用來描述輸入檔案中的section應該如何被影射到輸出檔案中去,同時還控制輸出檔案的記憶體布局。如果沒有提供連結腳本給連結器,那麼連接配接器将會采用預設的腳本,其已經被編譯到連結器可執行檔案中去了。使用ld –verbose可以顯示預設的連結腳本。

4 bboosect和bsetup的生成

A make bzImage → /top/Makefile → /top/arch/i386/Makefile

MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot

vmlinux: arch/i386/vmlinux.lds

bzImage: vmlinux

@$(MAKEBOOT) bzImage

現在到達@$(MAKEBOOT) bzImage,這裡的@表示執行該條指令的時候不要在顯示器上顯示,-C表示執行子目錄中的Makefile。對于MAKEBOOT中的$(MAKE),make指令在執行Makefile時會自動将其解釋為make,也就是make指令本身。ARCH則為/top/Makefile中導出的ARCH,即i386:

export VERSION PATCHLEVEL SUBLEVEL EXTRAVERSION KERNELRELEASE ARCH /

CONFIG_SHELL TOPDIR HPATH HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC /

CPP AR NM STRIP OBJCOPY OBJDUMP MAKE MAKEFILES GENKSYMS MODFLAGS PERL

最終@$(MAKEBOOT) bzImage被解釋為:@make -C arch/i386/boot bzImage,我們來看看其究竟做了些什麼。

B make bzImage → /top/Makefile → /top/arch/i386/Makefile→ /top/arch/i386/boot/Makefile

bzImage: $(CONFIGURE) bbootsect bsetup compressed/bvmlinux tools/build

$(OBJCOPY) compressed/bvmlinux compressed/bvmlinux.out

tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT_DEV) > bzImage

注解:在這裡make bzImage才得以被執行,執行前,需要依靠的對象為bbootsect bsetup compressed/bvmlinux tools/build,是以将首先編譯這四個對象。

* bbosect的生成過程

bbootsect的編譯還是在目前的/top/arch/i386/boot/Makefile中:

bbootsect: bbootsect.o

$(LD) -Ttext 0x0 -s -oformat binary $< -o $@

bbootsect.o: bbootsect.s

$(AS) -o $@ $<

bbootsect.s: bootsect.S Makefile $(BOOT_INCL)

$(CPP) $(CPPFLAGS) -D__BIG_KERNEL__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@

這裡$<為所依賴對象的第一個元素,例如第四行的$<即bboosect.S,而$@表示目的,例如第四行的$@表示bboosect.o。

LD、AS和CPP的定義均在/top/Makefile中,至于SVGA_MODE和RAMDISK這時暫無需考慮,不影響了解:

HPATH = $(TOPDIR)/include

CROSS_COMPILE =

AS = $(CROSS_COMPILE)as

LD = $(CROSS_COMPILE)ld

CC = $(CROSS_COMPILE)gcc

CPP = $(CC) –E

CPPFLAGS := -D__KERNEL__ -I$(HPATH)

而在/top/arch/i386Makefile中,LD又被重新定義,表示該目錄及該目錄下的子目錄統統使用目前的LD定義:

LD=$(CROSS_COMPILE)ld -m elf_i386

BOOT_INCL定義在/top/arch/i386/boot/Makefile中:

BOOT_INCL = $(TOPDIR)/include/linux/config.h /

$(TOPDIR)/include/linux/autoconf.h /

$(TOPDIR)/include/asm/boot.h

是以這裡最終的展開為:

gcc -E -D__KERNEL__ -I/root/linux/include -D__BIG_KERNEL__ -traditional -DSVGA_MODE=NORMAL_VGA bootsect.S -o bbootsect.s

as -o bbootsect.o bbootsect.s

ld -m elf_i386 -Ttext 0x0 -s -oformat binary bbootsect.o -o bbootsect

這裡我們解釋下其中的一些含義,其中第一句中關于gcc:

&#61548; -E,我們知道編譯可以分為4個階段,預處理,編譯,彙編和連結。前三個步驟适用于源檔案,結果産生一個object檔案;而第四個步驟用于将所有的Object檔案連結組合到可執行檔案中。-E選項用來表示在預處理階段之後就不要繼續下一步了,直接停止,輸出即為經過預處理的源代碼,對于C語言而言,就是将所有的外部引用添加到目标檔案中;

&#61548; - traditional,用于支援傳統的C文法;

&#61548; -DSVGA_MODE=NORMAL_VGA,也是-D的一個用法,用來将源碼中所有的SVGA_MODE宏替換成NORMAL_VGA;

&#61548; -o FILE,将結果輸出到FILE;

這裡産生的bbootsect.s基本文法和bootsect.S中本完全一樣,隻是所有的變量以及宏定義的外部引用全部被實際的數值所代替,使其不再依賴于其他檔案。

第二句中關于as,注意as輸出的訓示隻是輸出還沒有連結的object檔案:

&#61548; -o,使用as必定有對象檔案輸出,預設的為a.out,除非用-o來指定一個特定檔案名的檔案。

第三句關于ld:

&#61548; -Ttext ORG,用來設定.text段的起始位址,這裡為0x0;

&#61548; -s,在輸出檔案中忽略所有的符号資訊;

&#61548; -oformat,用來設定輸出檔案的類型,可以通過objdump –i來檢視可用的二進制格式。

這裡産生的bbootsect即是連結後的二進制檔案,其中已經去處了所有的符号資訊,所有的其他非相關資訊,隻剩下純代碼,并且第一條指令的起始位址為0。

* bsetup的生成過程

bsetup: bsetup.o

$(LD) -Ttext 0x0 -s -oformat binary -e begtext -o $@ $<

bsetup.o: bsetup.s

$(AS) -o $@ $<

bsetup.s: setup.S video.S Makefile $(BOOT_INCL) $(TOPDIR)/include/linux/version.h $(TOPDIR)/include/linux/compile.h

$(CPP) $(CPPFLAGS) -D__BIG_KERNEL__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@

基本原理和bbootsect類似,最終展開為:

gcc -E -D__KERNEL__ -I/root/linux/include -D__BIG_KERNEL__ -traditional -DSVGA_MODE=NORMAL_VGA setup.S -o bsetup.s

as -o bsetup.o bsetup.s

ld -m elf_i386 -Ttext 0x0 -s -oformat binary -e begtext -o bsetup bsetup.o

這裡唯一不同的就是最後一句的-e begtext,此處的begtext=0,定義在setup.S中。另外需要注意的是,bbootsect和bsetup的起始位址都為0,但是其被拷貝到記憶體中的時候起始位址卻為0x90000和0x90200,這樣假設原來bootsect.S中有個變量A,則在編譯連結之後,變量A所暗示的實體記憶體位址就是其相對于bbootsect的起始位址的偏移+bbootsect的起始位址=Addr,通路A處的指令中的位址也就是為Addr。但真實的拷貝到記憶體中時,A的真實實體位址卻未0x90000+Addr,那麼該如何辦呢?其實在bootsetup.S,一開始首先通過指令ljmp $INITSEG, $go設定CS=9000H,IP為go相對于該檔案開始初的偏移,同時也将自己的DS、ES、FS設定成0x9000,這樣雖然A的位址是A,但是CPU通路A的位址是通過DS:Addr的方式,也就相當于邏輯位址Addr加到DS*10H上進而實作了實體位址0x90000+Addr,是以才得以正确通路所有的資料和指令,對于指令就是CS:IP,則順着CS:go往下走就好了;對于setup.S,由于setup.S是bootsect.S調用的,而bootsect.S在調用setup.S的時候采用的是ljmp $SETUPSEG, $0,進而将CS:IP設定成9020H:0000,剛好是bsetup在記憶體中的起始位置,随後也同時設定了DS,是以也可以正确通路所有的指令和資料。

5 compressed/bvmlinux的生成

A make bzImage → /top/Makefile → /top/arch/i386/Makefile → /top/arch/i386/boot /Makefile → /top/arch/i386/boot /compressed/Makefile

HEAD = head.o

SYSTEM = $(TOPDIR)/vmlinux

OBJECTS = $(HEAD) misc.o

CFLAGS = $(CPPFLAGS) -O2 -DSTDC_HEADERS

ZLDFLAGS = -e startup_32

BZIMAGE_OFFSET = 0x100000

BZLINKFLAGS = -Ttext $(BZIMAGE_OFFSET) $(ZLDFLAGS)

bvmlinux: piggy.o $(OBJECTS)

$(LD) $(BZLINKFLAGS) -o bvmlinux $(OBJECTS) piggy.o

head.o: head.S

$(CC) $(AFLAGS) -traditional -c head.S

misc.o: misc.c

$(CC) $(CFLAGS) -c misc.c

piggy.o: $(SYSTEM)

tmppiggy=_tmp_$$$$piggy; /

rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; /

$(OBJCOPY) $(SYSTEM) $$tmppiggy; /

gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; /

echo "SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; }}" > $$tmppiggy.lnk; /

$(LD) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 -T $$tmppiggy.lnk; /

rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk

這裡涉及到/top/Makefile中的一些定義:

CPPFLAGS := -D__KERNEL__ -I$(HPATH)

AFLAGS := -D__ASSEMBLY__ $(CPPFLAGS)

最終展開為:

tmppiggy=_tmp_$$piggy; /

rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk; /

objcopy -O binary -R .note -R .comment -S /root/linux/vmlinux $tmppiggy; /

gzip -f -9 < $tmppiggy > $tmppiggy.gz; /

echo "SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; }}" > $tmppiggy.lnk; /

ld -m elf_i386 -r -o piggy.o -b binary $tmppiggy.gz -b elf32-i386 -T $tmppiggy.lnk; /

rm -f $tmppiggy $tmppiggy.gz $tmppiggy.lnk

gcc -D__ASSEMBLY__ -D__KERNEL__ -I/root/linux/include -traditional -c head.S

gcc -D__KERNEL__ -I/root/linux/include -O2 -DSTDC_HEADERS -c misc.c

ld -m elf_i386 -Ttext 0x100000 -e startup_32 -o bvmlinux head.o misc.o piggy.o

前面生成的vmlinux是elf-i386類型的可執行檔案,這裡objcopy用來将所有的這些可執行檔案頭資訊全部去除掉,這裡解釋下其中的參數:

&#61548; -O BFDNAME,将輸出檔案設定成binary格式,去除所有的可執行檔案格式的檔案;

&#61548; -R SECTIONNAME,将SECTIONNAME的section從輸出檔案中去除;

&#61548; -S,不要拷貝relocation和symbol資訊;

這樣拷貝後的檔案就不帶任何用來識别可執行檔案的資訊了,而是完全的執行代碼檔案$tmppiggy,再将其通過gzip壓縮,壓縮後的檔案$tmppiggy.gz又成了elf檔案,通過ld來relocation該檔案,目标位piggy.o 。最後将head.o,misc.o,piggy.o連結到一起,構成了bvmlinux = head.o + misc.o + piggy.o, 最後的bvmlinux其實位址為0x100000,開始符号為startup_32,也就是讓startup_32的位址為0x100000,剛好是bvmlinux被bootsect.S拷貝到1M記憶體處的第一條指令執行記憶體位址。

6 tools/build的生成

bulid.c的生成采用普通的make build。

7 bzImage的生成

A make bzImage → /top/Makefile → /top/arch/i386/Makefile→ /top/arch/i386/boot/Makefile

bzImage: $(CONFIGURE) bbootsect bsetup compressed/bvmlinux tools/build

$(OBJCOPY) compressed/bvmlinux compressed/bvmlinux.out

tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT_DEV) > bzImage

我們回到/top/arch/i386/boot/Makefile中,展開即為:

objcopy -O binary -R .note -R .comment -S compressed/bvmlinux compressed/bvmlinux.out

tools/build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzImage

首先将bvmlinux剝除掉可執行檔案資訊生成bvmlinux.out,然後通過tools/build将bboosect,bsetup,compressed/bvmlinux.out 和CURRENT組合,形成了bzImage。這裡bboosect,bsetup,compressed/bvmlinux.out統統為純二進制指令檔案,沒有其他的資訊。

8 build.c的流程

如果核心為壓縮的,則在調用build的時候應該加上-b選項,如果啟動root檔案系統沒有指定,則應該加上CURRENT選項:

build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzImage

build會将bbootsect共512K放在bzImage檔案的最起始處,bsetup放在随後,而bvmlinux.out放在最後,最後構成了真正的核心bzImage,另外build還有做如下這幾個事情:

&#61548; 如果有-b選項,則會檢測bvmlinux.out的大小不要超過0x280000(2.5M),否則大小不要超過0x7F000(508K);

&#61548; 将bvmlinux.out的大小/16寫入到bbootsect的500位元組處;

&#61548; 将bsetup的大小/512,也就是bsetup要占用的扇區數寫入到bbootsect的497位元組處;

&#61548; 依據CURRENT将目前 / 所處的root minor_root major_root寫入到bbootsect的508位元組處;

bzImage的組成:

bootsect <---ld bootsect.o

setup <---ld setup.o

vmlinux.bin <---objcopy arch/i386/boot/compressed/vmlinux

|--------------|------------|------------------------|

|bootsect------|---setup-----|------vmlinux.bin----|

|--------------|------------|------------------------|

arch/i386/boot/compressed/vmlinux的組成:

head.o <--- head.S

misc.o <--- misc.c

piggy.o <--- vmlinux.bin.gz <--- vmlinux.bin <--- vmlinux(源碼目錄下)

|-----------|-------------|---------------------------------|

|head.o-----|--misc.o-----|--------------piggy.o------------|

|-----------|-------------|---------------------------------|

注意不要混淆:

編譯過程中會産生兩個vmlinux.bin:

arch/i386/boot/vmlinux.bin <---objcopy arch/i386/boot/compressed/vmlinux

arch/i386/boot/compressed/vmlinux.bin <---objcopy vminux

兩個vmlinux:

源碼根目錄下的vmlinux

arch/i386/boot/compressed/vmlinux <---head.o misc.o piggy.o

---------------------bzImage

-------------------------|

----------------|--------|-------------------|

------------bootsect--setup-------------vmlinux.bin

--------------------------------------------|

-------------------------------------------vmlinux

--------------------------------------|----------|-----------------|

----------------------------------head.o---misc.o-------------piggy.o

------------------------------------------------------------------|

----------------------------------------------------------------vmlinux.bin.gz

------------------------------------------------------------------|

----------------------------------------------------------------vmlinux.bin

------------------------------------------------------------------|

----------------------------------------------------------------vmlinux

其中最後是由實用程式build(在build目錄下生成的)将bootsect,setup,vmlinux.bin拼接到一塊成為bzImage

繼續閱讀