实验一:系统软件启动过程
练习1:理解通过make生成执行文件的过程
1.1 操作系统镜像文件ucore.img是如何一步一步生成的?
Makefile
如下,解释部分内容参考
PROJ := challenge
EMPTY :=
SPACE := $(EMPTY) $(EMPTY)
SLASH := /
##make "V="可输出make执行的命令
V := @
#need llvm/cang-3.5+
#USELLVM := 1
##选择交叉编译器检查GCCPREFIX的设置
# try to infer the correct GCCPREFX
ifndef GCCPREFIX
GCCPREFIX := $(shell if i386-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \
then echo 'i386-elf-'; \
elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \
then echo ''; \
else echo "***" 1>&2; \
echo "*** Error: Couldn't find an i386-elf version of GCC/binutils." 1>&2; \
echo "*** Is the directory with i386-elf-gcc in your PATH?" 1>&2; \
echo "*** If your i386-elf toolchain is installed with a command" 1>&2; \
echo "*** prefix other than 'i386-elf-', set your GCCPREFIX" 1>&2; \
echo "*** environment variable to that prefix and run 'make' again." 1>&2; \
echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \
echo "***" 1>&2; exit 1; fi)
endif
##设置QEMU
# try to infer the correct QEMU
ifndef QEMU
QEMU := $(shell if which qemu-system-i386 > /dev/null; \
then echo 'qemu-system-i386'; exit; \
elif which i386-elf-qemu > /dev/null; \
then echo 'i386-elf-qemu'; exit; \
elif which qemu > /dev/null; \
then echo 'qemu'; exit; \
else \
echo "***" 1>&2; \
echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \
echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \
echo "***" 1>&2; exit 1; fi)
endif
# eliminate default suffix rules
.SUFFIXES: .c .S .h
##如果遇到error或者被中断了就删除所有目标文件
# delete target files if there is an error (or make is interrupted)
.DELETE_ON_ERROR:
##设置编译器选项
# define compiler and flags
ifndef USELLVM
##gcc编译,-g为了gdb调式,-Wall生成警告信息,-O2优化处理级别
HOSTCC := gcc
HOSTCFLAGS := -g -Wall -O2
CC := $(GCCPREFIX)gcc
##-fno-builtin不使用C语言的内建函数,-ggdb为GDB生成更丰富的调试信息,-m32用32位编译,-gstabs生成stabs格式调试信息但不包括GDB调试信息,-nostdinc不在系统默认头文件目录中寻找头文件,$(DEFS)未定义可用来扩展信息
CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS)
##$(shell)可以输出shell指令,-fno-stack-protector禁用堆栈保护,-E仅预处理不进行编译汇编链接可以提高速度,-x c指明c语言
##/dev/null指定目标文件,>/dev/null 2>&1标准错误重定向到标准输出,&&先运行前一句若成功再运行后一句
##意为只预处理,所有出错全部作为垃圾(/dev/null类似垃圾文件)测试能否开启-fno-stack-protector,若能则CFLAGS += -fno-stack-protector
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
else
##若使用clang,类似处理
HOSTCC := clang
HOSTCFLAGS := -g -Wall -O2
CC := clang
CFLAGS := -fno-builtin -Wall -g -m32 -mno-sse -nostdinc $(DEFS)
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
endif
##源文件类型为.c和.S
CTYPE := c S
LD := $(GCCPREFIX)ld
##shell中命令 ld -V可以输出支持的版本,|管道将前者的输出作为后者的输入,grep在输入中搜索elf_i386字串,找到就输出elf_i386
##意味如果支持elf_i386则LDFLAGS := -m elf_i386
LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null)
##-nostdlib不连接系统标准库文件
LDFLAGS += -nostdlib
OBJCOPY := $(GCCPREFIX)objcopy
OBJDUMP := $(GCCPREFIX)objdump
##定义一些shell命令
COPY := cp
MKDIR := mkdir -p
MV := mv
RM := rm -f
AWK := awk
SED := sed
SH := sh
TR := tr
TOUCH := touch -c
OBJDIR := obj
BINDIR := bin
ALLOBJS :=
ALLDEPS :=
TARGETS :=
##在function.mk中定义了大量辅助函数,部分说明参考了引用中的博文
include tools/function.mk
##call:call func,变量1,变量2,...
##listf:列出某地址下某类型的文件
##listf_cc:列出变量1下的.c与.S文件
listf_cc = $(call listf,$(1),$(CTYPE))
# for cc
##将文件打包
add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4))
##创建目标文件包
create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS))
# for hostcc
add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3))
create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS))
##patsubst替换通配符
##cgtype(filenames,type1,type2)把文件名中type1的改为type2,如.c改为.o
cgtype = $(patsubst %.$(2),%.$(3),$(1))
##列出所有目标文件,并按规则改后缀名
objfile = $(call toobj,$(1))
asmfile = $(call cgtype,$(call toobj,$(1)),o,asm)
outfile = $(call cgtype,$(call toobj,$(1)),o,out)
symfile = $(call cgtype,$(call toobj,$(1)),o,sym)
# for match pattern
match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?)
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# include kernel/user
INCLUDE += libs/
CFLAGS += $(addprefix -I,$(INCLUDE))
LIBDIR += libs
$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,)
# -------------------------------------------------------------------
# kernel
KINCLUDE += kern/debug/ \
kern/driver/ \
kern/trap/ \
kern/mm/
KSRCDIR += kern/init \
kern/libs \
kern/debug \
kern/driver \
kern/trap \
kern/mm
KCFLAGS += $(addprefix -I,$(KINCLUDE))
$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))
KOBJS = $(call read_packet,kernel libs)
# create kernel target
##将所有文件链接生成kernel
kernel = $(call totarget,kernel)
$(kernel): tools/kernel.ld
$(kernel): $(KOBJS)
@echo + ld $@
$(V)