pintos项目的makefile解析(一)
2017-09-14 13:41
218 查看
pintos项目是在大二下的时候学习操作系统时的实验课项目,由于当时学业压力大,根本没时间去仔细地了解Linux。
因此在这个时候重新来学习,先从这个makefile来学习,因为Makefile是linux下大型项目的一个编译工具,希望自己具备这样的能力。
那么先回忆一下,在Pintos的测试中是进入路径
pintos/src/threads/build
下输入make 命令,然后再输入 make check 命令来测试,输入 make clean 来清空之前编译的结果的。
那么这个目录下的Makefile应该是一个总的Makefile了,看看这个Makefile里面的内容,一点一点来解析吧!
然后看到 include 了一个文件,去看看 pintos/src/Make.config
那么对3个这Makefile的文件的解析我都写在这里了:
首先是../../Make.config:
接下来是
../Make.vars:
../../tests/Make.tests:
一,主要是对shell命令不够熟悉
二,是对编译,链接的知识不够了解,导致很多编译上的东西也没法看懂
先将这次简单学习的过程记录下来,如果以后有机会接触到大型项目的Makefile再回过头看看这篇文章,要知道自己曾经接触并且了解过Makefile!
因此在这个时候重新来学习,先从这个makefile来学习,因为Makefile是linux下大型项目的一个编译工具,希望自己具备这样的能力。
那么先回忆一下,在Pintos的测试中是进入路径
pintos/src/threads/build
下输入make 命令,然后再输入 make check 命令来测试,输入 make clean 来清空之前编译的结果的。
那么这个目录下的Makefile应该是一个总的Makefile了,看看这个Makefile里面的内容,一点一点来解析吧!
# -*- makefile -*- SRCDIR = ../.. #所有存放源文件的目录,也就是 pintos/src/ all: kernel.bin loader.bin #这个all是标签,Makefile中的第一个目标会被作为其默认目标,由于伪目标的特性是,总是被执行的, #所以其依赖的 kernel.bin loader.bin 就总是不如“all”这个目标新。所以,这2个二进制文件总会更新 include ../../Make.config include ../Make.vars include ../../tests/Make.tests这个是Makefile的前面几行,all是个标签,输入命令 make all 的话就会运行 kernel.bin loader.bin 这2个程序
然后看到 include 了一个文件,去看看 pintos/src/Make.config
那么对3个这Makefile的文件的解析我都写在这里了:
首先是../../Make.config:
# -*- makefile -*- SHELL = /bin/sh #指定使用的shell VPATH = $(SRCDIR) #特殊变量 VPATH 指定文件的查找路径 # Binary utilities. # If the host appears to be x86, use the normal tools. # If it's x86-64, use the compiler and linker in 32-bit mode. # Otherwise assume cross-tools are installed as i386-elf-*. X86 = i.86\|pentium.*\|[pk][56]\|nexgen\|viac3\|6x86\|athlon.*\|i86pc # .*是模糊匹配,\|是管道的转义 X86_64 = x86_64 # Pintos doesn't compile/run properly using gcc 4.3+. Force 4.1 for now. # 这句话的意思大概是 必须是4.1版本的gcc 才可以正常编译 CCPROG = /usr/class/cs140/x86_64/bin/i586-elf-gcc ifeq ($(strip $(shell command -v $(CCPROG) 2> /dev/null)),) #strip函数:功能:去掉<string>字串中开头和结尾的空字符。返回被去掉空格的字符串值。 #如果strip这个函数返回为空的话 #strip是函数名, 函数参数是 $(shell command -v $(CCPROG) 2> /dev/null) ,单独的一个变量 #参考博客: http://www.cnblogs.com/bethal/p/5430229.html # 这个函数会新生成一个Shell程序来执行命令 CCPROG = gcc endif ifneq (0, $(shell expr `uname -m` : '$(X86)')) CC = $(CCPROG) LD = ld OBJCOPY = objcopy else ifneq (0, $(shell expr `uname -m` : '$(X86_64)')) CC = $(CCPROG) -m32 LD = ld -melf_i386 OBJCOPY = objcopy else CC = i386-elf-gcc LD = i386-elf-ld OBJCOPY = i386-elf-objcopy endif endif ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),) $(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***) endif # Compiler and assembler invocation. DEFINES = WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers CFLAGS = -g -msoft-float -O CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/lib ASFLAGS = -Wa,--gstabs LDFLAGS = DEPS = -MMD -MF $(@:.o=.d) # Turn off -fstack-protector, which we don't support. ifeq ($(strip $(shell echo | $(CC) -fno-stack-protector -E - > /dev/null 2>&1; echo $$?)),0) CFLAGS += -fno-stack-protector endif # Turn off --build-id in the linker, which confuses the Pintos loader. ifeq ($(strip $(shell $(LD) --help | grep -q build-id; echo $$?)),0) LDFLAGS += -Wl,--build-id=none endif %.o: %.c #这是一个模式规则,目标中的"%"的值决定了依赖目标中的"%"的值 $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS) #-c参数表示只编译,不链接 #自动化变量$@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。 #自动化变量$< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 #后面的一些参数是一些编译选项,对我而言暂时比较复杂 %.o: %.S $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS) #同上
接下来是
../Make.vars:
# -*- makefile -*- kernel.bin: DEFINES = KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) TEST_SUBDIRS = tests/threads GRADING_FILE = $(SRCDIR)/tests/threads/Grading SIMULATOR = --bochs #以上都是定义一些路径变量而已再接下来是
../../tests/Make.tests:
# -*- makefile -*- include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) #$(patsubst <pattern>,<replacement>,<text>) ———— 模式字符串替换函数,将TEST_SUBDIRS中的变量 % 替换成 $(SRCDIR)/%/Make.tests PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES)) ##foreach语法是: $(foreach <var>,<list>,<text>) OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES)) ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES)) RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES)) #加后缀函数:$(addsuffix <suffix>,<names...>) 把后缀<suffix>加到<names>中的每个单词后面 ifdef PROGS include ../../Makefile.userprog endif TIMEOUT = 60 clean:: rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) #删除这些文件 grade:: results $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@ # "$<"表示了所有依赖目标的挨个值 这里也就是result了 # 管道和tee命令共同使用: 结果在终端打印stdout同时重定向到文件中 check:: results @cat $< @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \ FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \ if [ $$FAILURES = 0 ]; then \ echo "All $$COUNT tests passed."; \ else \ echo "$$FAILURES of $$COUNT tests failed."; \ exit 1; \ fi # fi表示if条件块的结束 # if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令, # 如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。 results: $(RESULTS) @for d in $(TESTS) $(EXTRA_GRADES); do \ if echo PASS | cmp -s $$d.result -; then \ echo "pass $$d"; \ else \ echo "FAIL $$d"; \ fi; \ done > $@ #这里重定向到了目标文件,即results这个文件 #do 和 done 使用——shell语法 #这个if条件里面的cmp用法令人有点费解 outputs:: $(OUTPUTS) $(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) $(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) $(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test))) #eval首先扫描命令行,进行所有的置换,然后再执行该命令,参考: http://blog.csdn.net/w_ww_w/article/details/7075867 # Prevent an environment variable VERBOSE from surprising us. VERBOSE = TESTCMD = pintos -v -k -T $(TIMEOUT) TESTCMD += $(SIMULATOR) TESTCMD += $(PINTOSOPTS) ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) #filter函数,只得到变量 KERNEL_SUBDIRS 中的userprog文件 TESTCMD += $(FILESYSSOURCE) TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) endif ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) TESTCMD += --swap-size=4 endif TESTCMD += -- -q TESTCMD += $(KERNELFLAGS) ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) TESTCMD += -f endif TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) TESTCMD += < /dev/null TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output %.output: kernel.bin loader.bin $(TESTCMD) %.result: %.ck %.output perl -I$(SRCDIR) $< $* $@最开始的我们执行的那个Makefile的文件加上注释是:
# -*- makefile -*- SRCDIR = ../.. #所有存放源文件的目录,也就是 pintos/src/ all: kernel.bin loader.bin #这个all是标签,Makefile中的第一个目标会被作为其默认目标,由于伪目标的特性是,总是被执行的, #所以其依赖的 kernel.bin loader.bin 就总是不如“all”这个目标新。所以,这2个二进制文件总会更新 include ../../Make.config include ../Make.vars include ../../tests/Make.tests从这里对实例Makefile的简单了解中可以大概清楚Makefile的书写规则,用法以及它给大型工程编译带来的编译,但是现在还不能看懂这些Makefile系列做了什么
# Compiler and assembler options.
kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel
# Core kernel.
threads_SRC = threads/start.S # Startup code.
threads_SRC += threads/init.c # Main program.
threads_SRC += threads/thread.c # Thread management core.
threads_SRC += threads/switch.S # Thread switch routine.
threads_SRC += threads/interrupt.c # Interrupt core.
threads_SRC += threads/intr-stubs.S # Interrupt stubs.
threads_SRC += threads/synch.c # Synchronization.
threads_SRC += threads/palloc.c # Page allocator.
threads_SRC += threads/malloc.c # Subpage allocator.
#.S文件应该是汇编语言文件
# Device driver code.
devices_SRC = devices/pit.c # Programmable interrupt timer chip.
devices_SRC += devices/timer.c # Periodic timer device.
devices_SRC += devices/kbd.c # Keyboard device.
devices_SRC += devices/vga.c # Video device.
devices_SRC += devices/serial.c # Serial port device.
devices_SRC += devices/block.c # Block device abstraction layer.
devices_SRC += devices/partition.c # Partition block device.
devices_SRC += devices/ide.c # IDE disk block device.
devices_SRC += devices/input.c # Serial and keyboard input.
devices_SRC += devices/intq.c # Interrupt queue.
devices_SRC += devices/rtc.c # Real-time clock.
devices_SRC += devices/shutdown.c # Reboot and power off.
devices_SRC += devices/speaker.c # PC speaker.
# Library code shared between kernel and user programs.
lib_SRC = lib/debug.c # Debug helpers.
lib_SRC += lib/random.c # Pseudo-random numbers.
lib_SRC += lib/stdio.c # I/O library.
lib_SRC += lib/stdlib.c # Utility functions.
lib_SRC += lib/string.c # String functions.
lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC.
lib_SRC += lib/ustar.c # Unix standard tar format utilities.
# Kernel-specific library code.
lib/kernel_SRC = lib/kernel/debug.c # Debug helpers.
lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists.
lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps.
lib/kernel_SRC += lib/kernel/hash.c # Hash tables.
lib/kernel_SRC += lib/kernel/console.c # printf(), putchar().
# User process code.
userprog_SRC = userprog/process.c # Process loading.
userprog_SRC += userprog/pagedir.c # Page directories.
userprog_SRC += userprog/exception.c # User exception handler.
userprog_SRC += userprog/syscall.c # System call handler.
userprog_SRC += userprog/gdt.c # GDT initialization.
userprog_SRC += userprog/tss.c # TSS management.
# No virtual memory code yet.
#vm_SRC = vm/file.c # Some file.
# Filesystem code.
filesys_SRC = filesys/filesys.c # Filesystem core.
filesys_SRC += filesys/free-map.c # Free sector bitmap.
filesys_SRC += filesys/file.c # Files.
filesys_SRC += filesys/directory.c # Directories.
filesys_SRC += filesys/inode.c # File headers.
filesys_SRC += filesys/fsutil.c # Utilities.
SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC))
#foreach语法是: $(foreach <var>,<list>,<text>)
OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES)))
#$(patsubst <pattern>,<replacement>,<text>) ———— 模式字符串替换函数
#这里解释一下变量OBJECTS的内容,第一步:$(patsubst %.S,%.o,$(SOURCES)) ,将变量SOURCE中的%.S文件替换成%.o文件
#第二步:将得到的新变量 中的 %.c文件 替换成 %.o文件
DEPENDS = $(patsubst %.o,%.d,$(OBJECTS))
#这里将OBJECTS中的%.o文件替换成%.d文件
threads/kernel.lds.s: CPPFLAGS += -P
threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h
kernel.o: threads/kernel.lds.s $(OBJECTS)
$(LD) -T $< -o $@ $(OBJECTS)
# ld命令是GNU的连接器,将目标文件连接为可执行程序。
# 使用'-T'命令行选项来提供你自己的连接脚本. 当你这么做的时候, 你的连接脚本会替换缺省的连接脚本.
#自动化变量 $< 依赖目标中的第一个目标名字
#自动化变量 $@ 表示规则中的目标文件集
#这里的意思就是, 使用(依赖目标中的第一个目标名字)threads/kernel.lds.s 作为连接脚本, 来链接 $(OBJECTS) 定义的文件,输出为 kernel.o
kernel.bin: kernel.o
$(OBJCOPY) -R .note -R .comment -S $< $@
#objcpy 命令:将目标文件的一部分或者全部内容拷贝到另外一个目标文件中,或者实现目标文件的格式转换
threads/loader.o: threads/loader.S
$(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES)
loader.bin: threads/loader.o
$(LD) -N -e 0 -Ttext 0x7c00 --oformat binary -o $@ $<
#链接
os.dsk: kernel.bin
cat $^ > $@
#自动化变量 $^ :所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
clean::
rm -f $(OBJECTS) $(DEPENDS)
rm -f threads/loader.o threads/kernel.lds.s threads/loader.d
rm -f kernel.bin.tmp
rm -f kernel.o kernel.lds.s
rm -f kernel.bin loader.bin
rm -f bochsout.txt bochsrc.txt
rm -f results grade
Makefile: $(SRCDIR)/Makefile.build
cp $< $@
-include $(DEPENDS)
一,主要是对shell命令不够熟悉
二,是对编译,链接的知识不够了解,导致很多编译上的东西也没法看懂
先将这次简单学习的过程记录下来,如果以后有机会接触到大型项目的Makefile再回过头看看这篇文章,要知道自己曾经接触并且了解过Makefile!
相关文章推荐
- Spring MVC入门-项目搭建步骤解析
- 如何给你iOS项目选择最合适的XML解析方式
- 安卓如何创建第一个项目-详细解析与错误报告
- eclipse生成的web项目在resin服务器上的发布(不能解析web.xml)
- github项目解析(九)-->实现activity跳转动画的五种方式
- iOS开发中常见的项目文件与MVC结构优化思路解析
- ireport引入到项目无法解析字体及pdf无法显示中文的解决方案
- MPC制作项目文件(makefile) (二)
- 开源项目源码解析
- linux命令ORshell脚本语言OR Makefile文档里一些命令解析
- 小女孩和稻草人大战(项目解析)
- 打开FL Studio项目文件的步骤解析
- Android官方MVP架构示例项目解析
- 开源OSS.Social微信项目解析
- yii2项目前台页面开发中,用到的算法1 (例题解析)
- maven 项目情动报错解析
- 如何给你iOS项目选择最合适的XML解析方式
- calamari项目结构解析
- U-boot-1.2.0 Makefile解析
- Github项目解析(十四)-->快速实现自定义地图聚合操作