[内核源码][Makefile] Linux内核源码的Makefile

前言

学习Linux内核最直接的方法就是阅读源码,但是Linux庞大的源代码却让很多初学者感到无从下手,而编译内核所用到的Makefile文件更是让初学者迷茫。我第一次阅读Makefile文件的感受是这是Makefile文件吗?甚至我已经怀疑自己,我会读写Makefile文件吗?

初学者首先应该学习make工具,并且能够熟练地读写简单的Makefile,当然系统全面地学习更好。只是,Makefile文件的规则很多,我们平时编译helloworld的时候根本无法涉及到较为复杂的规则。所以,更为有效的学习方法是,在掌握基本规则的前提下遇到某个规则的时候查阅资料,采用99 + 1的学习方法逐步掌握make工具及其Makefile文件规则。

本文不是专门讲述Makefile文件规则的文章,故无法通过本文学习Makefile文件规则。本文的初衷是讲述顶层Makefile与各层Makefile之间的关系,以此来让初学者明白内核映像文件是如何制作的。由于时间的关系,我会逐步完善本文。而因为本人也是个初学者,还往源码学习的老鸟们指正我的错误,以免让别人 学到不正确的内容而误入歧途。本文所使用的Linux源码版本是4.10,其他版本可能会有所不同,但变化不大。

本文第一次编辑于2017-3-24。


Makefile文件层次结构

首先我们在此说明一下,将Linux源码下载压缩后会产生linux-x.x(例如linux-4.10)的目录,在该目录下还有各个子模块源码的子目录。我们将这个linux-x.x目录称为顶层目录,而各个子目录称为模块子目录。在顶层目录中的Makefile称为顶层Makefile。

顶层Makefile中包含全局性质的变量和设置,子Makefile中包含各自的变量和设置。


顶层Makefile

版本及名称

在顶层Makefile最开始的地方定义了当前源码的版本及名称信息

VERSION = 4
PATCHLEVEL = 10
SUBLEVEL = 0
EXTRAVERSION =
NAME = Fearless Coyote

内置隐含规则和变量设置

因为make工具内置的隐含规则和变量设置可能会导致一些无法预料的结果,而使得调试变得更困难。因此,应该将他们禁用掉。

MAKEFLAGS += -rR --include-dir=$(CURDIR)

这一点很重要,这使得编译内核的整个过程都完全掌握在我们手中,规则完全由我们来制定。

修改C语言区域设置

unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC

避免shell环境设置的干扰

unexport GREP_OPTIONS

设置编译信息的输出模式

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

如果在命令行中对变量V赋值(例如:make V=1),则输出full模式;否则,输出quiet模式。同时对变量KBUILD_VERBOSE赋值。

ifneq ($(filter 4.%,$(MAKE_VERSION)),)   # make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
else                    # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  quiet=silent_
endif
endif

如果命令行中有 -s 的参数,则输出slient模式。

full模式:输出完整编译命令。
quiet模式:输出Compiling ***的格式。
silent模式:输出任何信息。

设置输出目录

Kbuild支持将输出文件保存在单独的目录中,由两种方式可以实现:

  1. 在命令行中设置变量O,例如:make O=dir/to/store/output/files/
  2. 设置变量KBUILD_OUTPUT的值,例如export KBUILD_OUTPUT=dir/to/store/output/files/
    变量O的优先级高于变量KBUILD_OUTPUT。

    ifeq ($(KBUILD_SRC),)
    ifeq ("$(origin O)", "command line")
    KBUILD_OUTPUT := $(O)
    endif
    PHONY := _all
    _all:
    $(CURDIR)/Makefile Makefile: ;
    ifneq ($(words $(subst :, ,$(CURDIR))), 1)
    $(error main directory cannot contain spaces nor colons)
    endif
    ifneq ($(KBUILD_OUTPUT),)
    saved-output := $(KBUILD_OUTPUT)
    KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
    && /bin/pwd)
    $(if $(KBUILD_OUTPUT),, \
    $(error failed to create output directory "$(saved-output)"))
    PHONY += $(MAKECMDGOALS) sub-make
    $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
    @:
    sub-make:
    $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
    -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
    skip-makefile := 1
    endif # ifneq ($(KBUILD_OUTPUT),)
    endif # ifeq ($(KBUILD_SRC),)

    KBUILD_SRC变量是在调用make的时候设置。
    如果设置了输出目录,则创建输出目录。

取消Enter/Leaving directory…的信息

MAKEFLAGS += --no-print-directory

设置是否进行对源代码的检查

对命令行参数变量C进行设置,可以选择使用sparse工具检查源代码。

  1. 如果C=1,则只对修改的源代码进行检查;
  2. 如果C=2,则对全部代码进行检查;
  3. 如果不设置C的值,则不进行检查。
ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

编译外部模块

如果编译外部模块,则对命令行参数变量M进行赋值

ifdef SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

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

包含Makefile自定义函数文件Kbuild.include

include scripts/Kbuild.include

该条语句将文件 scripts/Kbuild.include 包含进顶层Makefile文件中,Kbuild.include 文件中定义了Makefile 文件用到的自定义函数。

