转载请标注:
全面剖析《自己动手写操作系统》第五章---makefile文件
http://blog.csdn.net/zgh1988/article/details/7338380
1、make概述
2、编译和链接
3、makefile规则
4、自己动手写makefile文件
5、后记
作为程序员,我们都有使用过Visualstdio 2005、VC6.0、eclipse等经典开发工具,使用它们,我们可以方便地建立和管理自己的工程,往往直接点击两个按钮,就完成了编译,链接,运行等功能。但是我们有没有过这样的疑问?它是怎样做到的,它是怎样将不在一起的文件组合到一起的,我们自己能不能管理自己的工程呢。是的,在Linux环境下使用GNU的make工具就可以构建和管理自己的工程,而且还可以自己操作它们的编译、链接等。
在了解make工具之前,我们需要知道makefile文件,该文件就是用来描述整个工程的编译、链接等规则,其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何最后产生我们想要得到的可执行文件。我们的任务就是要掌握如何书写makefile文件。makefile文件有自己的书写格式、关键字、函数,而且可以使用系统shell所提供的任何命令(例如:nasm gcc等指令)。
make是一个命令工具,它解释makefile文件中的指令。在书写好makefile文件之后,我们只需要在shell提示符下输入make命令,整个工程就会按照makefile文件中的内容完全自动编译,极大的提高了效率。
传统意义上,在生成系统可执行文件之前,需要进行两步走战略:编译和链接。
编译(compile):指用编译器(compiler)将源代码(sourcecode)生成二进制目标文件(object file),在Windows下也就是 .obj 文件,Linux下是 .o 文件。编译时,编译器需要检查高级语言的语法,函数与变量的声明是否正确。只有所有语法正确,函数与变量声明正确,编译器就可以编译出二进制目标文件。为了和最终生成的可执行目标文件区别,我们暂且称编译后的目标文件为.o文件。
链接(link):找到所要用到函数所在的目标文件(.o文件),并把它们链接在一起合成为可执行文件(executable file),在Linux环境下,可执行文件的格式为“ELF”格式。链接时,要确保编译器能找到所有被用到了的函数所在的目标文件。链接过程中使用GNU的“ld”工具。
一个简单的makefile规则:
TARGET :PREREQUISITES
COMMAND
从这个形式上来看,我们可以得到两层含义:
(1) 要想得到TARGET,需要执行命令COMMAND。
(2) TARGET依赖于PREREQUISITES,当PREREQUISITES中至少有一个文件比TARGET文件新时,COMMAND才被执行。
TARGET :规则的目标。通常是最终可以在系统上运行的目标文件 或 实现这个目标文件所需要的中间过程文件。例如.o文件 或者 最后的可执行文件。另外,目标也可以是一个make执行的动作名称,如目标“clean”,不过我们称这样的目标为“伪目标”。
PREREQUISITES : 规则的依赖。生成规则目标所需要的文件列表,通常一个目标文件依赖于一个或者多个文件。
COMMAND : 规则的命令行。是规则所要执行的动作。
(语法1)一个规则可以有多个命令行,每一条命令行占一行。注意:每一个命令行必须以【TAB】字符开始,【TAB】字符告诉make此行是一个命令行。
目标:生成boot.bin和loader.bin文件
条件:boot.asm 包含 load.inc fat12hdr.inc
loader.asm 包含 load.incfat12hdr.inc pm.inc
下面我们开始写makefile文件:
(1) 首先我们要写出生成boot.bin 的规则:
boot.bin : boot.asm load.inc fat12hdr.inc
nasm–o boot.bin boot.asm
另外我们写出生成loader.bin的规则:
loader.bin : loader.asm load.incfat12hdr.inc pm.inc
nasm–o loader.bin loader.asm
我们将这两条规则保存到makefile文件中,将makefile文件放在boot文件下。
此时,如果我们只输入命令行make,发现它只是执行了第一条规则,也就是只生成了boot.bin目标文件。(语法2)因为在默认情况下,make执行的是makefile中的第一个规则。如果想执行第二条规则,我们必须执行make loader.bin,才能够达到目的。
(2) 问题又来了,我们如何通过一条命令把两个规则都执行了呢?我在文件开始处,添加了这样的一条规则,作为该makefile的第一条规则。
everything : boot.bin loader.bin
这个规则虽然命令行为空,也就是不做任何工作,但是makefile中有这样一条规则,(语法3)make在执行一条规则所定义的命令之前,首先需要处理的是目标文件的所有的依赖文件,在此规则中,也就是boot.bin 和 loader.bin。如果依赖文件不存在,则make会按照依赖文件列表从左到右的顺序依次寻找创建这些依赖文件的规则。
所以在本例中,在执行everything: boot.bin loader.bin这条规则之前,make发现其依赖文件boot.bin 和loader.bin文件不存在,就会去寻找创建这些依赖条件的规则,即规则2,和规则3,分别执行规则2,3,完成boot.bin loader.bin的创建。
(3) 此时我们在输入命令make,会发现一个错误:make:`everything' is up to date。这是为什么呢?难道我们写的规则出现问题了吗?
我们写的makefile文件是正确的,下面我来告诉你makefile的另一条规则(语法4)在执行一条规则中,如果目标文件不存在,则执行该规则以创建目标文件;如果目标文件存在,其依赖文件中有一个或者多个文件比它“更新”—也就是说依赖文件中有一个或者多个文件在目标文件生成之后,进行了修改,则根据规则重新创建目标文件;如果目标文件存在,它比它的任何一个依赖文件都要“更新”—也就是说在生成目标文件之后,所有的依赖文件都没有发生修改,则什么都不做。
所以此时执行make命令会发生错误。因为之前我们已经生成了boot.bin 和 loader.bin文件,而且其依赖文件列表没有更新。
此时怎么办呢?让我们在写一条规则放在最后一行:
clean :
rm–f boot.bin loader.bin
它不存在依赖条件,它是一条伪规则,命令行的作用是删除文件boot.bin loader.bin。我们通常使用rm指令,来删除一些中间生成文件(例如.o文件)。
(4) 最终的makefile文件如下:
everything : boot.bin loader.bin
boot.bin : boot.asm load.inc fat12hdr.inc
nasm-o boot.bin boot.asm
loader.bin : loader.asm load.incfat12hdr.inc pm.inc
nasm-o loader.bin loader.asm
clean:
rm-f boot.bin loader.bin
现在我们就可以先执行一个make clean,将目标文件boot.bin loader.bin删除,然后再执行make命令。执行结果如下:
这样我们就完成了我们第一个最简单的例子。
下面让我们来回忆上面所提到的4条规则:
(语法1)一个规则可以有多个命令行,每一条命令行占一行。注意:每一个命令行必须以[TAB]字符开始,【TAB】字符告诉make此行是一个命令行。
(语法2)因为在默认情况下,make执行的是makefile中的第一个规则。
(语法3)make在执行一条规则所定义的命令之前,首先需要处理的是目标文件的所有的依赖文件,如果依赖文件不存在,则make会按照依赖文件列表从左到右的顺序依次寻找创建这些依赖文件的规则。
(语法4)在执行一条规则中,如果目标文件不存在,则执行该规则以创建目标文件;如果目标文件存在,其依赖文件中有一个或者多个文件比它“更新”—也就是说依赖文件中有一个或者多个文件在目标文件生成之后,进行了修改,则根据规则重新创建目标文件;如果目标文件存在,它比它的任何一个依赖文件都要“更新”—也就是说在生成目标文件之后,所有的依赖文件都没有发生修改,则什么都不做。
此时,为了让整个工程规范,我将头文件load.inc fat12hdr.inc pm.inc放在包含在include下的文件夹里面。此时的makefile文件还是在boot文件夹目录下,此时的makefile该怎么写呢?如果我们不加修改的话,运行make指令肯定会出问题。原因是无法找到依赖文件列表中的load.inc fat12hdr.inc pm.inc文件,因为它们的路径不在/boot下,而是搬到了boot/include下。所以修改makefile文件如下:
everything : boot.bin loader.bin
boot.bin : boot.asm include/load.incinclude/fat12hdr.inc
nasm-I include -o boot.bin boot.asm
loader.bin : loader.asm include/load.incinclude/fat12hdr.inc include/pm.inc
nasm-I include -o loader.bin loader.asm
clean:
rm-f boot.bin loader.bin
我们相当于添加了一个路径来帮助make自己寻找这些头文件。在nasm指令中,-I 指的就是路径。
下面是运行结果:
这时候,你有没有想过,如果我的工程不断的扩大,到时候我这样一点点的写是不是很麻烦,还会出好多问题。
其实我还有一点没有告诉你呢,在makefile中,是可以添加变量的。所以我们就要将一些内容赋值给变量,在以后的道路上,我们就可以修改变量了。
在此问题上,我就要添加变量来简化makefile文件,使其更加清晰明了。
# Makefile for boot
# Programs, flags,etc.
ASM = nasm
ASMFLAGS = -I include
# This Program
TARGET = boot.bin loader.bin
# All Phony Targets
.PHONY : everything clean all
# Default starting position
everything : $(TARGET)
clean :
rm -f $(TARGET)
all : clean everything
boot.bin : boot.asminclude/load.inc include/fat12hdr.inc
$(ASM) $(ASMFLAGS) -o $@ $<
loader.bin :loader.asm include/load.inc include/fat12hdr.inc include/pm.inc
$(ASM) $(ASMFLAGS) -o $@ $<
首先,我们看到的是两个命令行变量,ASM = nasm ASMFLAGS = -Iinclude
在今后我们使用到nasm 和–I include 时,我们就只需要用$(ASM), $(ASMFLAGS)来代替,$这个符号的含义,就是取值操作。
其次是TARGET = boot.bin loader.bin,我们定义了一个目标变量,它的值保护boot.bin和loader.bin。所以在
everything :$(TARGET) 相当于everything : boot.bin loader.bin。
然后你注意到了我们将everything clean all定义为了.PHONY,我们知道everything clean all只是动作,它是伪规则,所以我们用.PHONY来定义它们。
最后是两个规则,一个是生成boot.bin,一个是loader.bin。
$(ASM) $(ASMFLAGS) –o $@ $< 相当于:
nasm -I include -o loader.bin loader.asm
S@ = loader.bin $< = loader.asm
这时候我们发现,我们的第二个规则有点长,所以我们需要将其变短一些。(语法5)这时候我们需要使用反斜线(\)将一个较长的行分成多行,这样我们的makefile书写清晰、容易阅读理解。但需要注意:反斜线之后不能有空格(这也是大家最容易犯的错误,错误比较隐蔽)
loader.bin : loader.asm include/load.incinclude/fat12hdr.inc \
include/pm.inc
$(ASM)$(ASMFLAGS) -o $@ $<
boot/boot.asm : boot/boot.asm boot/include/load.inc boot/include/fat12hdr.inc boot/loader.asm : boot/loader.asm boot/include/load.inc boot/include/fat12hdr.inc \ boot/include/pm.inc kernel/kernel.o : kernel/kernel.asm kernel/start.o : kernel/start.c include/type.h include/const.h include/protect.h \ include/proto.h include/string.h include/global.h kernel/i8259.o : kernel/i8259.c include/type.h include/const.h include/protect.h \ include/proto.h kernel/global.o : kernel/global.c include/type.h include/const.h include/protect.h \ include/proto.h include/global.h kernel/protect.o : kernel/protect.c include/type.h include/const.h include/protect.h \ include/global.h lib/klib.o : lib/klib.asm lib/string.o : lib/string.asm lib/klibc.o : lib/klib.c include/type.h include/const.h include/protect.h include/string.h \ include/proto.h include/global.h kernel.bin : kernel/kernel.o kernel/start.o kernel/i8259.o kernel/global.o kernel/protect.o \ lib/klib.o lib/string.o lib/klibc.o
(2) 根据上面的目标文件,我们需要定义目标变量,此时不要求完整,随着思考一步步的去完善。
TINIXBOOT = boot/boot.bin boot/loader.bin
TINIXKERNEL = kernel.bin
OBJS = kernel/kernel.o kernel/start.okernel/i8259.o kernel/global.o kernel/protect.o lib/klib.o lib/string.olib/klibc.o
DASMOUTPUT= kernel.bin.asm
(3) 我们可以需要取定义一些命令变量,此时不要求完整,随着思考一步步的去完善。ASM =nasm
DASM =ndisasm
CC =gcc
LD =ld
ASMBFLAGS =-I boot/include
ASMKFLAGS =-I include -f elf
ASMKLFLAGS =-f elf
CFLAGS =-I include -c -fno-builtin
LDFLAGS =-s -Ttext $(ENTRYPOINT)
DASMFLAGS =-u -o $(ENTRYPOINT) -e $(ENTRYOFFSET)
(4) 然后我们开始尝试组织这些变量,生成各种规则。
boot/boot.bin : boot/boot.asm boot/include/load.inc boot/include/fat12hdr.inc $(ASM) $(ASMBFLAGS) -o $@ $< boot/loader.bin : boot/loader.asm boot/include/load.inc boot/include/fat12hdr.inc \ boot/include/pm.inc $(ASM) $(ASMBFLAGS) -o $@ $< $(TINIXKERNEL) : $(OBJS) $(LD) $(LDFLAGS) -o $(TINIXKERNEL) $(OBJS) kernel/kernel.o : kernel/kernel.asm $(ASM) $(ASMKFLAGS) -o $@ $< kernel/start.o : kernel/start.c include/type.h include/const.h include/protect.h \ include/string.h include/proto.h $(CC) $(CFLAGS) -o $@ $< kernel/i8259.o : kernel/i8259.c include/type.h include/const.h include/protect.h \ include/proto.h $(CC) $(CFLAGS) -o $@ $< kernel/global.o : kernel/global.c include/type.h include/const.h include/protect.h \ include/proto.h include/global.h $(CC) $(CFLAGS) -o $@ $< kernel/protect.o : kernel/protect.c include/type.h include/const.h include/protect.h \ include/global.h $(CC) $(CFLAGS) -o $@ $< lib/klib.o : lib/klib.asm $(ASM) $(ASMKLFLAGS) -o $@ $< lib/string.o : lib/string.asm $(ASM) $(ASMKLFLAGS) -o $@ $< lib/klibc.o : lib/klib.c include/type.h include/const.h include/protect.h include/string.h \ include/proto.h include/global.h $(CC) $(CFLAGS) -o $@ $<
(5) 最后需要完成一些不同的伪规则,也就是行为,将其中的某些行为进行组合。
# All Phony Targets .PHONY : everything final image cleanrealclean disasm all buildimg # Default starting position everything : $(TINIXBOOT) $(TINIXKERNEL) all : realclean everything final : all clean image : final buildimg clean : rm -f $(OBJS) realclean: rm -f $(OBJS) $(TINIXBOOT) $(TINIXKERNEL) disasm : $(DASM) $(DASMFLAGS) $(TINIXKERNEL) > $(DASMOUTPUT) # Write "boot.bin" &"loader.bin" into floppy image "TINIX.IMG" # We assume that "TINIX.IMG"exists in current folder buildimg: mount Tinix.vfd /mnt/floppy -o loop cp -f boot/loader.bin /mnt/floppy cp -f kernel.bin /mnt/floppy umount /mnt/floppy
make+不同的伪规则形成不同的结果:
make everything: nasm -I boot/include -o boot/boot.binboot/boot.asm nasm -I boot/include -o boot/loader.binboot/loader.asm nasm -I include -f elf -o kernel/kernel.o kernel/kernel.asm gcc -I include -c -fno-builtin -okernel/start.o kernel/start.c nasm -f elf -o lib/klib.o lib/klib.asm nasm -f elf -o lib/string.o lib/string.asm ld -s -Ttext 0x30400 -o kernel.binkernel/kernel.o kernel/start.o lib/klib.o lib/string.o make clean: rm -f kernel/kernel.o kernel/start.o lib/klib.o lib/string.o make realclean: rm -f kernel/kernel.o kernel/start.o lib/klib.o lib/string.o boot/boot.bin boot/loader.bin kernel.bin make buildimg: mount Tinix.vfd /mnt/floppy -o loop cp -f boot/loader.bin /mnt/floppy cp -f kernel.bin /mnt/floppy umount /mnt/floppy make all: make realclean make everything make final: make all (make realclean make everything) make clean make image: make final (make all(make realclean makeeverything)) make buildimg
(6) 最后的结果如下:
# Makefile for TINIX # Entry Point of Tinix # It must be as same as 'KernelEntryPointPhyAddr' in load.inc!!! ENTRYPOINT = 0x30400 # Offset of entry point in kernel file # It depends on ENTRYPOINT ENTRYOFFSET = 0x400 # Programs, flags, etc. ASM = nasm DASM = ndisasm CC = gcc LD = ld ASMBFLAGS = -I boot/include ASMKFLAGS = -I include -f elf ASMKLFLAGS = -f elf CFLAGS = -I include -c -fno-builtin LDFLAGS = -s -Ttext $(ENTRYPOINT) DASMFLAGS = -u -o $(ENTRYPOINT) -e $(ENTRYOFFSET) # This Program TINIXBOOT = boot/boot.bin boot/loader.bin TINIXKERNEL = kernel.bin OBJS = kernel/kernel.o kernel/start.o kernel/i8259.o kernel/global.o kernel/protect.o lib/klib.o lib/string.o lib/klibc.o DASMOUTPUT = kernel.bin.asm # All Phony Targets .PHONY : everything final image clean realclean disasm all buildimg # Default starting position everything : $(TINIXBOOT) $(TINIXKERNEL) all : realclean everything final : all clean image : final buildimg clean : rm -f $(OBJS) realclean: rm -f $(OBJS) $(TINIXBOOT) $(TINIXKERNEL) disasm : $(DASM) $(DASMFLAGS) $(TINIXKERNEL) > $(DASMOUTPUT) # Write "boot.bin" & "loader.bin" into floppy image "TINIX.IMG" # We assume that "TINIX.IMG" exists in current folder buildimg: mount Tinix.vfd /mnt/floppy -o loop cp -f boot/loader.bin /mnt/floppy cp -f kernel.bin /mnt/floppy umount /mnt/floppy boot/boot.bin : boot/boot.asm boot/include/load.inc boot/include/fat12hdr.inc $(ASM) $(ASMBFLAGS) -o $@ $< boot/loader.bin : boot/loader.asm boot/include/load.inc boot/include/fat12hdr.inc \ boot/include/pm.inc $(ASM) $(ASMBFLAGS) -o $@ $< $(TINIXKERNEL) : $(OBJS) $(LD) $(LDFLAGS) -o $(TINIXKERNEL) $(OBJS) kernel/kernel.o : kernel/kernel.asm $(ASM) $(ASMKFLAGS) -o $@ $< kernel/start.o : kernel/start.c include/type.h include/const.h include/protect.h \ include/string.h include/proto.h $(CC) $(CFLAGS) -o $@ $< kernel/i8259.o : kernel/i8259.c include/type.h include/const.h include/protect.h \ include/proto.h $(CC) $(CFLAGS) -o $@ $< kernel/global.o : kernel/global.c include/type.h include/const.h include/protect.h \ include/proto.h include/global.h $(CC) $(CFLAGS) -o $@ $< kernel/protect.o : kernel/protect.c include/type.h include/const.h include/protect.h \ include/global.h $(CC) $(CFLAGS) -o $@ $< lib/klib.o : lib/klib.asm $(ASM) $(ASMKLFLAGS) -o $@ $< lib/string.o : lib/string.asm $(ASM) $(ASMKLFLAGS) -o $@ $< lib/klibc.o : lib/klib.c include/type.h include/const.h include/protect.h include/string.h \ include/proto.h include/global.h $(CC) $(CFLAGS) -o $@ $<
本着够用原则,只学习了这么一点关于makefile的知识,希望在以后的时间里,能够对makefile进行深入研究。
全面剖析《自己动手写操作系统》第五章---加载内核kernel.bin http://blog.csdn.net/zgh1988/article/details/7329941
全面剖析《自己动手写操作系统》第五章---Red Hat 9.0 的安装过程 http://blog.csdn.net/zgh1988/article/details/7315676
全面剖析《自己动手写操作系统》第四章---FAT12文件系统 http://blog.csdn.net/zgh1988/article/details/7284834
全面剖析《自己动手写操作系统》第四章---加载Loader.bin http://blog.csdn.net/zgh1988/article/details/7291909
全面剖析《自己动手写操作系统》第三章---进入保护模式 http://blog.csdn.net/zgh1988/article/details/7098981
全面剖析《自己动手写操作系统》第三章---“实模式--保护模式--实模式” http://blog.csdn.net/zgh1988/article/details/7255804
全面剖析《自己动手写操作系统》第三章---堆栈段的工作方式 http://blog.csdn.net/zgh1988/article/details/7256254
全面剖析《自己动手写操作系统》第三章---特权级以及不同特权级代码段之间的跳转 http://blog.csdn.net/zgh1988/article/details/7262901
全面剖析《自己动手写操作系统》第三章---分页机制 http://blog.csdn.net/zgh1988/article/details/7270748
全面剖析《自己动手写操作系统》第三章---中断机制 http://blog.csdn.net/zgh1988/article/details/7276259
全面剖析《自己动手写操作系统》第二章http://blog.csdn.net/zgh1988/article/details/7062065
全面剖析《自己动手写操作系统》第一章http://blog.csdn.net/zgh1988/article/details/7060032
《自己动手写操作系统》读后感http://blog.csdn.net/zgh1988/article/details/7059936