The Linux Kernel Makefile

来源:互联网 发布:鼻头大怎么办知乎 编辑:程序博客网 时间:2024/05/17 03:45

The Linux kernel makefile is anexcellent example of usingmake in a complex build environment. While it is beyond the scope of this book to explain how the Linux kernel is structured and built, we can examine several interesting uses of make employed by the kernel build system. Seehttp://macarchive.linuxsymposium.org/ols2003/Proceedings/All-Reprints/Reprint-Germaschewski-OLS2003.pdffor a more complete discussion of the 2.5/2.6 kernel build processand its evolution from the 2.4 approach.

Since the makefile has so many facets, we will discuss just a few features that are applicable to a variety of applications. First, we'll look at how single-lettermake variables are used to simulate single-letter command-line options. We'll see how the source and binary trees are separated in a way that allows users to invokemake from the source tree. Next,we'll examine the way themakefile controls the verboseness of the output.Then we'll review the most interesting user-definedfunctions and see how they reduce code duplication, improvereadability, and provide encapsulation. Finally,we'll look at the way themakefile implements a simple help facility.

The Linux kernel build follows the familiar configure, build, installpattern used by my most free software. While many free and opensoftware packages use a separateconfigurescript (typically built byautoconf), the Linuxkernelmakefile implements configuration withmake, invoking scripts and helper programsindirectly.

When the configuration phase is complete, a simplemake ormakeall will build the bare kernel, all the modules,and produce a compressed kernel image (these are thevmlinux,modules, andbzImage targets, respectively). Each kernel buildis given a unique version number in the fileversion.o linked into the kernel. This number(and theversion.o file) are updated by themakefile itself.

Some makefile features you might want to adaptto your ownmakefile are: the handling ofcommand line options, analyzing command-line goals, saving buildstatus between builds, and managing the output ofmake.

11.2.1 Command-Line Options

The first part of the makefile contains code for setting common build options from the command line. Here is an excerpt that controls theverbose flag:

# To put more focus on warnings, be less verbose as default# Use 'make V=1' to see the full commandsifdef V    ifeq ("$(origin V)", "command line")        KBUILD_VERBOSE = $(V)    endifendififndef KBUILD_VERBOSE    KBUILD_VERBOSE = 0endif

The nested(嵌套) ifdef/ifeq pair ensures that theKBUILD_VERBOSE variable is set only ifV is set on the command line. SettingV in the environment ormakefile has no effect. The followingifndef conditional will then turn off the verboseoption ifKBUILD_VERBOSE has not yet been set. Toset the verbose option from either the environment ormakefile, you must setKBUILD_VERBOSE and notV.

Notice, however, that setting KBUILD_VERBOSEdirectly on the command line is allowed and works as expected. Thiscan be useful when writing shell scripts (or aliases) to invoke themakefile. These scripts would then be moreself-documenting, similar to using GNU longoptions.

The other command-line options, sparse checking (C)and external modules (M), both use the same carefulchecking to avoid accidentally setting them from within themakefile.

The next section of the makefile handles the output directory option (O).This is a fairly involved piece of code. To high light its structure,we've replaced some parts of this excerpt with ellipses:

# kbuild supports saving output files in a separate directory.# To locate output files in a separate directory two syntax'es are supported.# In both cases the working directory must be the root of the kernel src.# 1) O=# Use "make O=dir/to/store/output/files/"## 2) Set KBUILD_OUTPUT# Set the environment variable KBUILD_OUTPUT to point to the directory# where the output files shall be placed.# export KBUILD_OUTPUT=dir/to/store/output/files/# make## The O= assigment takes precedence over the KBUILD_OUTPUT environment variable.# O= 赋值的优先级高于环境变量KBUILD_OUTPUT# KBUILD_SRC is set on invocation of make in OBJ directory# KBUILD_SRC is not intended to be used by the regular user (for now)ifeq ($(KBUILD_SRC), )         # OK, Make called in directory where kernel src resides        # Do we want to locate output files in a separate directory?        ifdef O                ifeq ("$(origin O)", "command line")                        KBUILD_OUTPUT := $(O)                endif        endif        ...        ifneq ($(KBUILD_OUTPUT),)                ...                #make执行时设置一个特殊变量“MAKECMDGOALS”,此变量记录了                #命令行参数指定的终极                #目标列表,没有通过参数指定终极目标时此变量为空。               .PHONY: $(MAKECMDGOALS)               $(filter-out _all,$(MAKECMDGOALS))  _all:                #对于一个已经定义的变量,可以使用“替换引用”将其值使用指定的字符(字符串)进行                #替换格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),意思是,#替换变量“VAR”中                #所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值的多个字                #以空格分开)。而对于变量其它部分的“A”字符不进行替换。                $(if $(KBUILD_VERBOSE:1=),@) $(MAKE) -C $(KBUILD_OUTPUT)        \                           KBUILD_SRC=$(CURDIR)         KBUILD_VERBOSE=$(KBUILD_VERBOSE)  \                            KBUILD_CHECK=$(KBUILD_CHECK) KBUILD_EXTMOD="$(KBUILD_EXTMOD)"  \                            -f $(CURDIR)/Makefile $@                    # Leave processing to above invocation of make                   skip-makefile := 1          endif # ifneq ($(KBUILD_OUTPUT),)endif # ifeq ($(KBUILD_SRC),)# We process the rest of the Makefile if this is the final invocation of make #ifeq ($(skip-makefile),)  ...the rest of the makefile here...endif   # skip-makefile