设置目录变量

ifeq ($(KBUILD_SRC),)
        # building in the source tree
        srctree := .
else
        ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
                # building in a subdirectory of the source tree
                srctree := ..
        else
                srctree := $(KBUILD_SRC)
        endif
endif
objtree     := .
src     := $(srctree)
obj     := $(objtree)

获取编译环境平台

SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
                  -e s/sun4u/sparc64/ \
                  -e s/arm.*/arm/ -e s/sa110/arm/ \
                  -e s/s390x/s390/ -e s/parisc64/parisc/ \
                  -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
                  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

设置目标平台

如果设置了变量ARCH,则即是设置了目标平台;否则,当前编译环境平台就是目标平台。

ARCH        ?= $(SUBARCH)
CROSS_COMPILE   ?= $(CONFIG_CROSS_COMPILE:"%"=%)

UTS_MACHINE     := $(ARCH)
SRCARCH     := $(ARCH)

ifeq ($(ARCH),i386)
        SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
        SRCARCH := x86
endif

ifeq ($(ARCH),sparc32)
       SRCARCH := sparc
endif
ifeq ($(ARCH),sparc64)
       SRCARCH := sparc
endif

ifeq ($(ARCH),sh64)
       SRCARCH := sh
endif

ifeq ($(ARCH),tilepro)
       SRCARCH := tile
endif
ifeq ($(ARCH),tilegx)
       SRCARCH := tile
endif

设置与平台相关的头文件的目录

hdr-arch  := $(SRCARCH)

设置 configure 文件

KCONFIG_CONFIG  ?= .config
export KCONFIG_CONFIG

设置调用bash的短名称

CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
      else if [ -x /bin/bash ]; then echo /bin/bash; \
      else echo sh; fi ; fi)

设置针对编译环境的变量

HOSTCC       = gcc
HOSTCXX      = g++
HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1)
HOSTCFLAGS  += -Wno-unused-value -Wno-unused-parameter \
        -Wno-missing-field-initializers -fno-delete-null-pointer-checks
endif

其中,如果编译器为clang,则添加其特有的编译参数。

判断是编译内核还是模块,还是两者均是

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
  KBUILD_MODULES := 1
endif

ifeq ($(MAKECMDGOALS),)
  KBUILD_MODULES := 1
endif

export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD

包含 scripts/Kbuild.include 文件

scripts/Kbuild.include: ;
include scripts/Kbuild.include

在此文件中定义了一些Makefile文件中用到的通用的自定义函数。

定义关于编译工具的变量

AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm
STRIP       = $(CROSS_COMPILE)strip
OBJCOPY     = $(CROSS_COMPILE)objcopy
OBJDUMP     = $(CROSS_COMPILE)objdump
AWK     = awk
GENKSYMS    = scripts/genksyms/genksyms
INSTALLKERNEL  := installkernel
DEPMOD      = /sbin/depmod
PERL        = perl
PYTHON      = python
CHECK       = sparse

CHECKFLAGS     := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
          -Wbitwise -Wno-return-void $(CF)
NOSTDINC_FLAGS  =
CFLAGS_MODULE   =
AFLAGS_MODULE   =
LDFLAGS_MODULE  =
CFLAGS_KERNEL   =
AFLAGS_KERNEL   =
LDFLAGS_vmlinux =
CFLAGS_GCOV = -fprofile-arcs -ftest-coverage -fno-tree-loop-im -Wno-maybe-uninitialized
CFLAGS_KCOV := $(call cc-option,-fsanitize-coverage=trace-pc,)

设置用户态头文件目录

USERINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include/uapi \
        -I$(objtree)/arch/$(hdr-arch)/include/generated/uapi \
        -I$(srctree)/include/uapi \
        -I$(objtree)/include/generated/uapi \
                -include $(srctree)/include/linux/kconfig.h

内核态头文件

LINUXINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include \
        -I$(objtree)/arch/$(hdr-arch)/include/generated/uapi \
        -I$(objtree)/arch/$(hdr-arch)/include/generated \
        $(if $(KBUILD_SRC), -I$(srctree)/include) \
        -I$(objtree)/include

LINUXINCLUDE    += $(filter-out $(LINUXINCLUDE),$(USERINCLUDE))

定义 KBuild 编译参数

KBUILD_CPPFLAGS := -D__KERNEL__

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89 $(call cc-option,-fno-PIE)


KBUILD_AFLAGS_KERNEL :=
KBUILD_CFLAGS_KERNEL :=
KBUILD_AFLAGS   := -D__ASSEMBLY__ $(call cc-option,-fno-PIE)
KBUILD_AFLAGS_MODULE  := -DMODULE
KBUILD_CFLAGS_MODULE  := -DMODULE
KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds

设置内核版本

KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

编译模块时的目录

export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions

包含 include arch/$(SRCARCH)/Makefile 文件

include arch/$(SRCARCH)/Makefile

arch/x86/Makefile

确定 defconfig

