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
这里解释下各个符号参数的含义:
 -DXX,将源码中所有的宏XX 替代为1;
 -I,增加头文件搜索目录;
 -Wall,打开所有的可选警告;
 -Wstrict-prototypes,如果定义或者声明的函数没有指定参数类型,则警告;
 -O2,最佳优化
 -pipe,使用管道在编译stage之间通信,而不使用临时文件;
 -march,产生指定架构类型的代码;
 -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'
// 好,到此为止,所有涉及到内核的子目录都已经编译完毕,将他们链接起来
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
这里解释下各个符号参数的含义:
 -mEMULATION,仿效EMULATION链接器,例如-m elf_i386。ld –verbose用来显示当前ld所支持的连接器,同时还会显示该链接器涉及到的环境变量定义脚本;
 -T,指定链接脚本文件;
 -e ENTRY,使用ENTRY符号作为程序开始执行点;
至此,包含内核所有相关模块的文件vmlinux已经完全生成,注意,我们在脚本文件vmlinux.lds中将其实地址设置成0xC0100000,这样内核中所有的符号绝对地址都以0xC0100000加上其相对于文件0位置的偏移地址构成,0xC0100000该地址经过Linux+CPU页式影射后就是物理地址0x100000。
3 vmlinux.lds
/*********************************************************************************************************
OUTPUT_FORMAT(DEFAULT, BIG, LITTLE)
改命令告诉ld输出文件的格式,另外,如果选择了-EB选项,ld将会使用BIG来控制输出格式,如果选择了-EL,ld将会使用LITTLE来控制输出格式,否则ld将会使用DEFAULT来控制输出格式。-EB用来指定大端字节,-EL用来指定小端字节。
OUTPUT_ARCH(BFDARCH)
指定特定计算机架构的输出格式。
ENTRY(SYMBOL)
程序中第一条指令称之为入口点,ENTRY用来设置入口点,入口点为源码中的符号,这里就是_start。
*********************************************************************************************************/
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
/*********************************************************************************************************
SECTIONS命令用来告诉ld如何将输入sections影射到输出sections,以及在内存中如何放置这些sections。
*********************************************************************************************************/
SECTIONS
{
/*********************************************************************************************************
这里’.’为当前位置计数器,这里将代码加载在起始位置为0xC0000000 + 0x100000处;_text = .用来获取当前位置计数器的值。
*********************************************************************************************************/
. = 0xC0000000 + 0x100000;
_text = .; /* Text and read-only data */
/*********************************************************************************************************
这里*(.text)、*(.fixup)、*(.gnu.warning)指示所有的.text、.fixup、.gnu.warning输入sections都被放在输出的.text中。0x9090用来填充几个section之间的空洞的数值,通常这个空洞是由于设置’.’造成的,这里没有其实没有空洞的。
*********************************************************************************************************/
.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),用来设置当前位置计数器的位置,但是位置时在当前位置后面的第一个和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将不会出现在输出文件中。
*********************************************************************************************************/
/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执行的机器指令部分。通常,正文段是可共享的,所以即使是经常执行的程序(如文本编辑程序、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:
 -E,我们知道编译可以分为4个阶段,预处理,编译,汇编和链接。前三个步骤适用于源文件,结果产生一个object文件;而第四个步骤用于将所有的Object文件链接组合到可执行文件中。-E选项用来表示在预处理阶段之后就不要继续下一步了,直接停止,输出即为经过预处理的源代码,对于C语言而言,就是将所有的外部引用添加到目标文件中;
 - traditional,用于支持传统的C语法;
 -DSVGA_MODE=NORMAL_VGA,也是-D的一个用法,用来将源码中所有的SVGA_MODE宏替换成NORMAL_VGA;
 -o FILE,将结果输出到FILE;
这里产生的bbootsect.s基本语法和bootsect.S中本完全一样,只是所有的变量以及宏定义的外部引用全部被实际的数值所代替,使其不再依赖于其他文件。
第二句中关于as,注意as输出的指示只是输出还没有链接的object文件:
 -o,使用as必定有对象文件输出,缺省的为a.out,除非用-o来指定一个特定文件名的文件。
第三句关于ld:
 -Ttext ORG,用来设置.text段的起始地址,这里为0x0;
 -s,在输出文件中忽略所有的符号信息;
 -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用来将所有的这些可执行文件头信息全部去除掉,这里解释下其中的参数:
 -O BFDNAME,将输出文件设置成binary格式,去除所有的可执行文件格式的文件;
 -R SECTIONNAME,将SECTIONNAME的section从输出文件中去除;
 -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还有做如下这几个事情:
 如果有-b选项,则会检测bvmlinux.out的大小不要超过0x280000(2.5M),否则大小不要超过0x7F000(508K);
 将bvmlinux.out的大小/16写入到bbootsect的500字节处;
 将bsetup的大小/512,也就是bsetup要占用的扇区数写入到bbootsect的497字节处;
 依据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