Essentially, this says that if KBUILD_OUTPUT is set, invokemake recursively(递归) in the output directory defined byKBUILD_OUTPUT. Set KBUILD_SRC to the directory where make was originally executed, and grab the makefile from there as well. The rest of the makefile will not be seen by make, sinceskip-makefile will be set. The recursivemake will reread this same makefile again, only this time KBUILD_SRC will be set, soskip-makefile will be undefined, and the rest ofthemakefile will be read and processed.

This concludes the processing of command-line options. The bulk o fthe makefile follows in the ifeq($(skip-makefile),) section.

11.2.2 Configuration Versus Building

The makefile contains configuration targets and build targets.The configuration targets have the formmenuconfig,defconfig, etc. Maintenance targets likeclean are treated as configuration targets aswell. Other targets such asall,vmlinux, andmodules are build targets.The primary result of invoking a configuration target is two files:.config and.config.cmd. These two files are included by themakefile for build targets but are not included for configuration targets (since the configuration target creates them). It is also possible to mix both configuration targets and build targets on a singlemake invocation, suchas:

$ make oldconfig all

In this case, the makefile invokes itself recursively handling each target individually, thus handling configuration targets separately from build targets.

The code controlling configuration, build, and mixed targets begin swith:

# To make sure we do not include .config for any of the *config targets# catch them early, and hand them over to scripts/kconfig/Makefile# It is allowed to specify more targets when calling make, including# mixing *config targets and build targets.# For example 'make oldconfig all'.# Detect when mixed targets is specified, and make a second invocation# of make so .config is not included in this case either (for *config).no-dot-config-targets := clean mrproper distclean \                         cscope TAGS tags help %docs check%config-targets := 0mixed-targets  := 0dot-config     := 1

The variable no-dot-config-targets lists additional targets that do not require a.configfile. The code then initializes theconfig-targets,mixed-targets,and dot-config variables. Theconfig-targets variable is 1 if there are any configuration targets on the command line.The dot-config variable is 1 if there are build targets on the command line.Finally,mixed-targets is 1 if there are both configuration and build targets.

The code to set dot-config is:

ifneq ( $(filter $(no-dot-config-targets), $(MAKECMDGOALS)), )   #到这里,说明命令行中有 $no-dot-config-targets  ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)         #这里说明除了$no-dot-config-targets,没别的了,也就是说命令行只有$no-dot-config-targets        dot-config := 0  endifendif

The filter expression is non-empty if there are configuration targets inMAKECMDGOALS. The ifneq part is true if thefilter expression is not empty. The code is hard to follow partly because it contains a double negative.The ifeq expression is true if MAKECMDGOALS contains only configuration targets.So,dot-config will be set to 0 if there are configuration targets and only configuration targets inMAKECMDGOALS. A more verbose implementation might make the meaning of these two conditionals more clear:

config-target-list := clean mrproper distclean \                         cscope TAGS tags help %docs check%config-target-goal := $(filter $(config-target-list), $(MAKECMDGOALS))build-target-goal := $(filter-out $(config-target-list), $(MAKECMDGOALS))ifdef config-target-goal  ifndef build-target-goal    dot-config := 0  endifendif

The ifdef form can be used instead of ifneq, becauseempty variables are treated as undefined, but care must be taken to ensure a variable does not contain merely a string of blanks (which would cause it to be defined).

