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 生成)

因为解压缩功能和内核开发关系不大,就不具体分析了。

时间: 2024-08-11 10:04:41

Arm Linux Kernel 构建 情景分析的相关文章

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-共享内存

一.库函数shmget()--共享内存区的创建与寻找 asmlinkage long sys_shmget (key_t key, size_t size, int shmflg) { struct shmid_kernel *shp; int err, id = 0; down(&shm_ids.sem); if (key == IPC_PRIVATE) { err = newseg(key, shmflg, size);//分配一个共享内存区供本进程专用,最后返回的是一体化的标示号 } el

Linux内核源代码情景分析-系统初始化

我们跳过boot,setup,直接来到head代码,内核映像的起点是stext,也是_stext,引导和解压缩以后的整个映像放在内存从0x100000即1MB开始的区间.CPU执行内核映像的入口startup_32就在内核映像开头的地方,因此其物理地址也是0x100000. 然而,在正常运行时整个内核映像都应该在系统空间中,系统空间的虚拟地址与物理地址间有个固定的位移,这就是0xC0000000,即3GB.所以,在连接内核映像时已经在所有的符号地址加了一个偏移量0xC0000000,这样star

Linux内核源代码情景分析-fork()

父进程fork出子进程: fork经过系统调用,来到了sys_fork,详细过程请参考Linux内核源代码情景分析-系统调用. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, &regs, 0); } int do_fork(unsigned long clone_flags, unsigned long stack_start, //stack_start为用户空间堆栈指针 str

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_

Linux内核源代码情景分析-文件系统安装后的访问

在Linux内核源代码情景分析-文件系统的安装,一文中,已经调用sudo mount -t ext2 /dev/sdb1 /mnt/sdb,在/mnt/sdb节点上挂载了文件系统,那么我们接下来访问/mnt/sdb/hello.c节点.我们来看一下path_walk的执行有什么不同? int path_walk(const char * name, struct nameidata *nd) { struct dentry *dentry; struct inode *inode; int er

Linux内核源代码情景分析-内存管理

用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3.进程间的共享内存区. 这些页面的的周转有两方面的意思. 1.页面的分配,使用,回收.如进程压栈时新申请的页面,这类页面不进行盘区交换,不使用时释放得以回收. 这部分通过一个场景来解释: Linux内核源代码情景分析-内存管理之用户堆栈的扩展. 2.盘区交换.如要执行硬盘上的对应代码段.把硬盘上的代码段换入内