天天看点

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升级使用

继续阅读