Netgear

12 15

OpenWRT编译系统分析之package/compile

查找package/compile

package/compile目标没有明确的Makefile定义。所以我们考虑通过顶层Makefile中include进来的package/Makefile找到突破口。

该文件中直接定义的只有cleanup、rootfs-prepare、index三个目标。文件最后以package作为参数调用了subdir函数。根据之前文章中关于subdir函数作用的分析,我们可以得出结论:任何非直接定义的package/xxx目标,都依赖于其特定子目录的对应目标。特定子目录的读取规则参考前文,这里直接列出结论:

  • install目标依赖于配置了CONFIG_PACKAGE_xxx为y的包目录
  • prereq目标依赖于CONFIG_PACKAGE_xxx为y或者m的包目录
  • 其他目标依赖于CONFIG_PACKAGE_xxx为y或者m的包目录

深入package/包名/

进入到具体的包,每个包的Makefile内容都各不一样。但是大体上都包含以下几个部分:

  1. include顶层的rules.mk文件
  2. 定义名称、版本、下载地址、依赖等包信息
  3. include顶层include/package.mk文件
  4. 定义该软件包的一些非通用的函数(Package/包名/操作)。
  5. 以包名为参数调用BuildPackage函数。

前面几步都没有什么特别的,最后一步的BuildPackage函数调用非常关键。该函数定义在前面include进来的顶层include/pacakge.mk文件中。

深入BuildPackage函数

