openwrt: Makefile 框架分析

本篇的主要目的是想通過分析Makefile,瞭解openwrt編譯過程。着重關注以下幾點:

  1. openwrt目錄結構
  2. 主Makefile的解析過程,各子目錄的目標生成。
  3. kernel編譯過程
  4. firmware的生成過程
  5. 軟件包的編譯過程

openwrt目錄結構

官方源下載速度太度,我從github上clone了openwrt的代碼倉庫。

git clone https://github.com/openwrt-mirror/openwrt.git

openwrt目錄結構

上圖是openwrt目錄結構,其中第一行是原始目錄,第二行是編譯過程中生成的目錄。各目錄的作用是:

  • tools - 編譯時需要一些工具, tools裏包含了獲取和編譯這些工具的命令。裏面是一些Makefile,有的可能還有patch。每個Makefile裏都有一句 $(eval $(call HostBuild)),表示編譯這個工具是爲了在主機上使用的。
  • toolchain - 包含一些命令去獲取kernel headers, C library, bin-utils, compiler, debugger
  • target - 各平臺在這個目錄裏定義了firmware和kernel的編譯過程。
  • package - 包含針對各個軟件包的Makefile。openwrt定義了一套Makefile模板,各軟件參照這個模板定義了自己的信息,如軟件包的版本、下載地址、編譯方式、安裝地址等。
  • include - openwrt的Makefile都存放在這裏。
  • scripts - 一些perl腳本,用於軟件包管理。

  • dl - 軟件包下載後都放到這個目錄裏
  • build_dir - 軟件包都解壓到build_dir/裏,然後在此編譯
  • staging_dir - 最終安裝目錄。tools, toolchain被安裝到這裏,rootfs也會放到這裏。
  • feeds -
  • bin - 編譯完成之後,firmware和各ipk會放到此目錄下。

OpenWrt Development Guide

main Makefile

openwrt根目錄下的Makefile是執行make命令時的入口。從這裏開始分析。

world:

ifndef ($(OPENWRT_BUILD),1) # 第一個邏輯 ... else # 第二個邏輯 ... endif

上面這段是主Makefile的結構,可以得知:

  1. 執行make時,若無任何目標指定,則默認目標是world
  2. 執行make時,無參數指定,則會進入第一個邏輯。如果執行命令make OPENWRT_BUILD=1,則直接進入第二個邏輯。

編譯時一般直接使用make V=s -j5這樣的命令,不會指定OPENWRT_BUILD變量

第一個邏輯

 override OPENWRT_BUILD=1 export OPENWRT_BUILD

更改了OPENWRT_BUILD變量的值。這裏起到的作用是下次執行make時,會進入到第二邏輯中。

toplevel.mk中的 %:: 解釋world目標的規則。

