linux 內核 編譯過程

要想分析內核的啓動過程,首先得知道內核從哪個函數入口的。那這個入口是誰呢?根據分析uboot啓動流程的經驗,你會想到lds文件。內核裏這麼多lds文件,要先分析哪個呢?
在linux下,uboot引導的是uImage,不妨從uImage入口。首先看頂層的Makefile。uImage是怎麼生成的:
我們查看頂層的Makefile。沒有找uImage。那就分析一下,這個uImage可能會在哪個Makefile。因爲我們編譯的是arm平臺,會不會在arch/arm下的Makefile呢?進去一看發現了uImage的藏身之處。那麼頂層的Mafefile會不會包含這個arch/arm/Makefile呢?在最頂層的Makefile中發現了
 484 include $(srctree)/arch/$(SRCARCH)/Makefile

這說明我們的分析是正確的,源碼根目錄下的Makefile確實是包含了arch/arm/Makefile($(SRCARCH)=arm)。接下來可以分析一下arch/arm/Makefile了。繼續跟蹤我們需要的uImage。
在arch/arm/Makefile中搜搜uImage

303 zImage Image xipImage bootpImage uImage: $(VMLINUX)
304     $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

可以看出uImage依賴(VMLINUX) (VMLINUX)又是誰呢?繼續分析

264 # Default target when executing plain make
265 ifeq ($(CONFIG_XIP_KERNEL),y)
266 KBUILD_IMAGE := xipImage
267 else ifeq ($(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE),y)
268 KBUILD_IMAGE := zImage-dtb.$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAME)
269 else
270 KBUILD_IMAGE := zImage
271 endif
272 
273 all:    $(KBUILD_IMAGE)
274 
275 boot := arch/arm/boot
276 comp := $(boot)/compressed
277 VMLINUX := vmlinux vmlinux.bin vmlinux.dump
278 CLEAN_FILES += vmlinux.srec vmlinux.bin vmlinux.dump
279 
280 vmlinux.bin: vmlinux
281     $(OBJCOPY) --remove-section=.reginfo -O binary $< $@
282 

這個VMLINUX:= vmlinux vmlinux.bin vmlinux.dump,結合zImage Image xipImage bootpImage uImage: $(VMLINUX)
那麼就是“”

zImage Image xipImage bootpImage uImage:vmlinux vmlinux.bin vmlinux.dump

還得一點一點的 看。uImage依賴了這麼多文件,一個一個看吧。接着你會發現vmlinux.bin: vmlinux 和,vmlinux.dump: vmlinux 即

 vmlinux.bin: vmlinux
281     $(OBJCOPY) --remove-section=.reginfo -O binary $< $@
282 
283 vmlinux.dump: vmlinux
284     @(\
285     tmp=$@.$$$$;\
286 rm -f $@.[0-9]*;\
287     trap '' SIGHUP SIGINT SIGTTIN SIGTTOU SIGWINCH;\
288     echo Dumping to $$tmp, will move to $@ when done | tee $@;\
289     exec <&- 2>&-;\
290     $(OBJDUMP) -dtr $(objdump-flags-y) $< > $$tmp \
291         && mv $$tmp $@\
292     )& \
293     [ -t 0 ] || wait ;: objdump async only when interactive
294     $(OBJDUMP) -f $<

