内核文件是raw binary格式,只包含机器码和数据。主要由setup和vmlinux两部分构成。
内核的构建过程:
1、生成内核链接脚本vmlinux.lds
2、内核子目录built-in.o、模块、符号表的生成
3、elf格式vmlinux与vmlinux.bin的生成
4、压缩elf格式vmlinux与压缩vmlinux.bin生成
5、setup.elf与setup.bin生成
6、setup.bin和vmlinux.bin生成最终的内核镜像bzImage
7、最终vmlinuz的生成
2、各级built-in.o与模块的生成:
内核的Makefile系统
linux kernel的Makefile系统提供简单的编程接口,各级内核子目录或模块的Makefile只需指定obj-y,obj-m等变量即可完成编译。
obj-y是指定内核.o,obj-m指定模块.o,obj-n不编译。
对子目录的递归处理是由scripts/Makefile.build完成的,主要功能为:
1、include子目录的Kbuild或Makefile,读取obj-y,obj-m等变量
2、include scripts/Makefile.lib,对obj-y,obj-m等变量处理,并生成新的变量如subdir-y等
3、built-in.o的生成,根据变量obj-y的值,递归子目录生成的built-in.o与当前目录的.o链接生成
4、module的处理,根据obj-m中的.o文件并递归子目录,生成modules.order,.tmp_versions/*.mod;以便顶级Makefile对模块的生成
5、其它处理,如lib-y的处理等
3、elf格式vmlinux与vmlinux.bin的生成
vmlinux生成:
vmlinux是由各级子目录的built-in.o根据链接脚本arch/x86/kernel/vmlinux.lds链接生成
Makefile:
845 # vmlinux image - including updated kernel symbols 846 vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE 847 ifdef CONFIG_HEADERS_CHECK 848 $(Q)$(MAKE) -f $(srctree)/Makefile headers_check 849 endif 850 ifdef CONFIG_SAMPLES 851 $(Q)$(MAKE) $(build)=samples 852 endif 853 ifdef CONFIG_BUILD_DOCSRC 854 $(Q)$(MAKE) $(build)=Documentation 855 endif 856 $(call vmlinux-modpost) 857 $(call if_changed_rule,vmlinux__) 858 $(Q)rm -f .old_version 726 # Link of vmlinux 727 # If CONFIG_KALLSYMS is set .version is already updated 728 # Generate System.map and verify that the content is consistent 729 # Use + in front of the vmlinux_version rule to silent warning with make -j2 730 # First command is ':' to allow us to use + in front of the rule 731 define rule_vmlinux__ 732 : 733 $(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version)) 734 735 $(call cmd,vmlinux__) 736 $(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd 737 738 $(Q)$(if $($(quiet)cmd_sysmap), \ 739 echo ' $($(quiet)cmd_sysmap) System.map' &&) \ 740 $(cmd_sysmap) $@ System.map; \ 741 if [ $$? -ne 0 ]; then \ 742 rm -f $@; \ 743 /bin/false; \ 744 fi; 745 $(verify_kallsyms) 746 endef 702 # Rule to link vmlinux - also used during CONFIG_KALLSYMS 703 # May be overridden by arch/$(ARCH)/Makefile 704 quiet_cmd_vmlinux__ ?= LD $@ 705 cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \ 706 -T $(vmlinux-lds) $(vmlinux-init) \ 707 --start-group $(vmlinux-main) --end-group \ 708 $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^) 709
vmlinux.bin的生成:
将vmlinux用objcopy将删掉.comment节、符号表、重定位表
arch/x86/boot/compressed/Makefile:
29 OBJCOPYFLAGS_vmlinux.bin := -R .comment -S 30 $(obj)/vmlinux.bin: vmlinux FORCE 31 $(call if_changed,objcopy) scripts/Makefile.lib: 195 quiet_cmd_objcopy = OBJCOPY $@ 196 cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
4、压缩elf格式vmlinux与压缩vmlinux.bin生成
压缩的vmlinux的生成:
将自解压代码,mkpiggy根据vmlinux.bin生成的piggy.o,链接生成压缩的vmlinux(ELF格式)
piggy.o的生成:
1、将vmlinux.bin压缩生成压缩文件
2、根据压缩文件,由mkpiggy产生piggy.S文件,数据为压缩文件
3、编译piggy.S生成piggy.o
4、将piggy.o与自解压代码链接生成elf格式的vmlinux
arch/x86/boot/compressed/Makefile:
42 vmlinux.bin.all-y := $(obj)/vmlinux.bin 43 vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs 44 45 $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE 46 $(call if_changed,gzip) 47 $(obj)/vmlinux.bin.bz2: $(vmlinux.bin.all-y) FORCE 48 $(call if_changed,bzip2) 49 $(obj)/vmlinux.bin.lzma: $(vmlinux.bin.all-y) FORCE 50 $(call if_changed,lzma) 51 52 suffix-$(CONFIG_KERNEL_GZIP) := gz 53 suffix-$(CONFIG_KERNEL_BZIP2) := bz2 54 suffix-$(CONFIG_KERNEL_LZMA) := lzma 55 56 quiet_cmd_mkpiggy = MKPIGGY $@ 57 cmd_mkpiggy = $(obj)/mkpiggy $< > $@ || ( rm -f $@ ; false ) 58 59 targets += piggy.S 60 $(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE 61 $(call if_changed,mkpiggy)
arch/x86/boot/compressed/piggy.S:
1 .section ".rodata.compressed","a",@progbits 2 .globl z_input_len 3 z_input_len = 3495594 4 .globl z_output_len 5 z_output_len = 7618944 6 .globl z_extract_offset 7 z_extract_offset = 0x3f8000 8 .globl z_extract_offset_negative 9 z_extract_offset_negative = -0x3f8000 10 .globl input_data, input_data_end 11 input_data: 12 .incbin "arch/x86/boot/compressed/vmlinux.bin.gz" 13 input_data_end:
arch/x86/boot/compressed/Makefile:
25 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o $(obj)/piggy.o FORCE 26 $(call if_changed,ld) 27 @:
压缩的vmlinux.bin生成:
objcopy将压缩的elf格式vmlinux转成binary格式vmlinux.bin
arch/x86/boot/Makefile
85 OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S 86 $(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE 87 $(call if_changed,objcopy) 88
5、setup.elf与setup.bin生成
setup.elf的构建:
arch/x86/boot/Makefile
29 setup-y += a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o edd.o 30 setup-y += header.o main.o mca.o memory.o pm.o pmjump.o 31 setup-y += printf.o regs.o string.o tty.o video.o video-mode.o 32 setup-y += version.o 33 setup-$(CONFIG_X86_APM_BOOT) += apm.o 34 35 # The link order of the video-*.o modules can matter. In particular, 36 # video-vga.o *must* be listed first, followed by video-vesa.o. 37 # Hardware-specific drivers should follow in the order they should be 38 # probed, and video-bios.o should typically be last. 39 setup-y += video-vga.o 40 setup-y += video-vesa.o 41 setup-y += video-bios.o 89 SETUP_OBJS = $(addprefix $(obj)/,$(setup-y)) 113 LDFLAGS_setup.elf := -T 114 $(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE 115 $(call if_changed,ld) scripts/Makefile.lib quiet_cmd_ld = LD $@ cmd_ld = $(LD) $(LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F)) \ $(filter-out FORCE,$^) -o $@
根据setup.ld链接脚本将SETUP_OBJS链接成setup.elf;
arch/x86/boot/setup.ld
10 SECTIONS 11 { 12 . = 0; 13 .bstext : { *(.bstext) } 14 .bsdata : { *(.bsdata) } 15 16 . = 497; 17 .header : { *(.header) } 18 .entrytext : { *(.entrytext) } 19 .inittext : { *(.inittext) } 20 .initdata : { *(.initdata) } 21 __end_init = .;
可以得出实模式内核第一条执行指令位于.header块中
The kernel is started by jumping to the kernel entry point, which is
located at *segment* offset 0x20 from the start of the real mode
kernel. This means that if you loaded your real-mode kernel code at
0x90000, the kernel entry point is 9020:0000.
arch/x86/boot/header.S:
94 .section ".header", "a" 95 .globl hdr 96 hdr: 97 setup_sects: .byte 0 /* Filled in by build.c */ 98 root_flags: .word ROOT_RDONLY 99 syssize: .long 0 /* Filled in by build.c */ 100 ram_size: .word 0 /* Obsolete */ 101 vid_mode: .word SVGA_MODE 102 root_dev: .word 0 /* Filled in by build.c */ 103 boot_flag: .word 0xAA55 104 105 # offset 512, entry point 106 107 .globl _start 108 _start: 109 # Explicitly enter this as bytes, or the assembler 110 # tries to generate a 3-byte jump here, which causes 111 # everything else to push off to the wrong offset. 112 .byte 0xeb # short (2-byte) jump 113 .byte start_of_setup-1f
setup是16位实模式代码
arch/x86/boot/Makefile:
58 # How to compile the 16-bit code. Note we always compile for -march=i386, 59 # that way we can complain to the user if the CPU is insufficient. 60 KBUILD_CFLAGS := $(LINUXINCLUDE) -g -Os -D_SETUP -D__KERNEL__ \ 61 -DDISABLE_BRANCH_PROFILING \ 62 -Wall -Wstrict-prototypes \ 63 -march=i386 -mregparm=3 \ 64 -include $(srctree)/$(src)/code16gcc.h \ 65 -fno-strict-aliasing -fomit-frame-pointer \ 66 $(call cc-option, -ffreestanding) \ 67 $(call cc-option, -fno-toplevel-reorder,\ 68 $(call cc-option, -fno-unit-at-a-time)) \ 69 $(call cc-option, -fno-stack-protector) \ 70 $(call cc-option, -mpreferred-stack-boundary=2)
arch/x86/boot/code16gcc.h:
1 /* 2 * code16gcc.h 3 * 4 * This file is -include'd when compiling 16-bit C code. 5 * Note: this asm() needs to be emitted before gcc emits any code. 6 * Depending on gcc version, this requires -fno-unit-at-a-time or 7 * -fno-toplevel-reorder. 8 * 9 * Hopefully gcc will eventually have a real -m16 option so we can 10 * drop this hack long term. 11 */ 12 13 #ifndef __ASSEMBLY__ 14 asm(".code16gcc"); 15 #endif
setup.bin的构建:
arch/x86/boot/Makefile
117 OBJCOPYFLAGS_setup.bin := -O binary 118 $(obj)/setup.bin: $(obj)/setup.elf FORCE 119 $(call if_changed,objcopy) scripts/Makefile.lib 195 quiet_cmd_objcopy = OBJCOPY $@ 196 cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
将setup由elf文件格式转换成binary格式
setup.bin是实模式的16位代码,bootloader装入实模式和保护模式代码后,首先将控制权交给实模式:
Documentation/x86/boot.txt
143 The first step in loading a Linux kernel should be to load the 144 real-mode code (boot sector and setup code) and then examine the 145 following header at offset 0x01f1. The real-mode code can total up to 146 32K, although the boot loader may choose to load only the first two 147 sectors (1K) and then examine the bootup sector size. 910 **** RUNNING THE KERNEL 911 912 The kernel is started by jumping to the kernel entry point, which is 913 located at *segment* offset 0x20 from the start of the real mode 914 kernel. This means that if you loaded your real-mode kernel code at 915 0x90000, the kernel entry point is 9020:0000.
setup相应的处理完成后会切换成保护模式,并将跳转到startup_32/startup_64执行
start_of_setup->main->go_to_protected_mode->startup_BITS
其中startup_BITS位置存放在实模式内核头的code32_start字段里,bzImage的默认值是0x100000
6、setup.bin和vmlinux.bin生成最终的内核镜像bzImage
内核bzImage的构建:
bzImage是由实模式setup.bin和保护模式内核vmlinux.bin组成
arch/x86/boot/Makefile
77 quiet_cmd_image = BUILD $@ 78 cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \ 79 $(ROOT_DEV) > $@ 80 81 $(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE 82 $(call if_changed,image) 83 @echo 'Kernel: $@ is ready' ' (#'`cat .version`')'
bzImage是arch/x86/boot/tools/build.c将setup.bin和vmlinux.bin组装而成,按512对齐后的setup.bin(以0补齐)和vmlinuz.bin合并成新文件bzImage,并设置相应的头信息,如setup.bin的大小、vmlinuz.bin的大小、CRC等
7、最终vmlinuz的生成
arch/x86/boot/Makefile:install->arch/x86/boot/install.sh
arch/x86/boot/install.sh:
49 cat $2 > $4/vmlinuz 50 cp $3 $4/System.map
其中make install还可以用/sbin/installkernel生成initramfs、System.map、启动项等