zoukankan      html  css  js  c++  java
  • OpenWrt构建过程(主Makefile分析)

    玩了这么久的OpenWrt,最近详细研究了一下整个工程的构建过程,也希望作为备份以便以后查阅。网上这方面的文章一大把,不过多数都只提及皮毛,我就选取了感觉还不错的放了过来,毕竟全部自己写太麻烦了。我主要整理了下面这两篇文章,和整个构建过程基本相符,在这里也对原作者表示感谢。

    http://m.blog.csdn.net/article/details?id=50363519

    http://m.blog.csdn.net/article/details?id=50408104

    编译过程概述

    编译的总体过程如下:

    1.编译host工具
    2.编译交叉工具链
    3.编译内核模块
    4.编译ipk
    5.安装ipk到文件系统
    6.编译内核
    7.将内核和文件系统组合成最终binary

    1. 编译host工具

    虽然我们在开始编译前已经安装了一些必要的工具,但编译过程中还需要其他一些主机工具。这部分工具将首先编译。

    2. 编译交叉工具链

    openwrt自带交叉编译链,当然在编译目标平台软件前,需要先编译。

    3. 编译内核模块

    因为部分内核模块将会生成独立的ipk,所以内核模块需要首先编译。

    4. 编译ipk

    这里将编译package目录下的各个软件包,这也是和我们最为息息相关的。

    5. 安装ipk

    将生成的ipk安装到文件系统之中(比如build_dir/target-XXX/root-ramips目录)。

    6. 编译内核

    在完成ipk编译之后,将会编译内核,压缩内核.同时使用mkimage工具,在内核前面生成一个用于uboot识别的头部。

    7. 合成

    在最后一步,将文件系统和内核连接在一起,即生成了目标二进制镜像文件。

    Makefile结构分析

    我们以chaos calmer的代码为例,整个编译的入口是在源码根目录下的Makefile。编译的各种命令都应该在源码根目录下键入。
    整个主Makefile的结构如下:

    world:
    ifneq ($(OPENWRT_BUILD),1)
        顶层
    else
        第二层
    endif

    开始部分是一些注释和变量定义及路径检查。
    根据Makefile的规则,在没有指定编译目标的时候,Makefile中的第一个目标将作为默认目标。
    换句话说,当我们执行make V=s时,这个时候编译的目标就是world.和我们执行make world V=s效果是一样的。

    顶层

    通常在编译时,我们不会定义变量OPENWRT_BUILD的值,所以通常我们是会走到顶层的。
    顶层代码如下:

      _SINGLE=export MAKEFLAGS=$(space);
    
      override OPENWRT_BUILD=1
      export OPENWRT_BUILD
      GREP_OPTIONS=
      export GREP_OPTIONS
      include $(TOPDIR)/include/debug.mk
      include $(TOPDIR)/include/depends.mk
      include $(TOPDIR)/include/toplevel.mk

    这里我们看到变量OPENWRT_BUILD被置为1,然后包含了3个.mk文件。
    这里稍微解释下.mk文件,它们一般没有什么执行动作,都是一些变量的定义还有依赖关系的说明,可以类比于C语言的头文件来理解。

    debug.mk:

    可以通过定义DEBUG的值来控制编译过程。

    depends.mk:

    主要定义了rdep这个变量。

    toplevel.mk:

    这个是我们跟踪编译过程的重要的文件,这个文件在源码根目录下的include文件夹下。

    核心代码如下:

     1 %::
     2     @+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq
     3     @( 
     4         cp .config tmp/.config; 
     5         ./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; 
     6         if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then 
     7             printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)
    " >&2; 
     8         fi 
     9     )
    10     @+$(ULIMIT_FIX) $(SUBMAKE) -r $@ $(if $(WARN_PARALLEL_ERROR), || { 
    11         printf "$(_R)Build failed - please re-run with -j1 to see the real error message$(_N)
    " >&2; 
    12         false; 
    13     } )

    除了少数在toplevel中被定义的目标外,其他编译目标都会走到这里,将之简化后(执行命令为: make V=s):

    %::
        @make V=s -r -s prereq
        @make -w -r world

    首先执行prereq,然后再执行我们指定的目标或者默认目标world

    prereq整理后的依赖关系如下:
    prereq
    其中,staging_dir/host/.prereq-build:

    将会执行一系列主机检查,是否安装了必要的软件。

    prepare-tmpinfo:

    根据scan.mk,扫描target/linuxpackage目录,生成packageinfo和targetinfo。

    总之,顶层完成一系列必要的准备工作.对于绝大多数的目标而言,顶层是必经之路。当然,在toplevel.mk中,我们也可以看到目标menuconfig。也就是说对于目标menuconfig而言,将不会执行到第二层的逻辑。

    第二层

    在上面执行完make prereq之后,将执行make world。
    还记得我们进入顶层后修改了变量OPENWRT_BUILD么?当再次执行make world的时候,由于条件不满足,我们将直接进入第二层来执行。

      include rules.mk
      include $(INCLUDE_DIR)/depends.mk
      include $(INCLUDE_DIR)/subdir.mk
      include target/Makefile
      include package/Makefile
      include tools/Makefile
      include toolchain/Makefile

    rules.mk:

    很重要的一个mk文件,其中规定了很多有用的变量,包括各种目录路径的定义,交叉编译器等等。其中:

    ifeq ($(DUMP),)
      -include $(TOPDIR)/.config
    endif

    就是包含了我们的配置文件。对于Makefile而言,.config文件就是一大串变量的定义,Makefile可以直接读取这些定义,从而控制编译过程。

    subdir.mk:

    这个是读懂我们整个编译过程的关键所在,其中主要定义了两个函数:subdirstampfile,我们稍后加以解释。

    接下来,包含了4个Makefile文件。我们以target/Makefile为例.该文件位于target目录下。
    核心部分为:

    $(eval $(call stampfile,$(curdir),target,prereq,.config))
    $(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
    $(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
    
    $(eval $(call subdir,$(curdir)))

    这里调用了subdir.mk中定义的stampfile函数。将会生成target/stamp-prereq,target/stamp-compile,target/stamp-install三个变量。
    target/stamp-prereq为例,执行部分为make target/prereq。同理target/stamp-compile,执行部分为make target/compile

    最后又调用了sbudir函数,这个函数规定了目标和各子文件夹之间的依赖关系。如果有一定的Makefile基础可以去读读subdir.mk文件。
    举例而言就是:

    当执行目标为target/compile,这个目标将依赖于target/linux/compile
    当执行目标为package/compile,这个目标将依赖于package目录下各子文件夹的compile

    下面就是规定了一系列的依赖关系,我已经将他们梳理了出来,如下图:
    world

    编译过程中的一些变量可能会造成一些困扰,这里将它们的真实值记录下来,以执行make V=s为例:

    1 $(PREP_MK) => OPENWRT_BUILD= QUIET=0
    2 
    3 $(NO_TRACE_MAKE) => make V=ss
    4 
    5 $(_SINGLE) => export MAKEFLAGS= ;
    6 
    7 $(ULIMIT_FIX) => _limit=1024; [  = unlimited -o  -ge 1024 ] || ulimit -n 1024;
  • 相关阅读:
    读Windows核心编程2字符和字符串
    HTTP Error 404.3 while browse to WCF service
    读Windows核心编程3内核对象
    代码安全性的基本原则[转载]
    在HyperV中安装和配置Ubuntu网络
    使用Windows Azure Mobile Service开发Windows Phone 8 App
    Js 学习 使用js arguments 写一个 多态overload 的小程序。 js 闭包写一个10的阶乘的算法
    js 学习 函数
    jquery slider show carouFredSel
    vs 2010 创建windows phone 程序 出现System.ArgumentNullException Value cannot be null. Parameter name: parentContext
  • 原文地址:https://www.cnblogs.com/merlindu/p/6576763.html
Copyright © 2011-2022 走看看