天天看點

openwrt固件編譯過程

注:1)make -n可列印makefile執行的指令,而不執行。

2)可以在規則的指令中增加echo跟蹤執行進度。

頂層目錄的makefile是openert的總makefile,第一個編譯目标world是make的預設編譯目标。

編譯邏輯可簡化為:

make v=s時,$openwrt_build沒有定義指派,是以總是執行“第一邏輯”,“第一邏輯”結束時再次執行make world,此時$openwrt_build=1,是以執行“第二邏輯”。

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

prereq:: prepare-tmpinfo .config

@+$(no_trace_make) -r -s $@

warn_parallel_error = $(if $(build_log),,$(and $(filter

-j,$(makeflags)),$(findstring s,$(openwrt_verbose))))

ifeq ($(sdk),1)

%::

@+$(prep_mk) $(no_trace_make) -r -s prereq

@./scripts/config/conf --defconfig=.config config.in

@+$(ulimit_fix) $(submake) -r $@

else

@( \

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 $@ $(if $(warn_parallel_error), ||

{ \

printf "$(_r)build failed - please re-run with -j1 to

see the real error message$(_n)\n" >&2; \

false; \

} )

endif

執行makev=s時,上面規則簡化為:

@make v=ss -r -s prereq

@make v=s -r -s prereq

@make -w -r world

首先就引入了target,

package, tools, toolchain這四個關鍵目錄裡的makefile檔案。

include target/makefile

include package/makefile

include tools/makefile

include toolchain/makefile

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

subdir指令包

# parameters: <subdir>

define subdir

$(call warn,$(1),d,d $(1))

$(foreach bd,$($(1)/builddirs),

$(call warn,$(1),d,bd $(1)/$(bd))

$(foreach target,$(subtargets),

$(foreach btype,$(buildtypes-$(bd)),

$(call

warn_eval,$(1)/$(bd),t,t,$(1)/$(bd)/$(btype)/$(target): $(if

$(quilt),,$($(1)/$(bd)/$(btype)/$(target)) $(call

$(1)//$(btype)/$(target),$(1)/$(bd)/$(btype))))

$(if $(call debug,$(1)/$(bd),v),,@)+$$(submake) -r -c

$(1)/$(bd) $(btype)-$(target) $(if $(findstring

$(bd),$($(1)/builddirs-ignore-$(btype)-$(target))), || $(call

error,$(1), error: $(1)/$(bd) [$(btype)] failed to build.))

$(if $(call diralias,$(bd)),$(call

warn_eval,$(1)/$(bd),l,t,$(1)/$(call

diralias,$(bd))/$(btype)/$(target): $(1)/$(bd)/$(btype)/$(target)))

$(call warn_eval,$(1)/$(bd),t,t,$(1)/$(bd)/$(target): $(if

$(quilt),,$($(1)/$(bd)/$(target)) $(call

$(1)//$(target),$(1)/$(bd))))

$(if $(build_log),@mkdir -p $(build_log_dir)/$(1)/$(bd))

$(foreach variant,$(if $(build_variant),$(build_variant),$(if

$(strip $($(1)/$(bd)/variants)),$($(1)/$(bd)/variants),$(if

$($(1)/$(bd)/default-variant),$($(1)/$(bd)/default-variant),__default))),

$(if $(call debug,$(1)/$(bd),v),,@)+$(if $(build_log),set

-o pipefail;) $$(submake) -r -c $(1)/$(bd) $(target)

build_variant="$(filter-out __default,$(variant))" $(if

$(build_log),silent= 2>&1 | tee

$(build_log_dir)/$(1)/$(bd)/$(target).txt) $(if $(findstring

$(bd),$($(1)/builddirs-ignore-$(target))), || $(call error,$(1),

error: $(1)/$(bd) failed to build$(if $(filter-out

__default,$(variant)), (build variant: $(variant))).))

$(if $(prereq_only)$(dump_target_db),,

# aliases

warn_eval,$(1)/$(bd),l,t,$(1)/$(call diralias,$(bd))/$(target):

$(1)/$(bd)/$(target)))

$(foreach target,$(subtargets),$(call subtarget,$(1),$(target)))

endef

subdir會周遊參數子目錄,執行make

-c操作。

stampfile指令包

# parameters: <subdir> <name>

<target> <depends> <config options> <stampfile

location>

define stampfile

$(1)/stamp-$(3):=$(if

$(6),$(6),$(staging_dir))/stamp/.$(2)_$(3)$(5)

$$($(1)/stamp-$(3)): $(tmp_dir)/.build $(4)

@+$(script_dir)/timestamp.pl -n $$($(1)/stamp-$(3)) $(1) $(4) ||

\

$(make) $(if $(quiet),--no-print-directory)

$$($(1)/flags-$(3)) $(1)/$(3)

@mkdir -p $$$$(dirname $$($(1)/stamp-$(3)))

@touch $$($(1)/stamp-$(3))

$$(if $(call debug,$(1),v),,.silent: $$($(1)/stamp-$(3)))

.precious: $$($(1)/stamp-$(3)) # work around a make bug

$(1)//clean:=$(1)/stamp-$(3)/clean

$(1)/stamp-$(3)/clean: force

@rm -f $$($(1)/stamp-$(3))

target/makefile中調用:

curdir:=target

$(curdir)/builddirs:=linux sdk imagebuilder toolchain

$(curdir)/builddirs-default:=linux

$(curdir)/builddirs-install:=linux $(if $(config_sdk),sdk) $(if

$(config_ib),imagebuilder) $(if $(config_make_toolchain),toolchain)

$(curdir)/imagebuilder/install:=$(curdir)/linux/install

$(eval $(call

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

stampfile,$(curdir),target,compile,$(tmp_dir)/.build))

stampfile,$(curdir),target,install,$(tmp_dir)/.build))

$($(curdir)/stamp-install): $($(curdir)/stamp-compile)

$(eval $(call subdir,$(curdir)))

$(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)

是以可以簡單的看作:

$(call stampfile,$(curdir),target,prereq,.config)) 生成了目标

$(target/stamp-prereq)

對于target分别生成了:$(target/stamp-prereq),

$(target/stamp-compile),

$(target/stamp-install)

toolchain

: $(toolchain/stamp-install)

package

: $(package/stamp-prereq),$(package/stamp-cleanup), $(package/stamp-compile),$(package/stamp-install)

tools

: $(tools/stamp-install)

倚賴關系如下:

$(toolchain/stamp-install):

$(tools/stamp-install)

$(target/stamp-compile):

$(toolchain/stamp-install) $(tools/stamp-install)

$(build_dir)/.prepared

$(package/stamp-compile):

$(target/stamp-compile) $(package/stamp-cleanup)

$(package/stamp-install):

$(package/stamp-compile)

$(target/stamp-install):

$(package/stamp-compile) $(package/stamp-install)

基本上就是toolchain依賴tools,target依賴toolchain,package依賴target,最後target/stamp-install倚賴于package。

kernel編譯可運作指令:maketarget/linux/{prepare,compile,install} v=s

target/linux/makefile

include $(topdir)/rules.mk

include $(include_dir)/target.mk

export target_build=1

prereq clean download prepare compile install menuconfig nconfig

oldconfig update refresh: force

@+$(no_trace_make) -c $(board) $@

target/linux/ipq806x/makefile

arch:=arm

board:=ipq806x

boardname:=qualcomm atheros ipq806x

features:=ubifs squashfs

cpu_type:=cortex-a7

maintainer:=john crispin <[email protected]>

kernelname:=zimage image dtbs

kernel_patchver:=3.14

$(eval $(call buildtarget))

buildtarget在include/target.mk中:

include $(include_dir)/kernel.mk

ifeq ($(target_build),1)

include $(include_dir)/kernel-build.mk

buildtarget?=$(buildkernel)

buildkernel在include/kernel-build.mk中:

define buildkernel

$(if $(quilt),$(build/quilt))

$(if $(linux_site),$(call download,kernel))

download: $(if $(linux_site),$(dl_dir)/$(linux_source))

prepare: $(stamp_configured)

compile: $(linux_dir)/.modules

$(make) -c image compile target_build=

oldconfig menuconfig nconfig: $(stamp_prepared) $(stamp_checked)

force

rm -f $(stamp_configured)

$(linux_reconf_cmd) > $(linux_dir)/.config

$(_single)$(make) -c $(linux_dir) $(kernel_makeopts) $$@

$(linux_reconf_diff) $(linux_dir)/.config >

$(linux_reconfig_target)

install: $(linux_dir)/.image

+$(make) -c image compile install target_build=

clean: force

rm -rf $(kernel_build_dir)

image-prereq:

@+$(no_trace_make) -s -c image prereq target_build=

prereq: image-prereq

至此編譯kernel時clean/prepare/compile/install目标規則出現,涉及的makefile包括:include/kernel-build.mk,include/kernel-defaults.mk.

1)觸發makevmlinux指令生成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/copyimage)

define kernel/copyimage

$(kernel_cross)objcopy -o binary $(objcopy_strip) -s

$(linux_dir)/vmlinux $(linux_kernel)$(1)

$(kernel_cross)objcopy $(objcopy_strip) -s $(linux_dir)/vmlinux

$(kernel_build_dir)/vmlinux$(1).elf

$(cp) $(linux_dir)/vmlinux $(kernel_build_dir)/vmlinux$(1).debug

$(foreach k, \

$(if $(kernel_images),$(kernel_images),$(filter-out

dtbs,$(kernelname))), \

$(cp)

$(linux_dir)/arch/$(linux_karch)/boot/$(images_dir)/$(k)

$(kernel_build_dir)/$(k)$(1); \

3)核心config處理

prepare -->$(stamp_configured) --> $(kernel/configure) --> $(call

kernel/configure/default)

define kernel/configure

$(call kernel/configure/default)

define kernel/configure/default

$(linux_conf_cmd) > $(linux_dir)/.config.target

# copy config_kernel_* settings over to .config.target

awk

'/^(#[[:space:]]+)?config_kernel/{sub("config_kernel_","config_");print}'

$(topdir)/.config >> $(linux_dir)/.config.target

echo "# config_kallsyms_extra_pass is not set" >>

$(linux_dir)/.config.target

echo "# config_kallsyms_all is not set" >>

echo "# config_kallsyms_uncompressed is not set" >>

$(script_dir)/metadata.pl kconfig $(tmp_dir)/.packageinfo

$(topdir)/.config $(kernel_patchver) >

$(linux_dir)/.config.override

$(script_dir)/kconfig.pl 'm+' '+' $(linux_dir)/.config.target

/dev/null $(linux_dir)/.config.override > $(linux_dir)/.config

$(call kernel/setnoinitramfs)

rm -rf $(kernel_build_dir)/modules

$(_single) [ -d $(linux_dir)/user_headers ] || $(make)

$(kernel_makeopts) install_hdr_path=$(linux_dir)/user_headers

headers_install

$(sh_func) grep '=[ym]' $(linux_dir)/.config | lc_all=c sort |

md5s > $(linux_dir)/.vermagic

firmware由kernel和rootfs兩個部分組成,要對兩個部分先分别處理,然後再合并成一個.bin檔案。

target/linux/ipq806x/image/makefile中最後定義了生成image的規則:

$(eval $(call buildimage))

本檔案中也定義了生成image鏡像(包含ubifs鏡像)的規則。

同時注意:編譯kernel的compile或install時也會執行image下的compile和install。

include/image.mk定義buildimage指令包:

define buildimage

download:

prepare:

compile:

clean:

image_prepare:

ifeq ($(ib),)

.phony: download prepare compile clean image_prepare mkfs_prepare

kernel_prepare install

$(call build/compile)

$(call build/clean)

image_prepare: compile

mkdir -p $(kdir)/tmp

$(call image/prepare)

mkfs_prepare: image_prepare

$(call image/mkfs/prepare)

kernel_prepare: mkfs_prepare

$(call image/buildkernel)

$(if $(config_target_rootfs_initramfs),$(if $(ib),,$(call

image/buildkernel/initramfs)))

$(call image/installkernel)

$(foreach device,$(target_devices),$(call device,$(device)))

$(foreach fs,$(target_filesystems) $(fs-subtypes-y),$(call

buildimage/mkfs,$(fs)))

$$(sort $$(_kernel_images)):

@touch $$@

install: kernel_prepare

$(foreach fs,$(target_filesystems),

$(call image/build,$(fs))

$(call image/mkfs/ubifs)

$(call image/checksum,md5sum --binary,md5sums)

$(call image/checksum,openssl dgst -sha256,sha256sums)

至此編譯firmware時clean/prepare/compile/install目标規則出現,但隻有install起作用。測試中發現直接運作maketarget/linux/ipq806x/image/install v=s出錯,需要運作make

target/linux/install v=s。

install-->kernel_prepare-->mkfs_prepare -->image_prepare --> $(call image/prepare)

define image/prepare

$(cp) $(linux_dir)/vmlinux $(kdir)/$(img_prefix)-vmlinux.elf

主要處理指令包為:image/buildkernel,将裝置樹和鏡像打包成fit鏡像樹檔案:openwrt-ipq806x-fit-uimage.itb。

define image/buildkernel

$(call image/buildkernel/template,fit)

image/buildkernel/genericfit,qcom-ipq40xx,$(ipq40xx_kernel_loadaddr))

$(call image/buildkernel/multidtbfit,qcom-ipq40xx-ap.dkxx, \

$(call finddevicetrees, qcom-ipq40??-ap) $(call

finddevicetrees, qcom-ipq40??-db), \

$(ipq40xx_kernel_loadaddr))

對檔案系統處理(include/image.mk)

install-->kernel_prepare-->mkfs_prepare -->$(call image/mkfs/prepare)

define image/mkfs/prepare/default

# use symbolic permissions to avoid clobbering suid/sgid/sticky

bits

- $(find) $(target_dir) -type f -not -perm /0100 -not -name

'ssh_host*' -not -name 'shadow' -print0 | $(xargs) -0 chmod

u+rw,g+r,o+r

- $(find) $(target_dir) -type f -perm /0100 -print0 | $(xargs) -0

chmod u+rwx,g+rx,o+rx

- $(find) $(target_dir) -type d -print0 | $(xargs) -0 chmod

u+rwx,g+rx,o+rx

$(install_dir) $(target_dir)/tmp $(target_dir)/overlay

chmod 1777 $(target_dir)/tmp

chmod 700 $(target_dir)/usr/bin/scvt

test -e $(target_dir)/etc/cert && chmod 0700

$(target_dir)/etc/cert || echo ok

test -e $(target_dir)/etc/init.d/cert && chmod 0700

$(target_dir)/etc/init.d/cert || echo ok

test -e $(target_dir)/.ocloud/ && chmod 0700

$(target_dir)/.ocloud/ || echo ok

test -e $(target_dir)/.ocloud/ && chmod 0600

$(target_dir)/.ocloud/* || echo ok

test -e $(target_dir)/etc/root.secret && chmod 0600

$(target_dir)/etc/root.secret || echo ok

test -e $(target_dir)/etc/rsync.pass && chmod 0600

$(target_dir)/etc/rsync.pass || echo ok

define image/mkfs/prepare/pub_cfgs

[ -d $(target_dir)/etc/cfm/config/config-pub ] || mkdir -p

$(target_dir)/etc/cfm/config/config-pub

cp $(target_dir)/etc/config/*

- mv $(target_dir)/etc/cfm/config/config-pub/network

$(target_dir)/etc/cfm/config/config-priv/

(cd $(target_dir)/etc/cfm/config/config-pub; md5sum * >

pub-cfg-md5)

define image/mkfs/prepare

$(call image/mkfs/prepare/default)

$(call image/mkfs/prepare/pub_cfgs)

squashfs處理

install-->kernel_prepare-->$(call buildimage/mkfs,$(fs))

define buildimage/mkfs

install: mkfs-$(1)

.phony: mkfs-$(1)

mkfs-$(1): mkfs_prepare

$(image/mkfs/$(1))

$(call build/mkfs/default,$(1))

$(call build/mkfs/$(1),$(1))

$(kdir)/root.$(1): mkfs-$(1)

define image/mkfs/squashfs

$(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打更新檔

install--> $(callimage/build,$(fs))

define image/build/squashfs

$(call prepare_generic_squashfs,$(kdir)/root.squashfs)

define image/build

$(call image/build/$(1),$(1))

dd if=$(kdir)/root$(2).$(1)

of=$(bin_dir)/$(img_prefix)$(2)-$(1)-root$(3).img bs=2k conv=sync

# pad to 4k, 8k, 16k, 64k, 128k, 256k and add jffs2 end-of-filesystem

mark

define prepare_generic_squashfs

$(staging_dir_host)/bin/padjffs2 $(1) 4 8 16 64 128 256

至此生成squashfs檔案系統鏡像openwrt-ipq806x-squashfs-root.img(root.squashfs)。

4)ubifs處理

intall --> $(callimage/mkfs/ubifs)

将檔案系統格式化為ubifs格式:openwrt-ipq806x-ubifs-root.img(root.ubifs)。

将kernel處理後的fit鏡像openwrt-ipq806x-fit-uimage.itb和rootfs處理後的root.squashfs打包成ubi格式的ubi鏡像openwrt-ipq806x-ubi-root.img。此鏡像可直接在uboot下燒寫和運作,也就是常說的factory.bin。

build_img.sh通過工具mkimage将ubi鏡像增加firmware鏡像頭後生成sysupgrade.bin以支援sysupgrade更新使用

繼續閱讀