prereq:: prepare-tmpinfo .config @+$(MAKE) -r -s tmp/.prereq-build $(PREP_MK) @+$(NO_TRACE_MAKE) -r -s $@ %:: @+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq @( \
		cp .config tmp/.config; \
		./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \ if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then \
			printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n" >&2; \
		fi \
	) @+$(ULIMIT_FIX) $(SUBMAKE) -r $@

執行 make V=s 時,上面這段規則簡化爲:

prereq:: prepare-tmpinfo .config @make -r -s tmp/.prereq-build @make V=ss -r -s prereq %:: @make V=s -r -s prereq @make -w -r world

可見其中最終又執行了prereq和world目標,這兩個目標都會進入到第二邏輯中。

第二邏輯

首先就引入了target, package, tools, toolchain這四個關鍵目錄裏的Makefile文件

 include target/Makefile include package/Makefile include tools/Makefile include toolchain/Makefile

這些子目錄裏的Makefile使用include/subdir.mk裏定義的兩個函數來動態生成規則,這兩個函數是subdir和stampfile

stampfile

拿target/Makefile舉例:

(eval(call stampfile,$(curdir),target,prereq,.config))

會生成規則:

 target/stamp-prereq:=$(STAGING_DIR)/stamp/.target_prereq $$(target/stamp-prereq): $(TMP_DIR)/.build .config @+$(SCRIPT_DIR)/timestamp.pl -n $$(target/stamp-prereq) target .config || \
		make $$(target/flags-prereq) target/prereq @mkdir -p $$$$(dirname $$(target/stamp-prereq)) @touch $$(target/stamp-prereq) $$(if $(call debug,target,v),,.SILENT: $$(target/stamp-prereq))

  .PRECIOUS: $$(target/stamp-prereq) # work around a make bug

  target//clean:=target/stamp-prereq/clean target/stamp-prereq/clean: FORCE @rm -f $$(target/stamp-prereq) 

所以可以簡單的看作: (eval(call stampfile,(curdir),target,prereq,.config))生成了目標(target/stamp-prereq)

  • 對於target分別生成了:(target/stamp?preq),(target/stamp-copile), $(target/stamp-install)
  • toolchain : $(toolchain/stamp-install)
  • package : (package/stamp?preq),(package/stamp-cleanup), (package/stamp?compile),(package/stamp-install)
  • tools : $(tools/stamp-install)

OpenWrt的主Makefile工作過程

subdir

subdir這個函數寫了一大堆東西,看起來很複雜 。

$(call subdir, target) 會遍歷下的子目錄,執行 make -C 操作。這樣就切入子目錄中去了。

目錄變量

幾個重要的目錄路徑:

  • KERNEL_BUILD_DIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/linux-3.14.18

  • LINUX_DIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/linux-3.14.18

  • KDIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a

  • BIN_DIR

    bin/ramips
    Makefile中包含了rules.mk, target.mk等.mk文件,這些文件中定義了許多變量,有些是路徑相關的,有些是軟件相關的。這些變量在整個Makefile工程中經常被用到,

  • TARGET_ROOTFS_DIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2

  • BUILD_DIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2

  • STAGING_DIR_HOST

    staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2

  • TARGET_DIR

    build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/root-ramips

kernel 編譯:

target/linux/ramips/Makefile: $(eval $(call BuildTarget))
target/linux/Makefile : export TARGET_BUILD=1
include/target.mk:

ifeq ($(TARGET_BUILD),1)
  include $(INCLUDE_DIR)/kernel-build.mk
  BuildTarget?=$(BuildKernel)
endif

BuildKernel是include/kernel-build.mk定義的一個多行變量,其中描述瞭如何編譯內核, 主要關注其中install規則的依賴鏈:

 $(KERNEL_BUILD_DIR)/symtab.h: FORCE
	rm -f $(KERNEL_BUILD_DIR)/symtab.h
	touch $(KERNEL_BUILD_DIR)/symtab.h
	+$(MAKE) $(KERNEL_MAKEOPTS) vmlinux
	... $(LINUX_DIR)/.image: $(STAMP_CONFIGURED) $(if $(CONFIG_STRIP_KERNEL_EXPORTS),$(KERNEL_BUILD_DIR)/symtab.h) FORCE $(Kernel/CompileImage) $(Kernel/CollectDebug)
	touch $$@


  install: $(LINUX_DIR)/.image +$(MAKE) -C image compile install TARGET_BUILD= 
1. 觸發make vmlinux命令生成vmlinux: install --> $(LINUX_DIR)/.image --> $(KERNEL_BUILD_DIR)/symtab.h --> `$(MAKE) $(KERNEL_MAKEOPTS) vmlinux` 2. 對vmlinux做objcopy, strip操作: $(LINUX_DIR)/.image --> $(Kernel/CompileImage) --> $(call Kernel/CompileImage/Default) --> $(call Kernel/CompileImage/Default) $(KERNEL_CROSS)objcopy -O binary $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(LINUX_KERNEL)$(1)
        --> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux $(KERNEL_CROSS)objcopy $(OBJCOPY_STRIP) -S $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux$(1).elf
        --> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux.elf $(CP) $(LINUX_DIR)/vmlinux $(KERNEL_BUILD_DIR)/vmlinux.debug
        --> build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/vmlinux.debug

生成firmware

firmware由kernel和rootfs兩個部分組成,要對兩個部分先分別處理,然後再合併成一個.bin文件。先看一下這個流程。

"target/linux/ramips/image/Makefile" 文件中的最後一句:$(eval $(call BuildImage)),將BuildImage展開在這裏。BuildImage定義在 include/image.mk 文件中,其中定義了數個目標的規則。

define BuildImage

    compile: compile-targets FORCE
		**$(call Build/Compile)**

    install: compile install-targets FORCE ... $(call Image/BuildKernel) ## 處理vmlinux ... $(call Image/mkfs/squashfs) ## 生成squashfs,並與vmlinux合併成一個.bin文件 ... endef

處理vmlinux: Image/BuildKernel

target/linux/ramips/image/Makefile:

define Image/BuildKernel
	cp $(KDIR)/vmlinux.elf $(BIN_DIR)/$(VMLINUX).elf
	cp $(KDIR)/vmlinux $(BIN_DIR)/$(VMLINUX).bin $(call CompressLzma,$(KDIR)/vmlinux,$(KDIR)/vmlinux.bin.lzma) $(call MkImage,lzma,$(KDIR)/vmlinux.bin.lzma,$(KDIR)/uImage.lzma)
	cp $(KDIR)/uImage.lzma $(BIN_DIR)/$(UIMAGE).bin
ifneq ($(CONFIG_TARGET_ROOTFS_INITRAMFS),)
	cp $(KDIR)/vmlinux-initramfs.elf $(BIN_DIR)/$(VMLINUX)-initramfs.elf
	cp $(KDIR)/vmlinux-initramfs $(BIN_DIR)/$(VMLINUX)-initramfs.bin $(call CompressLzma,$(KDIR)/vmlinux-initramfs,$(KDIR)/vmlinux-initramfs.bin.lzma) $(call MkImage,lzma,$(KDIR)/vmlinux-initramfs.bin.lzma,$(KDIR)/uImage-initramfs.lzma)
	cp $(KDIR)/uImage-initramfs.lzma $(BIN_DIR)/$(UIMAGE)-initramfs.bin
endif $(call Image/Build/Initramfs) endef

lzma壓縮內核

build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/ 目錄中:

lzma e vmlinux -lc1 -lp2 -pb2 vmlinux.bin.lzma

MkImage

build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/ 目錄中:

mkimage -A mips -O linux -T  kernel -C lzma -a 0x80000000 -e 0x80000000 -n "MIPS OpenWrt Linux-3.14.18" -d vmlinux.bin.lzma uImage.lzma

copy

VMLINUX:=$(IMG_PREFIX)-vmlinux --> openwrt-ramips-mt7620a-vmlinux UIMAGE:=$(IMG_PREFIX)-uImage --> openwrt-ramips-mt7620a-uImage
cp $(KDIR)/uImage.lzma $(BIN_DIR)/$(UIMAGE).bin

把uImage.lzma複製到bin/ramips/目錄下:
cp $(KDIR)/uImage.lzma bin/ramips/openwrt-ramips-mt7620a-uImage

製作squashfs,生成.bin: $(call Image/mkfs/squashfs)

 define Image/mkfs/squashfs @mkdir -p $(TARGET_DIR)/overlay $(STAGING_DIR_HOST)/bin/mksquashfs4 $(TARGET_DIR) $(KDIR)/root.squashfs -nopad -noappend -root-owned -comp $(SQUASHFSCOMP) $(SQUASHFSOPT) -processors $(if $(CONFIG_PKG_BUILD_JOBS),$(CONFIG_PKG_BUILD_JOBS),1) $(call Image/Build,squashfs)
endif

mkdir -p $(TARGET_DIR)/overlay

mkdir -p build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/root-ramips/overlay

mksquashfs4

$(STAGING_DIR_HOST)/bin/mksquashfs4 $(TARGET_DIR) $(KDIR)/root.squashfs -nopad -noappend -root-owned -comp $(SQUASHFSCOMP) $(SQUASHFSOPT) -processors $(if $(CONFIG_PKG_BUILD_JOBS),$(CONFIG_PKG_BUILD_JOBS),1)

製作squashfs文件系統,生成root.squashfs:

mksquashfs4 root-ramips root.squashfs -nopad -noappend -root-owned -comp gzip -b 256k -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' -processors 1

$(call Image/Build,squashfs)

在 target/linux/ramips/image/Makefile 中:

define Image/Build $(call Image/Build/$(1))
	dd if=$(KDIR)/root.$(1) of=$(BIN_DIR)/$(IMG_PREFIX)-root.$(1) bs=128k conv=sync $(call Image/Build/Profile/$(PROFILE),$(1))
endef
  • dd if=(KDIR)/root.squashfsof=(BIN_DIR)/$(IMG_PREFIX)-root.squashfs bs=128k conv=sync

dd if=build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_mt7620a/root.squashfs of=bin/ramips/openwrt-ramips-mt7620-root.squashfs bs=128k conv=sync

  • (callImage/Build/Profile/(PROFILE),squashfs)

target/linux/ramips/mt7620a/profiles/00-default.mk, 中調用 Profile 函數:$(eval $(call Profile,Default))

include/target.mk 中定義了 Profile 函數, 其中令 PROFILE=Default

define Image/Build/Profile/Default
	$(call Image/Build/Profile/MT7620a,$(1)) ... endef

規則依賴序列如下:

$(call Image/Build/Profile/$(PROFILE),squashfs)
  --> $(call BuildFirmware/Default8M/squashfs,squashfs,mt7620a,MT7620a)  --> $(call BuildFirmware/OF,squashfs,mt7620a,MT7620a,8060928)  --> $(call MkImageLzmaDtb,mt7620a,MT7620a)  --> $(call PatchKernelLzmaDtb,mt7620a,MT7620a)  --> $(call MkImage,lzma,$(KDIR)/vmlinux-mt7620a.bin.lzma,$(KDIR)/vmlinux-mt7620a.uImage)  --> $(call MkImageSysupgrade/squashfs,squashfs,mt7620a,8060928)

其中的主要步驟:

  • 複製: cp (KDIR)/vmlinux(KDIR)/vmlinux-mt7620a
  • 生成dtb文件: (LINUXDIR)/scripts/dtc/dtc?Odtb?o(KDIR)/MT7620a.dtb ../dts/MT7620a.dts
  • 將內核與dtb文件合併:(STAGINGDIRHOST)/bin/patch?dtb(KDIR)/vmlinux-mt7620a $(KDIR)/MT7620a.dtb
  • 使用lzma壓縮:(callCompressLzma,(KDIR)/vmlinux-mt7620a,$(KDIR)/vmlinux-mt7620a.bin.lzma)
  • 將lzma壓縮後的文件經過mkimage工具處理,即在頭部添加uboot可識別的信息。

接下來就是合併生成firmware固件了:

MkImageSysupgrade/squashfs, squashfs, mt7620a,8060928

cat vmlinux-mt7620a.uImage root.squashfs > openwrt-ramips-mt7620-mt7620a-squashfs-sysupgrade.bin
--> 製作squashfs bin文檔, 並確認它的大小 < 8060928 纔是有效的,否則報錯。


總結: 整個流程下來,其實最煩索的還是對內核生成文件vmlinux的操作,經過了objcopy, patch-dtb, lzma, mkimage 等過程生成一個uImage,再與mksquashfs工具製作的文件系統rootfs.squashfs合併。

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