現在vmlinux.bin vmlinux.dump都依賴於vmlinux,那麼我們就只看vmlinux是怎麼來的吧!在arch/arm/Makfile中沒有找到編譯vmlinux的過程。那麼看看最頂層的Makefile吧
在頂層的Makefile中

 509 init-y      := init/
 510 drivers-y   := drivers/ sound/ firmware/
 511 net-y       := net/
 512 libs-y      := lib/
 513 core-y      := usr/
  722 init-y      := $(patsubst %/, %/built-in.o, $(init-y))
 723 core-y      := $(patsubst %/, %/built-in.o, $(core-y))
 724 drivers-y   := $(patsubst %/, %/built-in.o, $(drivers-y))
 725 net-y       := $(patsubst %/, %/built-in.o, $(net-y))
 726 libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))
 727 libs-y2     := $(patsubst %/, %/built-in.o, $(libs-y))
 728 libs-y      := $(libs-y1) $(libs-y2)

 757 vmlinux-init := $(head-y) $(init-y)
 758 vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
 759 vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
 760 vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds

 #909 vmlinux image - including updated kernel symbols
 910 vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsym     s.o) FORCE
 911 ifdef CONFIG_HEADERS_CHECK
 912     $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
 913 endif
 914 ifdef CONFIG_SAMPLES
 915     $(Q)$(MAKE) $(build)=samples
 916 endif
 917 ifdef CONFIG_BUILD_DOCSRC
 918     $(Q)$(MAKE) $(build)=Documentation
 919 endif
 920     $(call vmlinux-modpost)
 921     $(call if_changed_rule,vmlinux__)
 922     $(Q)rm -f .old_version
 923 
 924 # build vmlinux.o first to catch section mismatch errors early
 925 ifdef CONFIG_KALLSYMS
 926 .tmp_vmlinux1: vmlinux.o
 927 endif

可以看出vmlinux依賴了很多文件,這些文件大多都是dir/built-in.o,如何生成一個built-in.o呢?
不妨隨便找一個目錄看一下,看看init/built-in.o是怎麼生成的吧。如果生成init/built-in.o,肯定要執行init下的Makeifile,在頂層的Makefile中應該有個make -f init/Makefile的過程。
找了一下沒找到。卻發生有個(MAKE) (build)=init
那麼build的內容是什麼呢?

