zoukankan      html  css  js  c++  java
  • Arm Linux Kernel 构建 情景分析

    概述

    构建一个内核,一般是先配置,后编译。这里以构建 Nexus5 内核为例,代号为 hammerhead。

    配置

    通常做法是以厂商预置的配置为基础,根据自己需要进行配置。命令:
    make ARCH=arm hammerhead_defconfig
     
    执行完毕后,"arch/arm/configs/hammerhead_defconfig" 文件会被复制到 ".config" ,作为默认配置。
    然后运行以下命令根据自己需要进行配置:
    make ARCH=arm menuconfig

    编译

    通常,需要生成 zImage 和 内核模块。如果不指定目标,这两个都会默认生成。命令:
    1. # CROSS_COMPILE 的值根据自己情况设定
      make ARCH=arm CROSS_COMPILE=arm-linux-androideabi-
    这条命令做了什么呢,把 make 输出到控制台的信息贴出来(省略中间相似的信息):
    make ARCH=arm CROSS_COMPILE=arm-linux-androideabi- CONFIG_DEBUG_SECTION_MISMATCH=y
    scripts/kconfig/conf --silentoldconfig Kconfig
    WRAP arch/arm/include/generated/asm/auxvec.h
    WRAP arch/arm/include/generated/asm/bitsperlong.h
    WRAP arch/arm/include/generated/asm/cputime.h
    ...
    WRAP arch/arm/include/generated/asm/siginfo.h
    WRAP arch/arm/include/generated/asm/sizes.h
    CHK include/linux/version.h
    UPD include/linux/version.h
    CHK include/generated/utsrelease.h
    UPD include/generated/utsrelease.h
    Generating include/generated/mach-types.h
    CC kernel/bounds.s
    GEN include/generated/bounds.h
    CC arch/arm/kernel/asm-offsets.s
    GEN include/generated/asm-offsets.h
    CALL scripts/checksyscalls.sh
    HOSTCC scripts/dtc/checks.o
    HOSTCC scripts/dtc/data.o
    ...
    HOSTCC scripts/conmakehash
    HOSTCC scripts/recordmcount
    CC init/main.o
    CHK include/generated/compile.h
    UPD include/generated/compile.h
    CC init/version.o
    CC init/do_mounts.o
    CC init/do_mounts_rd.o
    CC init/do_mounts_initrd.o
    LD init/mounts.o
    CC init/initramfs.o
    CC init/calibrate.o
    LD init/built-in.o
    ...
    AR lib/lib.a
    LD vmlinux.o
    MODPOST vmlinux.o
    GEN .version
    CHK include/generated/compile.h
    UPD include/generated/compile.h
    CC init/version.o
    LD init/built-in.o
    LD .tmp_vmlinux1
    KSYM .tmp_kallsyms1.S
    AS .tmp_kallsyms1.o
    LD .tmp_vmlinux2
    KSYM .tmp_kallsyms2.S
    AS .tmp_kallsyms2.o
    LD vmlinux
    SYSMAP System.map
    SYSMAP .tmp_System.map
    OBJCOPY arch/arm/boot/Image
    Kernel: arch/arm/boot/Image is ready
    AS arch/arm/boot/compressed/head.o
    GZIP arch/arm/boot/compressed/piggy.gzip
    AS arch/arm/boot/compressed/piggy.gzip.o
    CC arch/arm/boot/compressed/misc.o
    CC arch/arm/boot/compressed/decompress.o
    CC arch/arm/boot/compressed/string.o
    AS arch/arm/boot/compressed/lib1funcs.o
    AS arch/arm/boot/compressed/ashldi3.o
    LD arch/arm/boot/compressed/vmlinux
    OBJCOPY arch/arm/boot/zImage
    Kernel: arch/arm/boot/zImage is ready
    DTC arch/arm/boot/msm8974-hammerhead-rev-11.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-11j.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-10.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-c.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-b.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-bn.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-a.dtb
    DTC arch/arm/boot/msm8974-hammerhead-rev-f.dtb
    CAT arch/arm/boot/zImage-dtb
    Kernel: arch/arm/boot/zImage-dtb is ready
    make[1]:没有什么可以做的为`arch/arm/boot/dtbs'。
     
    简单分析一下,大致做了这么几件事情:
    1. 根据配置信息,生成了一些头文件
    2. 编译了一些小工具
    3. 根据配置信息,有选择性地编译一些源码,将输出的 obj 链接成对应的 built-in.o
    4. 生成符号表文件
    5. 将所有的 built-in.o 和符号表链接成内核 vmlinux
    6. 使用 BOJCOPY 从 vmlinux 生成 Image
    7. 生成压缩过的内核 arch/arm/boot/compressed/vmlinux
    8. 使用 OBJCOPY 从 压缩过的内核 vmlinux 生成 zImage
    9. 生成 dtb(device tree blob)
    10.  将 zImage 和 dtb 连接成一个文件:zImage-dtb
    而我们最终需要的文件就是 zImage-dtb(注意:这里没有生成内核模块,因为所有的内核功能都被配置为 built-in ,编译进 zImage-dtb 了)。

    要点分析

           内核配置和编译,依靠的是 make 和 kbuild 系统。无论是 make 还是 kbuild,都只是工具,我们并不一定要完全弄清其内部工作原理,只需要熟悉和工作相关的部分即可。
           这里涉及到的有如下几点:
    • vmlinux 的构建过程
    • arch/arm/boot/compressed/vmlinux 的构建过程
    • 源码是如何选择性地参与内核的构建的
           之所以要分析 vmlinux 和 arch/arm/boot/compressed/vmlinux ,是因为这个两个文件是最原始的两个可执行文件:Image 由 vmlinux 生成;zImage 由 arch/arm/boot/compressed/vmlinux 生成。分析这连个文件的生成,还有助于分析 linux 内核的启动过程。

    基础

           vmlinux 是 makefile 中的一个目标。makefile 中的规则定义了目标和源码的关系,命令则定义了如何由源码生成目标,变量起辅助作用。规则、命令和变量是 makefile 的三大要素。理清 makefile 规则中定义的依赖关系是分析构建过程的关键。涉及到的几个重要文件:
    Makefile
    arch/arm/Makefile
    arch/arm/boot/Makefile
    arch/arm/mach-msm/Makefile.boot
    arch/arm/compressed/Makefile


     

           vmlinux 是一个可执行程序,其链接过程必然涉及的链接脚本,链接脚本是做什么的?看看 ld 手册中的描述:
    通过 lds 文件,我们至少可以知道一个可执行程序的入口在哪里。
    这里又要涉及到几个重要文件:
     
    # 对应 vmlinux
    arch/arm/kernel/vmlinux.lds
    
    
    # 对应 /arch/arm/boot/compressed/vmlinux
    arch/arm/boot/compressed/vmlinux.lds
           vmlinux 是一个可执行程序,由源码编译、链接而来。那么是哪些源码参与了构建过程,又是如何控制这些源码参与的?后面会分析。
            为了分析 makefile,这里借用了 UML 的概念。
            用 包 表示 makefile 文件;用 类 表示 目标和文件;用类间依赖表示目标的依赖;用组合表示变量的定义。
           下面是一个总图,表明了各个目标之间的依赖关系:
    (红色边框是可执行程序,蓝色边框是对应的链接脚本)
    vmlinux 的构建过程

    和 lds 文件的关系

    依赖链:
    _all->all->vmlinux->$(vmlinux-lds)=arch/arm/kernel/vmlinux.lds

    从 _all 到 all:

    PHONY += all
    ifeq ($(KBUILD_EXTMOD),)
    _all: all
    else
    _all: modules
    endif

    KBUILD_EXTMOD 只有在内核树外编译内核模块的时候才会定义 M 变量,从而给其赋值,否则为空,这里为空。

    从 all 到 vmlinux:
    all: vmlinux
     
    从 vmlinux 到 $(vmlinux-lds):
    vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
     
    $(vmlinux-lds) 定义:
     
    vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

    和源码的关系

    ​依赖链:
     
    _all->all->vmlinux->$(vmlinux-init)+$(vmlinux-main)
    看看这个:
     
    # vmlinux
    # ^
    # |
    # +-< $(vmlinux-init)
    # | +--< init/version.o + more
    # |
    # +--< $(vmlinux-main)
    # | +--< driver/built-in.o mm/built-in.o + more
    # |
    # +-< kallsyms.o (see description in CONFIG_KALLSYMS section)
    关键部分上面已经列出,这里再次列出来:
     
    vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
    那么 $(vmlinux-init) 连个变量是什么呢?通过分析,第一次展开后为:“$(head-y) $(init-y)”。没有找到 $(head-y),而 $(init-y) 最终展开为:init/built-in.o。
           到这里有点眉目了(回头看看 make 过程输出的信息,里面有大量的 built-in.o)。可以说是众多的 built-in.o构成了vmlinux。所以 vmlinux 和源码的关系转变成了 built-in.o 和源码的关系。
           还是看 make 的输出信息:
     
    CC init/version.o
    CC init/do_mounts.o
    CC init/do_mounts_rd.o
    CC init/do_mounts_initrd.o
    LD init/mounts.o
    CC init/initramfs.o
    CC init/calibrate.o
    LD init/built-in.o
    可以推测:init/built-in.o 是由 init 目录下的 源码编译、链接而成。在 init 目录下发现 Makefile:
     
    obj-y := main.o version.o mounts.o
    ifneq ($(CONFIG_BLK_DEV_INITRD),y)
    obj-y += noinitramfs.o
    else
    obj-$(CONFIG_BLK_DEV_INITRD)+= initramfs.o
    endif
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY)+= calibrate.o
    
    mounts-y := do_mounts.o
    mounts-$(CONFIG_BLK_DEV_RAM)+= do_mounts_rd.o
    mounts-$(CONFIG_BLK_DEV_INITRD)+= do_mounts_initrd.o
    mounts-$(CONFIG_BLK_DEV_MD)+= do_mounts_md.o
    有内核开发经验的开发者应该知道,赋值到 obj-y 的目标会被编译进 vmlinux,至于是如何控制的,推测 kbuild 系统是有参与的,这属于 make 和 kubild 的内部原理,这里不分析了,知道有这么回事,会用就行了。$(CONFIG_BLK_DEV_INITRD) 等变量在 .config(没错,就是保存内核配置的文件) 文件中定义:
     
    CONFIG_RELAY=y
    CONFIG_BLK_DEV_INITRD=y
    CONFIG_INITRAMFS_SOURCE=""
    这里 vmlinux 和源码的关系就搞清了,是由 built-in.o 来当中间人的:
     
    vmlinux<->built-in.o<->*.c

    和符号表的关系

    略。

    arch/arm/boot/comressed/vmlinux 的构建过程

    有了分析 vmlinux 的基础,分析压缩过的 vmlinux 就容易了。看 规则:
     
    $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o 
    $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) FORCE
    @$(check_for_multiple_zreladdr)
    $(call if_changed,ld)
    @$(check_for_bad_syms)
    参与压缩过的 vmlinux 的构建过程的主要有三类文件:
    • 链接脚本:arch/arm/boot/compressed/vmlinux.lds
    • 解压代码:arch/arm/boot/compressed/ 下的源码
    • 压缩的数据:压缩的 Image(由未经压缩的 vmlinux 生成)
    因为解压缩功能和内核开发关系不大,就不具体分析了。
     


     

  • 相关阅读:
    冒泡排序
    Objective-C 命名规范
    时间轴的制作
    CocoaPods 哪些事
    消息转发机制入门篇
    架构
    算法学习
    AutoLayout自动布局
    网络学习
    HDU 3832 Earth Hour (最短路)
  • 原文地址:https://www.cnblogs.com/JonnyLulu/p/4214020.html
Copyright © 2011-2022 走看看