这节主要是对Makefile进行详细注解:
VERSION = 1 ##版本号
PATCHLEVEL = 0 #补丁号
ALPHA =
all: Version zImage
#all标示创建Makefile所知的最顶层的目标。这里就是Version和zImage。zImage就是引导启动盘映像文件,
#若将其写入软盘就可以使用该软盘引导linux系统了。
#Version用于生成.config和.depend文件,zImage就是要
#生成的核心,Version依赖于dummy,作用是删除tools/version.h和如果系统中没有.config
#和.depend文件的话,帮助生成这两个文件,zImage要生成的映像文件名,1.0版本的核心
#不支持非压缩的核心
.EXPORT_ALL_VARIABLES: #导出所有的变量给子目录中的Makefile使用
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
#使用make的shell函数来执行shell程序,使用if语句的-x选
#项来判断程序是否可以执行,BASH为shell的内部变量,在make中使用双$$来引用变量
#
# Make "config" the default target if there is no configuration file or
# "depend" the target if there is no top-level dependency information.
#
ifeq (.config,$(wildcard .config)) #如果当前目录下存在.config的文件
include .config #那么包含.config文件
ifeq (.depend,$(wildcard .depend)) #如果当前目录下存在.depend文件
include .depend #则包含.depend文件
else
CONFIGURATION = depend #否则变量CONFIGURATION赋值为depend
endif
else
CONFIGURATION = config #否则变量CONFIGURATION赋值为config
endif
#$(wildcard PATTERN)是make的函数,
#功能是列出当前目录下所有符合模式“ PATTERN”格式的文件名,
#返回值是空格分割的、存在当前目录下的所有符合模式“ PATTERN”的文件名
ifdef CONFIGURATION #如果定义了CONFIGURATION,则让CONFIGURE=dummy,也就是说让.config 和.depend依赖于dummy
CONFIGURE = dummy #否则CONFIGURE为空,就表示存在文件.config .depend
endif
#
# ROOT_DEV specifies the default root-device when making the image.
# This can be either FLOPPY, CURRENT, /dev/xxxx or empty, in which case
# the default of FLOPPY is used by 'build'.
#
#ROOT_DEV指定在创建内核映像(zImage)文件时所使用的默认根文件系统所在的设备,
#这可以是软盘(FLOPPY),CURRENT,/dev/xxxx #或者干脆为空,空着时build程序(在tools/目录中)
#就使用默认值FLOPPY
#ROOT_DEV = CURRENT
ROOT_DEV = /dev/fd0
#
# If you want to preset the SVGA mode, uncomment the next line and
# set SVGA_MODE to whatever number you want.
# Set it to -DSVGA_MODE=NORMAL_VGA if you just want the EGA/VGA mode.
# The number is the same as you would ordinarily press at bootup.
#
SVGA_MODE= -DSVGA_MODE=NORMAL_VGA
#
# standard CFLAGS
#
#-Wall打印#所有警告信息,-w关闭警告信息,
#-Wstrict-prototypes#如果函数的声明或定义没有指出参数类型,编译器就发出警告
#-O2包含-O1的优化并增加了不#需要在目标文件大小和执行速度上进行折衷的优化。编译器不执行循环展开以及函数#内联.
#此选项将增加编译时间和目标文件的执行性能
#-fomit-frame-pointer指明对于无需帧指针(Frame-pointer)的函数不要把帧指针
#保留在寄存器中。这样在函数中可以避免对帧指针的操作和维护。-pipe使用管道#代替临时文件。
CFLAGS = -Wall -w -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe
#-x language filename#设定文档所使用的语言,使后缀名无效,对以后的多个有效.
ifdef CONFIG_CPP
CFLAGS := $(CFLAGS) -x c++
endif
#高版本的编译器用-mtune=i486
ifdef CONFIG_M486
#CFLAGS := $(CFLAGS) -m486
CFLAGS := $(CFLAGS) -mtune=i486
else
CFLAGS := $(CFLAGS) -m386
endif
#
# if you want the ram-disk device, define this to be the
# size in blocks.
#
#如果你想使用RAM盘(RAMDISK)#设备的话就定义块的大小。这里默认RAMDISK盘没有定义
#否则gcc编译时会带有选项-DRAMDISK=512
#RAMDISK = -DRAMDISK=512
#RAMDISK = -DRAMDISK=2048
#可以看到既有8086的汇编编译器、连接器也有gnu的汇编器、连接器,说明linux1.0源码中存在两种汇编语言
AS86 =as86 -0 -a #8086汇编编译器和连接器
LD86 =ld86 -0 #-o #生成8086的16位目标程序,-a#生成与gas和gld部分兼容的代码。
AS =as #AS LD HOSTCC#三个变量分别为gnu的汇编器、连接器、C语言编译器
LD =ld
HOSTCC =gcc
CC =gcc -D__KERNEL__
MAKE =make
CPP =$(CC) -E #-E 预处理,预处理之后的代码将送往标准输出
AR =ar #函数库打包程序,可创建静态库.a文档
STRIP =strip #去掉可执行文件的调试信息
#kernel目录、mm目录、fs目录、net目录、ipc目录所产生的目标代码文件。为了方便引用在这里将它们
#用ARCHIVES(#归档文件)#标识符表示。
ARCHIVES =kernel/kernel.o mm/mm.o fs/fs.o net/net.o ipc/ipc.o
FILESYSTEMS =fs/filesystems.a #文件系统库文件
#块和字符设备库文件。.a表示该文件是个归档文件,也即包含有许多可执行二进制代码子程序集合的库文件,
#通常是用GNU的ar程序生成。ar是gnu的二进制文件处理程序,用于创建、修改以及从归档文件中抽取文件。
DRIVERS =drivers/block/block.a \
drivers/char/char.a \
drivers/net/net.a \
ibcs/ibcs.o
LIBS =lib/lib.a #由lib/目录中的文件所编译生成的通用库文件。
SUBDIRS =kernel drivers mm fs net ipc ibcs lib
#定义内核头文件路径
#KERNELHDRS =/usr/src/linux/include
KERNELHDRS =/mnt/mywork/linux-1.0/include/
#gcc的环境变量C_INCLUDE_PATH(for C header files)和CPLUS_INCLUDE_PATH(for C++ header files)是指明头文件的搜索路径,
#此两个环境变量指明的头文件会在-I指定路径之后,系统默认路径之前进行搜索。
#LIBRARY_PATH指明库搜索路径,此环境变量指明路径会在-L指定路径之后,系统默认路径之前被搜索。
C_INCLUDE_PATH =/mnt/mywork/linux-1.0/include/
#如果用户选择了SCSI设备,则这里就会增加这部分定义
ifdef CONFIG_SCSI
DRIVERS := $(DRIVERS) drivers/scsi/scsi.a
endif
#是否配置了声卡驱动
ifdef CONFIG_SOUND
DRIVERS := $(DRIVERS) drivers/sound/sound.a
endif
#是否配置了数学协处理器
ifdef CONFIG_MATH_EMULATION
DRIVERS := $(DRIVERS) drivers/FPU-emu/math.a
endif
#到此为止所定义的变量都会被导出
#下面是make老式的隐式后缀规则,该行指示make利用下面的命令将所有的'.c'文件编译生成'.s'
#汇编程序。':'#表示下面是规则的命令。整句表示让gcc采用前面CFLAGS所指定的选项,在适当的编译后不进行汇编就停止(-S),
#从而产生与输入的各个C文件对应的汇编语言形式的代码文件。默认情况下所产生的汇编程序文件是原C文件名去掉'.c'后再
#加上'.s'后缀。'-o'表#示其后是输出文件的形式。'$*.s'(或'$@')是自动目标变量,'$<'#代表第一个先决条件,
#这里即是符合条件'*.c'的文件。
.c.s:
$(CC) $(CFLAGS) -S -o $*.s $<
#表示将所有.s汇编程序文件编译成.o目标文件。整句表示使用as编译器将汇编程序编译成.o目标文件。
.s.o:
$(AS) -o $*.o $<
#$(AS) -c -o $*.o $<
#类似上面*.c->*.o目标文件。整句表示使用gcc将C语言文件编译成.o目标文件但不链接。
#-c#表示只编译或汇编,但不进行连接操作。
.c.o:
$(CC) $(CFLAGS) -c -o $*.o $<
Version: dummy
rm -f tools/version.h
#make config执行此命令,进行内核配置,第一条命令展开为bash Configure < config.in生成.tmpconfig文件
#Configure是可执行脚本文件,config.in是脚本文件的输入文件
#第二条命令使用grep在.tmpconfig文件中搜索字符串CONFIG_SOUND,如果内核中配置有声卡,就进入驱动中sound目录进行
#声卡配置,最后将.tmpconfig 复制为文件.config
config:
$(CONFIG_SHELL) Configure $(OPTS) < config.in
@if grep -s '^CONFIG_SOUND' .tmpconfig ; then \
$(MAKE) -C drivers/sound config; \
else : ; fi
mv .tmpconfig .config
#命令展开就是set -e; for i in kernel drivers mm fs net ipc ibcs lib; do make -C $i; done
#就是进入每个子目录中进行编译
#"set -e"#表示之后出现的代码,一旦出现了返回值非零即有错误,整个脚本就会立即退出。
#
linuxsubdirs: dummy
set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i; done
tools/./version.h: tools/version.h
#version.h文件的先决条件是dummy和Makefile,dummy的先决条件是存在.config和.depend文件
#第一条命令使用脚本makever.sh生成隐藏文件.version,里面是一个累计数字,然后使用echo命令生成版本文件。
tools/version.h: $(CONFIGURE) Makefile
@./makever.sh
@echo \#define UTS_RELEASE \"$(VERSION).$(PATCHLEVEL)$(ALPHA)\" > tools/version.h
@echo \#define UTS_VERSION \"\#`cat .version` `date`\" >> tools/version.h
@echo \#define LINUX_COMPILE_TIME \"`date +%T`\" >> tools/version.h
@echo \#define LINUX_COMPILE_BY \"`whoami`\" >> tools/version.h
@echo \#define LINUX_COMPILE_HOST \"`hostname`\" >> tools/version.h
@echo \#define LINUX_COMPILE_DOMAIN \"`domainname`\" >> tools/version.h
tools/build: tools/build.c $(CONFIGURE) #由tools目录下的build.c程序生成执行程序build
$(HOSTCC) $(CFLAGS) -o $@ $<
boot/head.o: $(CONFIGURE) boot/head.s #利用上面的.s.o规则生成head.o目标文件
#-traditional尝试支持传统C编译器的某些方面。详见GNU C手册。
boot/head.s: boot/head.S $(CONFIGURE) include/linux/tasks.h
$(CPP) -traditional $< -o $@
tools/version.o: tools/version.c tools/version.h
init/main.o: $(CONFIGURE) init/main.c
$(CC) $(CFLAGS) $(PROFILING) -c -o $*.o $<
#tools目录中的system要由冒号右边所列的元素生成。后续是生成system的命令。
#-Ttext ADDRESS 指定链接生成的文件中代码段存放的内存位置。
#nm tools/zSystem会在标准输出设备(通常是屏幕)上打印出链接印象(link map)信息,即是指由链接程序ld产生的
#目标程序zSystem内存地址映像信息,其中列出了程序段装入到内存中的位置信息,然后通过管道传给grep处理。
#grep pattern [file...] 在文件中搜索所有 pattern 出现的位置, pattern 既可以是要搜索的字符串,也可以是一个正则表达式。
#grep -v '\(compiled\)\|\(\.o$$\)\|\( a \)'即是grep -v '(compiled)|(.o$$)|( a )',-v表示不显示所有匹配的行。
#shell中()中的命令作为一个子shell来执行,这句就表示过滤掉所有包含有内容compiled、.o$$、a(a前后有一个空格)的行。
#sort > System.map表示将grep处理后的内容排序后重定向到System.map文件
#System.map中保存的是内核符号表信息,内核符号表是所有内核符号及其对应地址的一个列表。当内核运行出错时,通过
#System.map文件中的符号表解析,就可以查到一个地址值对应的变量名,或反之。
#通过比较System.map文件和nm tools/zSystem > tmp文件可以进一步的理解grep命令的执行。
tools/system: boot/head.o init/main.o tools/version.o linuxsubdirs
$(LD) $(LDFLAGS) -Ttext 1000 boot/head.o init/main.o tools/version.o \
$(ARCHIVES) \
$(FILESYSTEMS) \
$(DRIVERS) \
$(LIBS) \
-o tools/system
nm tools/zSystem | grep -v '\(compiled\)\|\(\.o$$\)\|\( a \)' | \
sort > System.map
#下面两条使用8086的连接器和汇编器,也即boot/setup.S使用的是8086汇编语言编写
boot/setup: boot/setup.o
$(LD86) -s -o $@ $<
boot/setup.o: boot/setup.s
$(AS86) -o $@ $<
#-traditional尝试支持传统C编译器的某些方面。详见GNU C手册。
boot/setup.s: boot/setup.S $(CONFIGURE) include/linux/config.h Makefile
$(CPP) -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@
##下面两条使用8086的连接器和汇编器,也即boot/bootsect.S使用的是8086汇编语言编写
boot/bootsect: boot/bootsect.o
$(LD86) -s -o $@ $< #-s选项表示要去除目标文件中的符号信息
boot/bootsect.o: boot/bootsect.s
$(AS86) -o $@ $<
boot/bootsect.s: boot/bootsect.S $(CONFIGURE) include/linux/config.h Makefile
$(CPP) -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@
zBoot/zSystem: zBoot/*.c zBoot/*.S tools/zSystem
$(MAKE) -C zBoot
#sync强制写入所有需要更新的 buffer 上的数据到硬盘上
zImage: $(CONFIGURE) boot/bootsect boot/setup zBoot/zSystem tools/build
tools/build boot/bootsect boot/setup zBoot/zSystem $(ROOT_DEV) > zImage
sync
zdisk: zImage
dd bs=8192 if=zImage of=/dev/fd0
zlilo: $(CONFIGURE) zImage
if [ -f /vmlinuz ]; then mv /vmlinuz /vmlinuz.old; fi
if [ -f /zSystem.map ]; then mv /zSystem.map /zSystem.old; fi
cat zImage > /vmlinuz
cp zSystem.map /
if [ -x /sbin/lilo ]; then /sbin/lilo; else /etc/lilo/install; fi
#表示tools/zSystem文件要由冒号右边所列的元素生成。紧接的是生成zSystem的命令。
#最后的> zSystem.map表示需要将zSystem经过处理后重定向到zSystem.map文件中。
tools/zSystem: boot/head.o init/main.o tools/version.o linuxsubdirs
$(LD) $(LDFLAGS) -e startup_32 -Ttext 100000 boot/head.o init/main.o tools/version.o \
$(ARCHIVES) \
$(FILESYSTEMS) \
$(DRIVERS) \
$(LIBS) \
-o tools/zSystem
nm tools/zSystem | grep -v '\(compiled\)\|\(\.o$$\)\|\( a \)' | \
sort > zSystem.map
#通过在命令行中指定变量SUBDIRS=fs的值只对莫个模块进行编译,fs的命令行展开就是:
#[root@localhost linux_1.0_study]# make fs
#make linuxsubdirs SUBDIRS=fs
#make[1]: Entering directory `/mnt/mywork/linux_1.0_study'
#set -e; for i in fs; do make -C $i; done
#make[2]: Entering directory `/mnt/mywork/linux_1.0_study/fs'
#......................
#make[2]: Leaving directory `/mnt/mywork/linux_1.0_study/fs'
#make[1]: Leaving directory `/mnt/mywork/linux_1.0_study'
#可以清楚的看到只进入fs目录进行编译,后面的lib、mm、ipc、kernel、drivers、net同理
fs: dummy
$(MAKE) linuxsubdirs SUBDIRS=fs
lib: dummy
$(MAKE) linuxsubdirs SUBDIRS=lib
mm: dummy
$(MAKE) linuxsubdirs SUBDIRS=mm
ipc: dummy
$(MAKE) linuxsubdirs SUBDIRS=ipc
kernel: dummy
$(MAKE) linuxsubdirs SUBDIRS=kernel
drivers: dummy
$(MAKE) linuxsubdirs SUBDIRS=drivers
net: dummy
$(MAKE) linuxsubdirs SUBDIRS=net
clean:
rm -f kernel/ksyms.lst
rm -f core `find . -name '*.[oas]' -print`
rm -f core `find . -name 'core' -print`
rm -f zImage zSystem.map tools/zSystem tools/system
rm -f Image System.map boot/bootsect boot/setup
rm -f zBoot/zSystem zBoot/xtract zBoot/piggyback
rm -f .tmp* drivers/sound/configure
rm -f init/*.o tools/build boot/*.o tools/*.o
mrproper: clean
rm -f include/linux/autoconf.h tools/version.h
rm -f drivers/sound/local.h
rm -f .version .config* config.old
rm -f .depend `find . -name .depend -print`
distclean: mrproper
backup: mrproper
cd .. && tar cf - linux | gzip -9 > backup.gz
sync
#该目标或规则用于产生各文件之间的依赖关系。创建这些依赖关系是为了让make命令用他们来确定是否需要重建一个
#目标对象。比如当某个头文件被改动过后,make就能通过生成的依赖关系,重新编译与该头文件有关的所有*.c文件。
#touch用来修改文件时间戳,或者新建一个不存在的文件
#shell遇到”>”操作符,会判断右边文件是否存在,如果存在就先删除,并且创建新文件。不存在直接创建。 无论左边命令执行是否成功,右边文件都会变为空。
#“>>”操作符,判断右边文件,如果不存在,先创建。以添加方式打开文件,会分配一个文件描述符[不特别指定,默认为1,2]然后,与左边的标准输出(1)或错误输出(2) 绑定。
#第一个for语句对指定目录下(init/)的每一个c文件执行gcc预处理操作,标志-M告诉gcc预处理器输出描述每个目标文件
#相关性的规则,并且这些规则符合make语法。对于每一个源文件,预处理程序会输出一个规则,其结果形式就是相应
#源程序文件的目标文件名加上其依赖关系,即该源文件中包含的所有头文件列表。echo用于加上路径名。将预处理的结果
#添加到临时文件.tmpdepend中。
#同理,第二个for语句也是将tools/目录下的每一个c文件执行gcc预处理操作,结果添加到 .tmpdepend文件的后面。
#第四条命令是进入每一个子目录中,对每一个源文件进行gcc预处理操作,并在相应的目录下面生成.depend依赖文件。
#mv .tmpdepend .depend就是产生顶层目录下的依赖文件。
depend dep:
touch tools/version.h
for i in init/*.c;do echo -n "init/";$(CPP) -M $$i;done > .tmpdepend
for i in tools/*.c;do echo -n "tools/";$(CPP) -M $$i;done >> .tmpdepend
set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i dep; done
rm -f tools/version.h
mv .tmpdepend .depend
#如果不存在.config或者.depend文件,CONFIGURATION变量就会赋值为config或者depend,在这里就会有错误提示
#
ifdef CONFIGURATION
..$(CONFIGURATION):
@echo
@echo "You have a bad or nonexistent" .$(CONFIGURATION) ": running 'make" $(CONFIGURATION)"'"
@echo
$(MAKE) $(CONFIGURATION)
@echo
@echo "Successful. Try re-making (ignore the error that follows)"
@echo
exit 1
dummy: ..$(CONFIGURATION) #..$(CONFIGURATION)就表示隐藏文件.config或者.depend
else
dummy:
endif
#
# Leave these dummy entries for now to tell people that they are going away..
#
lilo:
@echo
@echo Uncompressed kernel images no longer supported. Use
@echo \"make zlilo\" instead.
@echo
@exit 1
Image:
@echo
@echo Uncompressed kernel images no longer supported. Use
@echo \"make zImage\" instead.
@echo
@exit 1
disk:
@echo
@echo Uncompressed kernel images no longer supported. Use
@echo \"make zdisk\" instead.
@echo
@exit 1