頂層Makeifle include $(srctree)/scripts/Kbuild.includeKbuild.include中定義了
build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
在頂層的MakefileKBUILD_SRC已經被定義爲$(CURDIR)即linux源碼的根目錄。
 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@其實就是
  $(Q)$(MAKE)  -f $(srctree)/)scripts/Makefile.build  obj=$(boot) MACHINE=$(MACHINE) $(boot)/$@ 
  其中 $(boot)就是arch/arm/boot
  ```
繼續看看scripts/Makefile.build,在它裏搜關鍵字"built-in"
發現了`133 ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),)
134 lib-target := $(obj)/lib.a
135 endif
136 
137 ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target))    ,)
138 builtin-target := $(obj)/built-in.o
139 endif
`

意思很明確,除了lib下的編譯成了lib.a外,其他的在obj目錄下的*.o都被編進了built-in.o
那麼obj是什麼呢?由上面的分析可以知道obj就是我們要編譯的目錄。
原來頂層的Makeifle是這麼編譯build-in.o的。
(Q) (MAKE) (build)= (MYDIR) 即可。
這個命令展開以後就是

 $(Q)$(MAKE) $(build)=(MYDIR)
 等價於:
 $(Q)$(MAKE)  -f $(srctree)/)scripts/Makefile.build  obj=(MYDIR) 
 //根據 scripts下的Makefile.build,可以再次展開爲
 builtin-target:=(MYDIR/built-in.o)

在頂層的Makefile中執行了多少個 (Q) (MAKE) $(build)=就有多少個built-in.o(除lib外)
搜索一下有多少個這樣的語句

4.y# cat Makefile  | grep '$(build)='
    $(Q)$(MAKE) $(build)=scripts/basic
    $(Q)$(MAKE) $(build)=scripts/kconfig $@
    $(Q)$(MAKE) $(build)=scripts/kconfig $@
    $(Q)$(MAKE) $(build)=$(@)
    $(MAKE) $(build)=init
    $(Q)$(MAKE) $(build)=samples
    $(Q)$(MAKE) $(build)=Documentation
    $(Q)$(MAKE) $(build)=$@
    $(Q)$(MAKE) $(build)=.
    $(Q)$(MAKE) $(build)=scripts build_unifdef
    $(Q)$(MAKE) $(build)=$(package-dir) $@
    $(Q)$(MAKE) $(build)=$(package-dir) $@
    $(Q)$(MAKE) $(build)=$(package-dir) $@
    @$(MAKE) $(build)=$(package-dir) help
    $(Q)$(MAKE) $(build)=scripts build_docproc
    $(Q)$(MAKE) $(build)=Documentation/DocBook $@
    $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@)
    $(build)=$(build-dir)
    $(build)=$(build-dir)
    $(build)=$(build-dir) $(@:.ko=.o)

上面分析的是vmlinux的生成過程,有了vmlinux後,怎麼得到uImage呢?

在arch/arm下的Makefile中看一下.

300 bzImage: zImage
301 
302 #zImage Image xipImage bootpImage uImage: vmlinux
303 zImage Image xipImage bootpImage uImage: $(VMLINUX)
304     $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
305 
306 #vmlinux
307 zinstall uinstall install: $(VMLINUX)
308 #zinstall uinstall install: vmlinux
309     $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $@

所有$VMLINUX都已經準備好了,繼續看arm/arm/Makefile

47 $(obj)/Image: vmlinux FORCE
 48     $(call if_changed,objcopy)
 49     @echo '  Kernel: $@ is ready'
 50 
 51 $(obj)/compressed/vmlinux: $(obj)/Image FORCE
 52     $(Q)$(MAKE) $(build)=$(obj)/compressed $@
 53 
 54 $(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
 55     $(call if_changed,objcopy)
 56     @echo '  Kernel: $@ is ready'
 57 
...............
88 $(obj)/uImage:  $(obj)/zImage FORCE
 89     @$(check_for_multiple_loadaddr)
 90     $(call if_changed,uimage)
 91     @echo '  Image $@ is ready'
 92 
 93 $(obj)/bootp/bootp: $(obj)/zImage initrd FORCE
 94     $(Q)$(MAKE) $(build)=$(obj)/bootp $@
 95     @:
 96 
 97 $(obj)/bootpImage: $(obj)/bootp/bootp FORCE
 98     $(call if_changed,objcopy)
 99     @echo '  Kernel: $@ is ready'

可以看出文件的依賴關係
uImage依賴zImage
zImage 依賴compressed/vmlinux
compressed/vmlinux 依賴Image
Image又依賴於 vmlinux(這個vmlinux就是前面生成的,內核鏡像elf文件vmlinux)
顯然,我們首先要得到Image。
那麼就分析一下怎麼得到Image的吧

 47 $(obj)/Image: vmlinux FORCE
 48     $(call if_changed,objcopy)
 49     @echo '  Kernel: $@ is ready'

這裏可以猜出,大概是vmlinux是經過objcopy後纔得到了Image,是不是這樣的呢?
我們跟蹤一下,if_changed是個函數,這個函數在哪定義呢?
那要看看這個arch/arm/boot下的Makefile本身或者其include的文件中的內容了。
跟蹤後發現在arch/arm/boot下的Makeifle中沒有定義if_changed這個函數,且其include的文件(只有Makeifle.boot)也沒有這個函數。
遇到這種情況,不如直接去分析頂層的Makefile,很可能是頂層的Makefile 的函數,大家都可以使用。
在頂層的Makefile中有一句“”

 327 include $(srctree)/scripts/Kbuild.include
//Kbuild.include的內容是什麼呢?
199 ifneq ($(KBUILD_NOCMDDEP),1)
200 # Check if both arguments has same arguments. Result is empty string if equal.
201 # User may override this check using make KBUILD_NOCMDDEP=1
202 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
203                     $(filter-out $(cmd_$@),   $(cmd_$(1))) )
204 else
205 arg-check = $(if $(strip $(cmd_$@)),,1)
206 endif
207 
208 # >'< substitution is for echo to work,
209 # >$< substitution to preserve $ when reloading .cmd file
210 # note: when using inline perl scripts [perl -e '...$$t=1;...']
211 # in $(cmd_xxx) double $$ your perl vars
212 make-cmd = $(subst \\,\\\\,$(subst \#,\\\#,$(subst $$,$$$$,$(call escsq,$(cmd_$(1))))))
213 
214 # Find any prerequisites that is newer than target or that does not exist.
215 # PHONY targets skipped in both cases.
216 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
217 
218 # Execute command if command has changed or prerequisite(s) are updated.
219 #
220 if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
221     @set -e;                                                             \
222     $(echo-cmd) $(cmd_$(1));                                             \
223     echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)

終於找到if_changed的原形了
這裏的any-prereq作用是找到比較新的依賴文件,且過濾僞目標。之後執行了cmd_1 (call if_changed,objcopy) 就應該是cmd_objcopy
我們搜一下cmd_objcopy

root@ubuntu:/home/work2/pdk/Hi3535_SDK/Hi3535_SDK_V2.0.4.0/source/arm11/linux-3.4.y/scripts# grep -nr 'cmd_objcopy'
Makefile.lib:235:quiet_cmd_objcopy = OBJCOPY $@
Makefile.lib:236:cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
//原來在Makefile.lib中定義了

其實就是執行了objcopy的命令。其中傳遞的objcopyflags先不分析,肯定是去除符號信息的。

1. 由內核源碼根目錄下的elf 文件 vmlinux 經過objcopy 得到了Image.

我們之前分析過“uImage依賴zImage
zImage 依賴compressed/vmlinux
compressed/vmlinux 依賴Image
Image又依賴於 vmlinux(這個vmlinux就是前面生成的,內核鏡像elf文件vmlinux)”

得到了Image如何得到compressed/vmlinux 呢?

 51 $(obj)/compressed/vmlinux: $(obj)/Image FORCE
 52     $(Q)$(MAKE) $(build)=$(obj)/compressed $@

根據這句話,就知道應該分析一下$(obj)/compressed

 24 HEAD    = head.o
 91 suffix_$(CONFIG_KERNEL_GZIP) = gzip
 92 suffix_$(CONFIG_KERNEL_LZO)  = lzo
 93 suffix_$(CONFIG_KERNEL_LZMA) = lzma
 94 suffix_$(CONFIG_KERNEL_XZ)   = xzkern
 95 

182 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o     \
183         $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) FORCE
184     @$(check_for_multiple_zreladdr)
185     $(call if_changed,ld)
186     @$(check_for_bad_syms)
187 
188 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
189     $(call if_changed,$(suffix_y))
190 
191 $(obj)/piggy.$(suffix_y).o:  $(obj)/piggy.$(suffix_y) FORCE

這裏面的內容告訴我們
要想生成vmlinux,需要準備vmlinux.lds,這裏的 (obj)compressedhead.opiggy. (suffix_y).o 。head.o對應compressed下的head.S
這裏我們選擇的gzip壓縮方式,piggy.$(suffix_y).o 對應的就是piggy.gzip.S
看看這個 piggy.gzip.S

  1     .section .piggydata,#alloc
  2     .globl  input_data
  3 input_data:
  4     .incbin "arch/arm/boot/compressed/piggy.gzip"
  5     .globl  input_data_end
  6 input_data_end:
~                                                                               
~                                

incbin了一個二進制文件arch/arm/boot/compressed/piggy.gzip”,它又是怎麼來的呢?

arch/arm/boot/compressed下的Makefile寫道:
188 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
189     $(call if_changed,$(suffix_y))
190 
//piggy.$(suffix_y)就是piggy.gzip

這樣的話就明白了。需要找到cmd_gzip這個函數。
還在scripts 目錄下搜索

4.y/scripts# grep -nr 'cmd_gzip'
Makefile.lib:241:quiet_cmd_gzip = GZIP    $@
Makefile.lib:242:cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@) || \

md_gzip = (cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@) || \
243     (rm -f $@ ; false)
244 

原來是執行了一條gzip -n -f -9的命令生成了piggy.gzip。
回過頭來看一下:
1.源碼下的vmlinux經過objcopy得到了Image
2.Image經過了gzip -n -f -9得到了piggy.gzip
3.piggy.gzip.S head.o 和vmlinux.lds共同生成了compressed下的vmlinux。

1. 壓縮效率:LZO <GZIP < BZIP2 < LZMA

2. 壓縮時間:GZIP <LZO ~= BZIP2 << LZMA

3. 解壓縮時間:LZO <GZIP < LZMA<< BZIP2

4.壓縮需要的內存:GZIP < LZO < BZIP2 << LZMA

5.解壓縮需要的內存:GZIP < LZO < BZIP2 << LZMA

如果FLASH不是問題,啓動速度是關鍵,選LZO。如果是在低成本嵌入式設備上,FLASH和RAM很小,GZIP是不錯的選擇;如果RAM有限,選BZIP2;如果RAM還夠,可以考慮LZMA。

[END]

根據上面分析的。
“uImage依賴zImage
zImage 依賴compressed/vmlinux
compressed/vmlinux 依賴Image
Image又依賴於 vmlinux(這個vmlinux就是前面生成的,內核鏡像elf文件vmlinux)”
離目標越來越近了。
看看zImage是怎麼生成的吧

在arch/arm/boot下的Makefile中。
 54 $(obj)/zImage:  $(obj)/compressed/vmlinux FORCE
 55     $(call if_changed,objcopy)
 56     @echo '  Kernel: $@ is ready'

原來compressed/vmlinux 經過objcopy後得到了zImage。
這個zImage就是經過壓縮後的內核鏡像。
但是它仍然不能被Uboot引導。因爲Uboot引導過程中會解析內核鏡像的前64字節。
繼續看看zImage怎麼得到了uImage。

在arch/arm/boot下的Makefile中。
 88 $(obj)/uImage:  $(obj)/zImage FORCE
 89     @$(check_for_multiple_loadaddr)
 90     $(call if_changed,uimage)
 91     @echo '  Image $@ is ready'

原來有個命令是cmd_uimage。它是原形是什麼呢?
老辦法,搜索

 grep -nr 'cmd_uimage'
Makefile.lib:324:quiet_cmd_uimage = UIMAGE  $(UIMAGE_OUT)
Makefile.lib:325:      cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
\\Makeifle.lib:
323 
324 quiet_cmd_uimage = UIMAGE  $(UIMAGE_OUT)
325       cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
326             -C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
327             -T $(UIMAGE_TYPE) \
328             -a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
329             -n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)
330 
331 # XZ

原來是用mkimage工具生成的。
-A 指定了cpu架構是arm
-O 指定了os是linux
-T指定了uImage類型是kernel
-a指定了加載地址
-e指定了入口地址,一般是加載地址減去64字節。
-n指定了uImage頭部結構中填充的name
這些參數是uboot能夠解析,如果uboot支持這種類型的uImage,就會引導它啓動。

到這裏,我們可以知道,要分析入口函數,應該先看arch/arm/boot/compressed下的vmlinux.lds文件

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
    /*
     * Discard any r/w data - this produces a link error if we have any,
     * which is required for PIC decompression.  Local data generates
     * GOTOFF relocations, which prevents it being relocated independently
     * of the text/got segments.
     */
    *(.data)
  }

  . = 0;
  _text = .;

  .text : {
    _start = .;
    *(.start)
    *(.text)
    *(.text.*)
    *(.fixup)
    *(.gnu.warning)
    *(.glue_7t)
    *(.glue_7)
  }
  .rodata : {
    *(.rodata)
    *(.rodata.*)
  }
  .piggydata : {
    *(.piggydata)
  }

  . = ALIGN(4);
  _etext = .;

  .got.plt      : { *(.got.plt) }
  _got_start = .;
  .got          : { *(.got) }
  _got_end = .;

  /* ensure the zImage file size is always a multiple of 64 bits */
  /* (without a dummy byte, ld just ignores the empty section) */
  .pad          : { BYTE(0); . = ALIGN(8); }
  _edata = .;

  . = ALIGN(8);
  __bss_start = .;
  .bss          : { *(.bss) }
  _end = .;

  . = ALIGN(8);     /* the stack must be 64-bit aligned */
  .stack        : { *(.stack) }

  .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) }
}

定義了入口函數_start。它定義在arch/arm/boot/compressed下的head.S
如果要分析內核啓動過程的話,當然是先從這個文件入手。
啓動過程將在下篇進行分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章