The config-targets and mixed-targets variables are set in the next code block:

ifeq ($(KBUILD_EXTMOD),)  ifneq ($(filter config %config,$(MAKECMDGOALS)),)    config-targets := 1    ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)      mixed-targets := 1    endif  endifendif

KBUILD_EXTMOD 在makefile 开始部分的赋值过程

ifeq ("$(origin M)", "command line")

  KBUILD_EXTMOD := $(M)
endif
在编译模块时,M=选项让该makefile在构造目标之前返回到模块源代码目录。可以参考LDD第三版 29页

KBUILD_EXTMOD will be non-empty when external modules are being built, but not during normal builds.The first ifneq will be true when MAKECMDGOALS contains a goal with theconfig suffix. The second ifneq will be true when MAKECMDGOALS contains nonconfigtargets, too.

Once the variables are set, they are used in an if-else chain with four branches. The code has been condensed and indented to hig hlight its structure:

ifeq ($(mixed-targets),1)        # We're called with mixed targets (*config and build targets).        # Handle them one by one.        %:: FORCE                $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@else        ifeq ($(config-targets),1)                # *config targets only - make sure prerequisites are updated, and descend                # in scripts/kconfig to make the *config target                %config: scripts_basic FORCE                        $(Q)$(MAKE) $(build)=scripts/kconfig $@        else            # Build targets only - this includes vmlinux, arch specific targets, clean            # targets and others. In general all targets except *config targets.            ...            ifeq ($(dot-config),1)                  # In this section, we need .config                  # Read in dependencies to all Kconfig* files, make sure to run                  # oldconfig if changes are detected.                                    -include .config.cmd                  include .config                  # If .config needs to be updated, it will be done via the dependency                  # that autoconf has on .config.                  # To avoid any implicit rule to kick in, define an empty command                  .config: ;                  # If .config is newer than include/linux/autoconf.h, someone tinkered                  # with it and forgot to run make oldconfig                  include/linux/autoconf.h: .config                          $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig            else                  # Dummy target needed, because used as prerequisite                  include/linux/autoconf.h: ;            endif            include $(srctree)/arch/$(ARCH)/Makefile            ... lots more make code ...      endif #ifeq ($(config-targets),1)endif #ifeq ($(mixed-targets),1)

The first branch, ifeq ($(mixed-targets),1),handles mixed command-line arguments. The only target in this branch is a completely generic pattern rule. Since there are no specific rules to handle targets (those rules are in another conditional branch), each target invokes the pattern rule once. This is how a command line with both configuration targets and build targets is separated into a simpler command line. The command script for the generic pattern rule invokes make recursively for each target, causing this same logic to be applied, only this time with no mixed command-line targets. TheFORCEprerequisite(依赖) is used instead of.PHONY, because pattern rules like:

%:: FORCE

cannot be declared .PHONY. So it seems reasonable to useFORCE consistently everywhere.

The second branch of theif-else chain,ifeq($(config-targets),1), is invoked when there are only configuration targets on the commandline. Here the primary target in the branch is the pattern rule %config (other targets have been omitted). The command script invokesmake recursively in thescripts/kconfig subdirectory and passes along the target.The curious$(build) construct is defined at the end of themakefile:

# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=dir# Usage:# $(Q)$(MAKE) $(build)=dirbuild := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj

If KBUILD_SRC is set, the-foption is given a full path to thescripts makefile, otherwise a simple relative path is used. Next, theobj variable is set to the righthand side of the equals sign.

The third branch, ifeq($(dot-config),1),handles build targets that require including the two generated configuration files,.config and.config.cmd.The final branch merely includes a dummy target for autoconf.h to allow it to be used as aprerequisite, even if it doesn't exist.

Most of the remainder of the makefile follows the third and fourth branches. It contains the code for building thekernel and modules.

11.2.3 Managing Command Echo

The kernel makefiles use a noveltechnique for managing the level of detailechoed by commands. Each significant task is represented in both averbose and a quiet version. The verbose version is simply thecommand to be executed in its natural form and is stored in avariable namedcmd_action. The briefversion is a short message describing the action and is stored in avariable namedquiet_cmd_action. Forexample, the command to produce emacs tags is:

quiet_cmd_TAGS = MAKE $@      cmd_TAGS = $(all-sources) | etags -

A command is executed by calling the cmdfunction:

# If quiet is set, only print short version of commandcmd = @$(if $($(quiet)cmd_$(1)),\         echo '  $($(quiet)cmd_$(1))' &&) $(cmd_$(1))

To invoke the code for building emacs tags, themakefile would contain:

TAGS:        $(call cmd,TAGS)

Notice the cmd function begins with an@, so the only text echoed by the function is textfrom theecho command. In normal mode, thevariablequiet is empty, and the test in theif,$($(quiet)cmd_$(1)),expands to$(cmd_TAGS). Since this variable is notempty, the entire function expands to:

echo '  $(all-sources) | etags -' && $(all-sources) | etags -

If the quiet version is desired, the variablequiet contains the valuequiet_and the function expands to:

echo '  MAKE $@' && $(all-sources) | etags -

The variable can also be set to silent_. Sincethere is no commandsilent_cmd_TAGS, this valuecauses thecmd function to echo nothing at all.

Echoing the command sometimes becomes more complex, particularly ifcommands contain single quotes. In these cases, themakefile contains this code:

echo '  MAKE $@' && $(all-sources) | etags -

Here the echo command contains a substitution thatreplaces single quotes with escaped single quotes to allow them to beproperly echoed.

Minor commands that do not warrant the trouble of writingcmd_ andquiet_cmd_ variablesare prefixed with$(Q), which contains eithernothing or@:

ifeq ($(KBUILD_VERBOSE),1)  quiet =  Q =else  quiet=quiet_  Q = @endif# If the user is running make -s (silent mode), suppress echoing of# commandsifneq ($(findstring s,$(MAKEFLAGS)),)  quiet=silent_endif

11.2.4 User-Defined Functions

The kernel makefile defines anumber of functions. Here wecover the most interesting ones. The code has been reformatted toimprove readability.

The check_gcc function is used to select agcc command-line option.

# $(call check_gcc,preferred-option,alternate-option)check_gcc =                                                                  \                $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null \                -xc /dev/null > /dev/null 2>&1;      \               then                                    \               echo "$(1)";                          \               else                                    \               echo "$(2)";                          \               fi ;)


The function works by invoking gcc on a null inputfile with the preferred command-line option. The output file,standard output, and standard error files are discarded. If thegcc command succeeds, it means the preferredcommand-line option is valid for this architecture and is returned bythe function. Otherwise, the option is invalid and the alternateoption is returned. An example use can be found inarch/i386/Makefile:

# prevent gcc from keeping the stack 16 byte alignedCFLAGS += $(call check_gcc,-mpreferred-stack-boundary=2,)

The if_changed_dep function generates dependencyinformation using a remarkable technique.

# execute the command and also postprocess generated# .d dependencies fileif_changed_dep =                                        \    $(if                                                \      $(strip $?                                        \        $(filter-out FORCE $(wildcard $^),$^)           \        $(filter-out $(cmd_$(1)),$(cmd_$@))             \        $(filter-out $(cmd_$@),$(cmd_$(1)))),           \      @set -e;                                          \      $(if $($(quiet)cmd_$(1)),                         \        echo '  $(subst ','\'',$($(quiet)cmd_$(1)))';)  \ $(cmd_$(1));                                      \      scripts/basic/fixdep                              \   $(depfile)                                     \    $@                                             \         '$(subst $,$$,$(subst ','\'',$(cmd_$(1))))' \         > $(@D)/.$(@F).tmp;                            \      rm -f $(depfile);                                 \      mv -f $(@D)/.$(@F).tmp $(@D)/.$(@F).cmd)



The function consists of a single if clause. Thedetails of the test are pretty obscure, but it is clear the intent isto be non-empty if the dependency file should be regenerated. Normaldependency information is concerned with the modification timestampson files. The kernel build system adds another wrinkle to this task.The kernel build uses a wide variety of compiler options to controlthe construction and behavior of components. To ensure thatcommand-line options are properly accounted for during a build, themakefile is implemented so that if command-lineoptions used for a particular target change, the file is recompiled.Let's see how this is accomplished.

In essence, the command used to compile each file in the kernel issaved in a.cmd file. When a subsequent build isexecuted,make reads the.cmdfiles and compares the current compile command with the last command.If they are different, the.cmd dependency fileis regenerated causing the object file to be rebuilt. The.cmd file usually contains two items: thedependencies that represent actual files for the target file and asingle variable recording the command-line options. For example, thefilearch/i386/kernel/cpu/mtrr/if.c yields this(abbreviated) file:

cmd_arch/i386/kernel/cpu/mtrr/if.o := gcc -Wp,-MD ...; if.cdeps_arch/i386/kernel/cpu/mtrr/if.o := \  arch/i386/kernel/cpu/mtrr/if.c \  ...arch/i386/kernel/cpu/mtrr/if.o: $(deps_arch/i386/kernel/cpu/mtrr/if.o)$(deps_arch/i386/kernel/cpu/mtrr/if.o):

Getting back to the if_changed_dep function, thefirst argument to thestrip is simply theprerequisites that are newer than the target, if any. The secondargument tostrip is all the prerequisites otherthan files and the empty target FORCE. The reallyobscure bit is the last two filter-out calls:

$(filter-out $(cmd_$(1)),$(cmd_$@))$(filter-out $(cmd_$@),$(cmd_$(1)))

One or both of these calls will expand to a non-empty string if thecommand-line options have changed. The macro$(cmd_$(1)) is the current command and$(cmd_$@) will be the previous command, forinstance the variablecmd_arch/i386/kernel/cpu/mtrr/if.o just shown. Ifthe new command contains additional options, the firstfilter-out will be empty, and the second willexpand to the new options. If the new command contains fewer options,the first command will contain the deleted options and the secondwill be empty. Interestingly, since filter-outaccepts a list of words (each treated as an independent pattern), theorder of options can change and thefilter-outwill still accurately identify added or removed options. Prettynifty.

The first statement in the command script sets a shell option to exitimmediately on error. This prevents the multiline script fromcorrupting files in the event of problems. For simple scripts anotherway to achieve this effect is to connect statements with&& rather than semicolons.

The next statement is an echo command writtenusing the techniques described inSection 11.2.3 earlier in this chapter,followed by the dependency generating command itself. The commandwrites$(depfile), which is then transformed byscripts/basic/fixdep. The nestedsubst function in thefixdepcommand line first escapes single quotes, then escapes occurrences of$$ (the current process number in shell syntax).

Finally, if no errors have occurred, the intermediate file$(depfile) is removed and the generated dependencyfile (with its.cmd suffix) is moved into place.

The next function, if_changed_rule, uses thesame comparison technique asif_changed_dep tocontrol the execution of a command:

# Usage: $(call if_changed_rule,foo)# will check if $(cmd_foo) changed, or any of the prequisites changed,# and if so will execute $(rule_foo)if_changed_rule =                                   \    $(if $(strip $?                                 \           $(filter-out $(cmd_$(1)),$(cmd_$(@F)))   \           $(filter-out $(cmd_$(@F)),$(cmd_$(1)))), \      @$(rule_$(1)))



In the topmost makefile, this function is usedto link the kernel with these macros:

# This is a bit tricky: If we need to relink vmlinux, we want# the version number incremented, which means recompile init/version.o# and relink init/init.o. However, we cannot do this during the# normal descending-into-subdirs phase, since at that time# we cannot yet know if we will need to relink vmlinux.# So we descend into init/ inside the rule for vmlinux again....quiet_cmd_vmlinux_ _ = LD $@define cmd_vmlinux_ _  $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) \    ...endef# set -e makes the rule exit immediately on errordefine rule_vmlinux_ _  +set -e;                                              \  $(if $(filter .tmp_kallsyms%,$^),,                    \    echo '  GEN     .version';                          \    . $(srctree)/scripts/mkversion > .tmp_version;      \    mv -f .tmp_version .version;                        \    $(MAKE) $(build)=init;)                             \  $(if $($(quiet)cmd_vmlinux_ _),                        \    echo '  $($(quiet)cmd_vmlinux_ _)' &&)               \  $(cmd_vmlinux_ _);                                     \  echo 'cmd_$@ := $(cmd_vmlinux_ _)' > $(@D)/.$(@F).cmdendefdefine rule_vmlinux  $(rule_vmlinux_ _);          \  $(NM) $@ |                  \  grep -v '\(compiled\)\|...' | \  sort > System.mapendef



The if_changed_rule function is used to invokerule_vmlinux, which performs the link and buildsthe finalSystem.map. As the comment in themakefile notes, therule_vmlinux__ function must regenerate the kernel version file andrelinkinit.o before relinkingvmlinux. This is controlled by the firstif inrule_vmlinux_ _. Thesecondif controls the echoing of the linkcommand,$(cmd_vmlinux_ _). After the linkcommand, the actual command executed is recorded in a.cmd file for comparison in the next build.