本篇的主要目的是想通过分析Makefile,了解openwrt编译过程。着重关注以下几点:
- openwrt目录结构
- 主Makefile的解析过程,各子目录的目标生成。
- kernel编译过程
- firmware的生成过程
- 软件包的编译过程
openwrt目录结构
官方源下载速度太度,我从github上clone了openwrt的代码仓库。
git clone https://github.com/openwrt-mirror/openwrt.git
上图是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的结构,可以得知:
- 执行make时,若无任何目标指定,则默认目标是world
- 执行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合并。
相关推荐
OpenWrt的编译框架基于Makefile系统,允许开发者通过修改配置文件来选择要编译的软件包,定制系统功能。`feeds.conf`文件用于定义外部源仓库,使得开发者可以从这些源获取并编译额外的软件包。此外,OpenWrt还提供了...
《OpenWRT系统的编译框架分析——以TP-LINK为例》 OpenWRT是一个开源的嵌入式操作系统,主要用于路由器设备。它基于Linux内核,提供了丰富的软件包管理和编译框架,使得开发者能够轻松定制和扩展路由器的功能。本文...
1. **Makefile框架分析** 在`openwrt Makefile 框架分析-lwchsz-ChinaUnix博客.htm`中,作者 lwchsz 分析了OpenWrt构建系统的核心——Makefile。Makefile是控制编译过程的关键文件,它定义了软件构建的规则和依赖...
通过对 OpenWrt 目录结构的深入分析,我们不仅能够更好地理解其构建流程,还能更加高效地进行定制化开发。了解每个目录的作用和功能是进行 OpenWrt 自定义开发的基础。希望本文能帮助读者更深入地掌握 OpenWrt 的...
标题 "Openwrt_QT5.12_porting.rar" 提示我们这是一份关于将Qt 5.12框架移植到OpenWrt操作系统的资源包。OpenWrt是一个轻量级、高度可定制的Linux发行版,常用于路由器和其他嵌入式设备。Qt则是一个跨平台的应用程序...
如果你的软件包依赖于其他OpenWrt包,需要在`Makefile`中声明这些依赖。这样,在安装你的包时,OpenWrt会自动处理这些依赖关系。 6. **编译与打包**: 在OpenWrt源码树根目录执行`make menuconfig`,启用你新添加...
它提供了一个高度可定制的软件框架,允许用户根据需要自由选择和安装软件包,极大地丰富了路由器的功能。 一、概述 OpenWRT的核心特性包括其功能模块化(IPK)和软件架构设计。IPK是OpenWRT软件包管理系统的一部分...
在OpenWRT框架下,开发者可以充分利用MT7628的硬件特性,为其定制功能丰富的固件。 在这个压缩包中,重点是Makefile文件。Makefile是构建软件项目的关键文件,它定义了编译和链接的目标、规则以及依赖关系。将这个...
2. **下载用于编程的OpenWRT源代码**:可以从官方网站获取最新的源代码仓库。 3. **烧写OpenWRT固件**:使用适当的工具将构建好的固件烧录到目标设备上。 4. **配置开发板的基本参数**:包括设置IP地址、网络接口等...
UCI(Unified Configuration Interface)是OpenWrt项目中的配置框架,它允许用户通过文本文件轻松地管理各种服务和设备的配置。对于无线网络的配置,UCI提供了一套简单易用的接口。用户可以通过编辑相应的UCI文件来...
QSDK通常指的是Qt Software Development Kit,是用于开发图形用户界面应用程序的工具集,尤其是针对Qt框架。 【描述】:“2017的qsdk.rar”的描述简洁,没有提供具体细节,但可以推测它可能包含了一系列用于2017年...
- `lib/`:包含 LuCI 的核心库和框架组件。 - `themes/`:各种用户界面主题。 - `luasrc/`:Lua 脚本源代码。 - `init.lua`:LuCI 的启动脚本。 - `config/`:可能包含默认配置文件。 - `Makefile`:构建系统...
2. **Raspberry Pi 3**: Raspberry Pi 3是一款流行的单板计算机,拥有四核处理器和无线网络功能,常用于教育、实验和原型开发。 3. **Arancino rpi3板**: 这是基于Raspberry Pi 3的开发板,可能包含额外的特性或...
这可能涉及到修改Makefile、Kconfig等配置文件,确保驱动在编译时被正确包含。 在移植过程中,详细的指导文档起着至关重要的作用。这些文档通常包括步骤指南、注意事项、可能遇到的问题及其解决方案。遵循文档,...
openwrt是一个开源的嵌入式Linux系统自动构建框架,是由Makefile脚本和Kconfig配置文件构成的。使得用户可以通过menuconfig配置,编译出一个完整的可以直接烧写到机器上运行的Linux系统软件。 第四章节:Tina开发...
10. **软件框架与库**:熟悉一些常用的嵌入式软件框架,如Qt、GTK+,以及各类库,如SQLite数据库、XML解析库等,这些可以帮助开发更复杂的用户界面和数据处理功能。 通过本教程的学习,开发者可以掌握从底层硬件...
libubox是OpenWrt项目中的一个库,提供了基本的框架和工具,便于在OpenWrt设备上开发守护进程和服务。OpenWrt是一个开源的嵌入式操作系统,常见于无线路由器。json-c则是一个C语言实现的JSON(JavaScript Object ...
2. **Makefile**:在标签中提到了“entware Makefile”,这通常是指用于自动化构建过程的Makefile文件。Makefile包含了编译、链接、安装等步骤的指令,使得开发者能方便地管理和构建项目。 3. **软件包管理**:...
源代码文件主要分为两部分:一部分是实现各种命令的C语言代码,另一部分是 BusyBox 的核心框架,负责命令的调度和管理。Makefile则是编译时的规则定义,包括编译选项、依赖关系等。配置脚本"config"允许用户通过交互...