openwrt: makefile 框架分析-凯发app官方网站

凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1900587
  • 博文数量: 376
  • 博客积分: 2147
  • 博客等级: 大尉
  • 技术积分: 3642
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-06 10:47
文章分类

(376)

  • (3)
  • (30)
  • (2)
  • (0)
  • (26)
  • (6)
  • (5)
  • (18)
  • (2)
  • (3)
  • (12)
  • (2)
  • (2)
  • (46)
  • (14)
  • (10)
  • (2)
  • (2)
  • (5)
  • (24)
  • (1)
  • (16)
  • (1)
  • (71)
  • (16)
  • (10)
  • (47)
文章存档

(3)

(28)

(15)

(17)

(182)

(16)

(115)

我的朋友
相关博文
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·
  • ·

分类: 嵌入式

2014-12-19 10:04:34

本篇的主要目的是想通过分析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会放到此目录下。

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)

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合并。

阅读(32131) | 评论(0) | 转发(28) |
4

上一篇:

下一篇:

给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图