bzImage的概要生成过程
1 找到執(zhí)行目標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目標,故最終會跳到那里去執(zhí)行。
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才得以被執(zhí)行,注意這里表要依靠目標vmlinux,同時給目標vmlinux增加ld腳本arch/i386/vmlinux.lds,而vmlinux定義在/top/Makefile中,當vmlinux完全生成后,才會執(zhí)行下面的@$(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的依賴已經(jīng)變成了$(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
另外在經(jīng)過/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
可見這些變量定義已經(jīng)被找到,同時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函數(shù),而.config是該函數(shù)的參數(shù),這個函數(shù)表示當前目錄下是否存在.config文件,如果存在就返回.config,如果不存在就返回空,一般情況下,我們在編譯內(nèi)核的時候,都會首先執(zhí)行make menuconfig然后save,這時候就會在/top目錄下生成一個.config文件,因此當make走到此處的時候,該條件為真,也就是將會include .config 。.config文件中的內(nèi)容一般如下:
…..
CONFIG_PARPORT =y
CONFIG_DRM =y
…..
也就是我們在make menuconfig中選擇某個選項的時候,相應的變量例如CONFIG_X86_BSWAP的值就被設置成y,而在前面/top/Makefile中DRIVERS-y:則會依據(jù)這些宏定義而不斷地增加需要編譯的對象,例如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中包含了一些通用的編譯規(guī)則。
最終展開為:
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
這里解釋下各個符號參數(shù)的含義:
 -DXX,將源碼中所有的宏XX 替代為1;
 -I,增加頭文件搜索目錄;
 -Wall,打開所有的可選警告;
 -Wstrict-prototypes,如果定義或者聲明的函數(shù)沒有指定參數(shù)類型,則警告;
 -O2,最佳優(yōu)化
 -pipe,使用管道在編譯stage之間通信,而不使用臨時文件;
 -march,產(chǎn)生指定架構類型的代碼;
 -c,編譯/匯編源文件,但是不連接,這里這樣是因為最后我們會自己調用ld來連接;
 -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'
// 好,到此為止,所有涉及到內(nèi)核的子目錄都已經(jīng)編譯完畢,將他們鏈接起來
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
這里解釋下各個符號參數(shù)的含義:
 -mEMULATION,仿效EMULATION鏈接器,例如-m elf_i386。ld –verbose用來顯示當前l(fā)d所支持的連接器,同時還會顯示該鏈接器涉及到的環(huán)境變量定義腳本;
 -T,指定鏈接腳本文件;
 -e ENTRY,使用ENTRY符號作為程序開始執(zhí)行點;
至此,包含內(nèi)核所有相關模塊的文件vmlinux已經(jīng)完全生成,注意,我們在腳本文件vmlinux.lds中將其實地址設置成0xC0100000,這樣內(nèi)核中所有的符號絕對地址都以0xC0100000加上其相對于文件0位置的偏移地址構成,0xC0100000該地址經(jīng)過Linux+CPU頁式影射后就是物理地址0x100000。
3 vmlinux.lds
/*********************************************************************************************************
OUTPUT_FORMAT(DEFAULT, BIG, LITTLE)
改命令告訴ld輸出文件的格式,另外,如果選擇了-EB選項,ld將會使用BIG來控制輸出格式,如果選擇了-EL,ld將會使用LITTLE來控制輸出格式,否則ld將會使用DEFAULT來控制輸出格式。-EB用來指定大端字節(jié),-EL用來指定小端字節(jié)。
OUTPUT_ARCH(BFDARCH)
指定特定計算機架構的輸出格式。
ENTRY(SYMBOL)
程序中第一條指令稱之為入口點,ENTRY用來設置入口點,入口點為源碼中的符號,這里就是_start。
*********************************************************************************************************/
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
/*********************************************************************************************************
SECTIONS命令用來告訴ld如何將輸入sections影射到輸出sections,以及在內(nèi)存中如何放置這些sections。
*********************************************************************************************************/
SECTIONS
{
/*********************************************************************************************************
這里’.’為當前位置計數(shù)器,這里將代碼加載在起始位置為0xC0000000 + 0x100000處;_text = .用來獲取當前位置計數(shù)器的值。
*********************************************************************************************************/
. = 0xC0000000 + 0x100000;
_text = .; /* Text and read-only data */
/*********************************************************************************************************
這里*(.text)、*(.fixup)、*(.gnu.warning)指示所有的.text、.fixup、.gnu.warning輸入sections都被放在輸出的.text中。0x9090用來填充幾個section之間的空洞的數(shù)值,通常這個空洞是由于設置’.’造成的,這里沒有其實沒有空洞的。
*********************************************************************************************************/
.text : {
*(.text)
*(.fixup)
*(.gnu.warning)
} = 0x9090
.text.lock : { *(.text.lock) } /* out-of-line lock text */
_etext = .; /* End of text section */
.rodata : { *(.rodata) }
.kstrtab : { *(.kstrtab) }
/*********************************************************************************************************
這里ALIGN(EXP),用來設置當前位置計數(shù)器的位置,但是位置時在當前位置后面的第一個和EXP對齊的位置。
*********************************************************************************************************/
. = ALIGN(16); /* Exception table */
__start___ex_table = .;
__ex_table : { *(__ex_table) }
__stop___ex_table = .;
__start___ksymtab = .; /* Kernel symbol table */
__ksymtab : { *(__ksymtab) }
__stop___ksymtab = .;
.data : { /* Data */
*(.data)
CONSTRUCTORS
}
_edata = .; /* End of data section */
. = ALIGN(8192); /* init_task */
.data.init_task : { *(.data.init_task) }
. = ALIGN(4096); /* Init code and data */
__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 : {
*(.bss)
}
_end = . ;
/*********************************************************************************************************
/DISCARD/ section非常特殊,其用來丟棄指定的輸入sections,這些sections將不會出現(xiàn)在輸出文件中。
*********************************************************************************************************/
/DISCARD/ : {
*(.text.exit)
*(.data.exit)
*(.exitcall.exit)
}
/*********************************************************************************************************
這里的.stab 0 :中的0表示該段起始于位置0處。
*********************************************************************************************************/
/* Stabs debugging sections. */
.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執(zhí)行的機器指令部分。通常,正文段是可共享的,所以即使是經(jīng)常執(zhí)行的程序(如文本編輯程序、C編譯程序、shell等)在存儲器中也只需有一個副本,另外,正文段常常是只讀的,以防止程序由于意外事故而修改其自身的指令。
2、 初始化數(shù)據(jù)段。通常將此段稱為數(shù)據(jù)段,它包含了程序中需賦初值的變量。例如, C程序中任何函數(shù)之外的說明:
int maxcount = 99; 使此變量以初值存放在初始化數(shù)據(jù)段中。
3、 非初始化數(shù)據(jù)段。通常將此段稱為bss 段,在程序開始執(zhí)行之前,內(nèi)核將此段初始化為0。函數(shù)外的說明:
long sum[1000] ; 使此變量存放在非初始化數(shù)據(jù)段中。
4、 棧。自動變量以及每次函數(shù)調用時所需保存的信息都存放在此段中。每次函數(shù)調用時,其返回地址、以及調用者的環(huán)境信息(例如某些機器寄存器)都存放在棧中。然后,新被調用的函數(shù)在棧上為其自動和臨時變量分配存儲空間。通過以這種方式使用棧, C函數(shù)可以遞歸調用。
5、 堆。通常在堆中進行動態(tài)存儲分配。由于歷史上形成的慣例,堆位于非初始化數(shù)據(jù)段頂和棧底之間。
|--------------------|
| 命令行參數(shù)和 |
| 環(huán)境變量 |
|--------------------|
| 棧 |
|--------------------|
| ↓ |
| |
| |
| |
| |
| ↑ |
|--------------------|
| 堆 |
|--------------------|+--
| | |
| 未初始化的數(shù)據(jù) | ++ 由exec賦初值0
| | |
|--------------------|+--
| 初始化的數(shù)據(jù) | |
|--------------------| ++ exec從程序文件中讀取
| 正文 | |
|--------------------|+--
從圖中可以看到末初始化數(shù)據(jù)段的內(nèi)容并不存放在磁盤程序文件中。需要存放在磁盤程序文件中的段只有正文段和初始化數(shù)據(jù)段。
關于ld主要功能簡介
1、 ld用來組合對象文件和檔案文件,重新部署其中的數(shù)據(jù)以及綁定符號引用。編譯程序的最后一步通常就是ld, ld通常使用BFD庫來操作對象文件,這使得ld可以按照多種不同的格式例如COFF、a.out,來讀取、組合以及寫對象文件。不同的格式可以組合在一起產(chǎn)生任何可用類型的目標文件;
2、 ld 接受按照AT&T鏈接命令語言語法編寫的鏈接命令語言文件,從而對鏈接過程實施明確的整體上的控制;
3、 每個鏈接過程都是通過鏈接腳本來控制的,該腳本按照鏈接器命令語言編寫。鏈接腳本的主要目的就是用來描述輸入文件中的section應該如何被影射到輸出文件中去,同時還控制輸出文件的內(nèi)存布局。如果沒有提供鏈接腳本給鏈接器,那么連接器將會采用缺省的腳本,其已經(jīng)被編譯到鏈接器可執(zhí)行文件中去了。使用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
現(xiàn)在到達@$(MAKEBOOT) bzImage,這里的@表示執(zhí)行該條命令的時候不要在顯示器上顯示,-C表示執(zhí)行子目錄中的Makefile。對于MAKEBOOT中的$(MAKE),make命令在執(zhí)行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才得以被執(zhí)行,執(zhí)行前,需要依靠的對象為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又被重新定義,表示該目錄及該目錄下的子目錄統(tǒng)統(tǒng)使用當前的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:
 -E,我們知道編譯可以分為4個階段,預處理,編譯,匯編和鏈接。前三個步驟適用于源文件,結果產(chǎn)生一個object文件;而第四個步驟用于將所有的Object文件鏈接組合到可執(zhí)行文件中。-E選項用來表示在預處理階段之后就不要繼續(xù)下一步了,直接停止,輸出即為經(jīng)過預處理的源代碼,對于C語言而言,就是將所有的外部引用添加到目標文件中;
 - traditional,用于支持傳統(tǒng)的C語法;
 -DSVGA_MODE=NORMAL_VGA,也是-D的一個用法,用來將源碼中所有的SVGA_MODE宏替換成NORMAL_VGA;
 -o FILE,將結果輸出到FILE;
這里產(chǎn)生的bbootsect.s基本語法和bootsect.S中本完全一樣,只是所有的變量以及宏定義的外部引用全部被實際的數(shù)值所代替,使其不再依賴于其他文件。
第二句中關于as,注意as輸出的指示只是輸出還沒有鏈接的object文件:
 -o,使用as必定有對象文件輸出,缺省的為a.out,除非用-o來指定一個特定文件名的文件。
第三句關于ld:
 -Ttext ORG,用來設置.text段的起始地址,這里為0x0;
 -s,在輸出文件中忽略所有的符號信息;
 -oformat,用來設置輸出文件的類型,可以通過objdump –i來查看可用的二進制格式。
這里產(chǎn)生的bbootsect即是鏈接后的二進制文件,其中已經(jīng)去處了所有的符號信息,所有的其他非相關信息,只剩下純代碼,并且第一條指令的起始地址為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,但是其被拷貝到內(nèi)存中的時候起始地址卻為0x90000和0x90200,這樣假設原來bootsect.S中有個變量A,則在編譯鏈接之后,變量A所暗示的物理內(nèi)存地址就是其相對于bbootsect的起始地址的偏移+bbootsect的起始地址=Addr,訪問A處的指令中的地址也就是為Addr。但真實的拷貝到內(nèi)存中時,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上從而實現(xiàn)了物理地址0x90000+Addr,因此才得以正確訪問所有的數(shù)據(jù)和指令,對于指令就是CS:IP,則順著CS:go往下走就好了;對于setup.S,由于setup.S是bootsect.S調用的,而bootsect.S在調用setup.S的時候采用的是ljmp $SETUPSEG, $0,從而將CS:IP設置成9020H:0000,剛好是bsetup在內(nèi)存中的起始位置,隨后也同時設置了DS,因此也可以正確訪問所有的指令和數(shù)據(jù)。
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類型的可執(zhí)行文件,這里objcopy用來將所有的這些可執(zhí)行文件頭信息全部去除掉,這里解釋下其中的參數(shù):
 -O BFDNAME,將輸出文件設置成binary格式,去除所有的可執(zhí)行文件格式的文件;
 -R SECTIONNAME,將SECTIONNAME的section從輸出文件中去除;
 -S,不要拷貝relocation和symbol信息;
這樣拷貝后的文件就不帶任何用來識別可執(zhí)行文件的信息了,而是完全的執(zhí)行代碼文件$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內(nèi)存處的第一條指令執(zhí)行內(nèi)存地址。
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剝除掉可執(zhí)行文件信息生成bvmlinux.out,然后通過tools/build將bboosect,bsetup,compressed/bvmlinux.out 和CURRENT組合,形成了bzImage。這里bboosect,bsetup,compressed/bvmlinux.out統(tǒng)統(tǒng)為純二進制指令文件,沒有其他的信息。
8 build.c的流程
如果內(nèi)核為壓縮的,則在調用build的時候應該加上-b選項,如果啟動root文件系統(tǒng)沒有指定,則應該加上CURRENT選項:
build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzImage
build會將bbootsect共512K放在bzImage文件的最起始處,bsetup放在隨后,而bvmlinux.out放在最后,最后構成了真正的內(nèi)核bzImage,另外build還有做如下這幾個事情:
 如果有-b選項,則會檢測bvmlinux.out的大小不要超過0x280000(2.5M),否則大小不要超過0x7F000(508K);
 將bvmlinux.out的大小/16寫入到bbootsect的500字節(jié)處;
 將bsetup的大小/512,也就是bsetup要占用的扇區(qū)數(shù)寫入到bbootsect的497字節(jié)處;
 依據(jù)CURRENT將當前 / 所處的root minor_root major_root寫入到bbootsect的508字節(jié)處;
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------------|
|-----------|-------------|---------------------------------|
注意不要混淆:
編譯過程中會產(chǎn)生兩個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
轉載于:https://www.cnblogs.com/yuzaipiaofei/archive/2011/08/02/4124432.html
總結
以上是生活随笔為你收集整理的bzImage的概要生成过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HUT-1556 网址
- 下一篇: 文件操作一