定义在顶层include/package.mk文件的该函数,内容不多。主要包含:

  1. 读取顶层overlay/*/包名.mk文件里定义的特殊规则
  2. 读取默认的Package/DefaultPackage/包名函数以导入软件包的相关变量。
  3. 检查必要的变量名是否正确定义。
  4. 重要通过遍历,构建目标规则。
  5. 以$1作为参数调用Build/DefaultTargets函数设置一些默认目标规则。该函数就在本文件中定义

遍历构造规则部分,主要是遍历列表内容作为target变量的值,然后调用BuildTarget/$(target)。遍历的列表分为集中情况:

  1. 当定义了Package/包名/targets时,将其作为遍历的内容。
  2. 否则检查是否存在PKG_TARGETS变量,如果有,则将其作为遍历内容。
  3. 否则将ipkg作为遍历的内容,如果*CONFIG_DEBUG_DIR*变量设置的话,将debug也添加为遍历的内容。

由此可见,默认情况下就是调用的BuildTarget/ipkg函数,该函数定义在package.mk前面include进来的include/package-ipkg.mk文件中。

Build/DefaultTargets函数主要定义了prepare、configure、dist、distclean这些准备性的目标,及其关联的目标stamp文件目标。

BuildTarget/ipkg函数定义了compile、install两个重要的目标,及其关联的依赖目标。需要注意的这两个目标定义,在Package/包名/install函数有定义的前提下才有效。而这个函数必须要自己定义。

所以,执行package/compile实质上执行的就是include/package-ipkg.mk中定义的compile目标。

package/包名/compile的依赖分析

每个软件包的compile目标,都依赖于IPKG_包名$(STAGING_DIR_ROOT)/stamp/.$(1)_installed两个目标。前者主要是编译打包成ipkg。后者主要调用Package/包名/installPackage/包名/install_lib两个方法,将软件包安装到STAGING_DIR_ROOTstaging_dir/target-mips_uClibc-0.9.30.1/root-wndr4500v3/)中。

IPKG_包名这个目标的执行体主要包括三个功能:

  • 生成ipkg所需要的软件包的元数据
  • 调用Package/包名/install安装软件包到临时目录build_dir/target-mips_uClibc-0.9.30.1/包名/ipkg-wndr4500v3/包名
  • 把临时目录、元数据打包成ipkg格式的软件包,放在bin/packages/wndr4500v3_uClibc-0.9.30.1/

IPKG_包名同时依赖于$(STAMP_BUILT)目标,该目标定义在include/package.mk中。

$(STAMP_BUILT)的执行体,主要调用Build/CompileBuild/Install两个函数,以及相关的Hooks。这两个函数分别是等价于在include/pacakge-defaults.mk中定义的Build/Compile/DefaultBuild/Install/Default

$(STAMP_BUILT)依赖于$(STAMP_CONFIGURED)。类似地,这个目标调用的是Build/Configure/Default。并依赖于$(STAMP_PREPARED)目标,这个目标同样类似的调用的是Build/Prepare/Default

上面被最终调用的,定义在include/package-defaults.mk中的默认目标分别是:

  • Build/Prepare/Default:调用PKG_UNPACK解压代码并调用Build/Patch打补丁
  • Build/Configure/Default:带参数执行默认的./configure
  • Build/Compile/Default:带参数执行默认的make
  • Build/Install/Default:带参数执行默认的make install

package/包名/install

该目标依赖于IPKG_包名目标,也就是说他也需要先将软件包打包成ipkg格式。该目标的执行体最简单,直接调用ipkg命令安装打包好的ipkg文件。需要注意的是ipkg命令本身设置了几个环境变量:

  • IPKG_TMP安装所需临时目录,值为tmp/ipkg
  • IPKG_INSTROOT安装目录,值为build_dir/target-mips_uClibc-0.9.30.1/root-wndr4500v3/
  • IPKG_CONF_DIR配置目录,值为staging_dir/target-mips_uClibc-0.9.30.1/etc
  • IPKG_OFFLINE_ROOT离线安装目录,值为build_dir/target-mips_uClibc-0.9.30.1/root-wndr4500v3
12 15

OpenWRT编译系统分析之target/install

查找target/install

该目标主要用于安装target系统。但是搜索代码可以发现,该目标没有任何Makefile直接、明确的定义过。那么该目标是怎么执行的呢?

从顶层Makefile我们可以看到有include target/Makefile指令。所以多半这个目标应该和target/Makefile有关系了。而打开target/Makefile你会发现,除了一堆变量定义,最后就是将target目录名作为$1参数调用了之前文章中提到的subdir函数。

按照之前文章中的解析,subdir函数肯定定义了$1/install也就是target/instal目标,该目标没有执行体,只有依赖目标。由于target/Makefile中存在target/builddirs-install变量,根据前面的解析,其变量值就是target/install的依赖目标。

target/builddirs-install的内容第一个就是写死的linux。其次,如果定义了CONFIG_SDK那么还包含sdk,如果定义了CONFIG_IB,还包含imagebuilder。

所以综合起来,执行target/install,实质就是执行其依赖target/linux/install。而根据前面的解析,target/linux/install的执行体,就是进入到target下的linux/子目录,执行install目标。

深入target/linux子目录

该目录下的Makefile非常简单,直接定义了install这个目标。依赖FORCE这个伪目标,表示每次都必须执行。执行体也只有一行,就是进入到BOARD这个变量定义的子目录下,执行install目标。

那么BOARD在哪儿定义的呢?跟踪代码容易发现,该变量定义在顶层目录的rules.mk文件,值大概就是CONFIG_TARGET_BOARD变量。这个变量就不用说了,定义在配置文件.config中。针对本文所用的WNDR4300v2而言,该变量的值就是wndr4500v3(擦,竟然不是wndr4300v2,Netgear也是够懒的)。

OK,那么接下来make就会进入到target/linux目录的wndr4500v3子目录了。

深入target/linux/wndr4500v3

和target/Makefile的尿性一样,wndr4500v3目录中的Makefile,特么也没有定义任何目标,只是在文件末尾调用了BuildGitTarget函数。该函数定义在哪儿呢?答案是顶层目录下include/target.mk文件。

这个文件更奇葩了。的确是有BuildGitTarget的定义。一个是当DUMP变量为1时,该函数等价于BuildTargets/DumpCurrent。一个是当TARGET_BUILD变量为1时,该函数就是直接等价于BuildGitKernel

由于调用我们的targe/linux/Makefile文件中,明确通过export定义了TARGET_BUILD为1。加上根据经验猜测,BuildTargets/DumpCurrent并不会真正Build,只是将当前已经Built的显示出来而已。所以我们可以直接认为BuildGitTarget就等价于BuildGitKernel

那么BuildGitKernel在哪儿定义的呢?答案就是顶层目录下的include/kernel-build.mk文件中。该文件就是在BuildGitKernel赋值给BuildGitTarget前,通过include指令明确引用的。

从include/kernel-build.mk文件中,很容易看到,BuildGitTarget函数中,定义了一个install目标,由于是target/linux/wndr4500v3/Makefile中调用的,所以target/linux/wndr4500v3/Makefile中install目标,实际上也就是该目标。那么该目标的具体内容是什么呢?

首先,该目标依赖于*$(LINUX_DIR)/.image*目标。其执行体也很简单,把TARGET_BUILD变量的值清空后,进入到image子目录,执行compile、install两个目标。

先说依赖,该依赖目标同样也是在BuildGitKernel函数中定义的,主要调用了Kernel/CompileImage函数。该函数也在该文件中定义,直接调用Kernel/CompileImage/Default。该函数定义在顶层目录的include/kernel-default.mk文件中。主要干了两件事,一是编译内核。一是将内核通过objcopy打包成vmlinux。

再说执行体,执行体就是进入image子目录,执行compile和install两个目标。

深入target/linux/wndr4500v3/image

在目录下执行compile和install两个目标,就要先看看该目录下的Makefile了。不错,又是一个更坑爹的货。照样没有直接定义目标,照样在最后调用了一个BuildImage的函数。是的,通过搜索代码很容易发现,这个函数定义在顶层目录下的include/image.mk中。

打开该文件发现,根据IB变量的不同,compileinstall目标的定义也不同。IB变量主要当使用ImageBuilder制作镜像时定义(别问我为什么知道,我真的是凭感觉猜的),其他情况未定义。所以:

  • 对于compile目标,其依赖是compile-targets,执行体调用Build/Compile函数。这两个函数实际上都没有定义。所以该目标什么都没做。(注意include/package.mk中定义了Build/Compile函数,但那是package的默认编译函数,和这里的无关)
  • 对于install目标,其以来是compile、install-targets(该目标依赖和执行体没有,可以忽略),执行体依次调用
    • Image/Prepare
    • Image/mkfs/prepare
    • Image/BuildKernel
    • Image/mkfs/jffs2
    • Image/mkfs/squashfs
    • Image/mkfs/tgz
    • Image/mkfs/cpiogz
    • Image/mkfs/ext2
    • Image/mkfs/iso
    • Image/Checksum (定义在include/image.mk) 这一系列目标中,除了Image/PrepareImage/BuildKernel两个目标和具体的平台相关,所以就在target/linux/wndr4500v3/image/Makefile中定义。。其他的都定义在顶层目录下的include/image.mk中

总结

target/xxx 目标,通过层层嵌套,最终执行的是*target/linux/wndr4500v3/中的*xxx*目标。

target/compile 的执行流程就是:

  1. 通过Kernel/Configure配置内核,依赖FORCE每次都执行
  2. 通过Kernel/CompileModules编译内核模块,依赖FORCE每次都执行
  3. 进入到images目录执行compile,本质上没做什么。

target/install 的执行流程就是:

  1. 通过Kernel/Configure配置内核,依赖FORCE每次都执行
  2. 通过Kernel/CompileImage编译内核,依赖FORCE每次都执行
  3. 进入到images目录执行install和compile(compile本质上没做什么)。

其中,Kernel/CompileModulesKernel/CompileImage定义在顶层目录下的include/kernel-defaults.mk中。

12 15

OpenWRT编译系统分析之rootfs/prepare

顾名思义,该任务的主要作用就是根文件系统的一些准备工作。 该make目标定义在*package/Makefile*中。主要包含以下内容:

  1. 调用*package/preconfig*,该目标实际上不存在,所以这个调用没有任何作用。
  2. 检查顶层目录下是否存在files目录。如果存在全部复制到目标根文件系统目录(*build_dir/target-mips_uClibc-0.9.30.1/root-wndr4500v3*)中。
  3. 在目标根文件系统目录中,创建*/etc/rc.d*目录。
  4. 进入目标根文件系统目录,遍历*/etc/init.d/下所有的文件,找出所有包含#!/bin/sh /etc/rc.common的文件。并调用目标根文件系统中的/etc/rc.common script enable把这些脚本都安装到/etc/rc.d*中。
  5. 清理目标根文件系统中所有的*CSV.svn文件以及所有以.*开头的文件。
  6. 调用mklibs函数。该函数只有*CONFIG_USE_MKLIBS*有效时才有有效的定义,但该配置默认未设置。所以实际上这一步也没有任何作用。