ifeq ($(ARCH),x86)
  ifeq ($(shell uname -m),x86_64)
        KBUILD_DEFCONFIG := x86_64_defconfig
  else
        KBUILD_DEFCONFIG := i386_defconfig
  endif
else
        KBUILD_DEFCONFIG := $(ARCH)_defconfig
endif

确定16位实模式代码的编译参数

CODE16GCC_CFLAGS := -m32 -Wa,$(srctree)/arch/x86/boot/code16gcc.h
M16_CFLAGS   := $(call cc-option, -m16, $(CODE16GCC_CFLAGS))

REALMODE_CFLAGS := $(M16_CFLAGS) -g -Os -D__KERNEL__ \
           -DDISABLE_BRANCH_PROFILING \
           -Wall -Wstrict-prototypes -march=i386 -mregparm=3 \
           -fno-strict-aliasing -fomit-frame-pointer -fno-pic \
           -mno-mmx -mno-sse \
           $(call cc-option, -ffreestanding) \
           $(call cc-option, -fno-stack-protector) \
           $(call cc-option, -mpreferred-stack-boundary=2)
export REALMODE_CFLAGS

需要注意的是,gcc 4.9之后才支持-m16参数,而 clang从3.5才支持。早期编译器,需要传递一个包含.code16gcc的头文件。

生成系统调用表

archheaders:
    $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all

内核目标

head-y := arch/x86/kernel/head_$(BITS).o
head-y += arch/x86/kernel/head$(BITS).o
head-y += arch/x86/kernel/ebda.o
head-y += arch/x86/kernel/platform-quirks.o

libs-y  += arch/x86/lib/

core-y += arch/x86/

drivers-$(CONFIG_MATH_EMULATION) += arch/x86/math-emu/
drivers-$(CONFIG_PCI)            += arch/x86/pci/

drivers-$(CONFIG_OPROFILE) += arch/x86/oprofile/

drivers-$(CONFIG_PM) += arch/x86/power/

drivers-$(CONFIG_FB) += arch/x86/video/

drivers-$(CONFIG_RAS) += arch/x86/ras/

制作内核映像(未压缩及压缩)

boot := arch/x86/boot

BOOT_TARGETS = bzlilo bzdisk fdimage fdimage144 fdimage288 isoimage

all: bzImage

KBUILD_IMAGE := $(boot)/bzImage

bzImage: vmlinux
ifeq ($(CONFIG_X86_DECODER_SELFTEST),y)
    $(Q)$(MAKE) $(build)=arch/x86/tools posttest
endif
    $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
    $(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
    $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@

$(BOOT_TARGETS): vmlinux
    $(Q)$(MAKE) $(build)=$(boot) $@

$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)这一句可看出,make进入arch/x86/boot进行编译。


arch/x86/boot/Makefile

设置SVGA 模式

SVGA_MODE   := -DSVGA_MODE=NORMAL_VGA

编译目标

targets     := vmlinux.bin setup.bin setup.elf bzImage
targets     += fdimage fdimage144 fdimage288 image.iso mtools.conf
subdir-     := compressed

setup-y     += a20.o bioscall.o cmdline.o copy.o cpu.o cpuflags.o cpucheck.o
setup-y     += early_serial_console.o edd.o header.o main.o memory.o
setup-y     += pm.o pmjump.o printf.o regs.o string.o tty.o video.o
setup-y     += video-mode.o version.o
setup-$(CONFIG_X86_APM_BOOT) += apm.o

setup-y     += video-vga.o
setup-y     += video-vesa.o
setup-y     += video-bios.o

targets     += $(setup-y)

编译

KBUILD_CFLAGS   := $(REALMODE_CFLAGS) -D_SETUP
KBUILD_AFLAGS   := $(KBUILD_CFLAGS) -D__ASSEMBLY__
GCOV_PROFILE := n
UBSAN_SANITIZE := n

$(obj)/bzImage: asflags-y  := $(SVGA_MODE)

quiet_cmd_image = BUILD   $@
cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
                   $(obj)/zoffset.h $@

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @echo 'Kernel: $@ is ready' ' (#'`cat .version`')'

OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S
$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,objcopy)

SETUP_OBJS = $(addprefix $(obj)/,$(setup-y))

sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [ABCDGRSTVW] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|input_data\|_end\|_ehead\|_text\|z_.*\)$$/\#define ZO_\2 0x\1/p'

quiet_cmd_zoffset = ZOFFSET $@
      cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@

targets += zoffset.h
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,zoffset)


AFLAGS_header.o += -I$(objtree)/$(obj)
$(obj)/header.o: $(obj)/zoffset.h

LDFLAGS_setup.elf   := -T
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
    $(call if_changed,ld)

OBJCOPYFLAGS_setup.bin  := -O binary
$(obj)/setup.bin: $(obj)/setup.elf FORCE
    $(call if_changed,objcopy)

$(obj)/compressed/vmlinux: FORCE
    $(Q)$(MAKE) $(build)=$(obj)/compressed $@

镜像默认命令行参数

image_cmdline = default linux $(FDARGS) $(if $(FDINITRD),initrd=initrd.img,)
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值