您的位置:首页 > 其它

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里面的内容,一点一点来解析吧!

# -*- 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
# 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)
从这里对实例Makefile的简单了解中可以大概清楚Makefile的书写规则,用法以及它给大型工程编译带来的编译,但是现在还不能看懂这些Makefile系列做了什么

一,主要是对shell命令不够熟悉

二,是对编译,链接的知识不够了解,导致很多编译上的东西也没法看懂

先将这次简单学习的过程记录下来,如果以后有机会接触到大型项目的Makefile再回过头看看这篇文章,要知道自己曾经接触并且了解过Makefile!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: