疯狂的 Make:xv6 的 Makefile
Makefile
Makefile 的基本规则结构如下:
目标: 依赖
命令需要注意的是,命令前面必须是 Tab 而非空格。Makefile 中可以提供多个目标,在终端输入 make <目标名> 即执行相关的命令。
在执行没有参数的 make 时,make 程序会尝试构建 Makefile 中第一个目标。
.PHONY
make 在尝试构建目标时,会根据文件时间戳判断目标是否需要更新:如果目标不存在,或者依赖文件比目标更新,就会调用相关命令生成或更新目标。
但很多时候,目标并不是一个文件,而只是一个任务名,比如 make clean。这种情况下,可以在 Makefile 中写
.PHONY: 目标1 目标2 目标3 # 也可以分多行写这样,make 在构建这类目标的时候就退化为一个命令执行器。
变量
xv6 的 Makefile 中用到的变量大致可分为这几类:立即展开变量、递归展开变量、条件变量。他们的语法是:
立即展开变量 := 变量值
递归变量 = 变量值
ifndef 条件变量
条件变量 := 默认值
endif
ifneq ($(变量或表达式),)
变量名 += 追加内容
endif # 若不相等,执行其中的语句立即展开变量会在定义时立刻计算出来,并把结果保存下来。递归展开变量不会在定义时立刻计算右侧内容,而是在使用变量时再展开。
通过调用 shell 命令生成变量
语法为 $(shell 命令),这里会调用系统shell,把命令的输出结果作为变量值。
通配符
$(wildcard ...) 这种写法允许使用通配符,比如
clang-format -i $(wildcard kernel/*.[ch] user/*.[ch] mkfs/*.c)`自动变量:$@、$<、$^、$*
| 自动变量 | 含义 |
|---|---|
$@ | 当前目标 |
$< | 第一个依赖 |
$^ | 所有依赖,去重 |
$* | 模式规则中 % 匹配到的部分 |
Xv6 的 Makefile
常量折叠
xv6 的 Makefile 开头定义了这样一坨东西
# Try to infer the correct TOOLPREFIX if not set
ifndef TOOLPREFIX
TOOLPREFIX := $(shell if riscv64-unknown-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-unknown-elf-'; \
elif riscv64-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-elf-'; \
elif riscv64-none-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-none-elf-'; \
elif riscv64-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-linux-gnu-'; \
elif riscv64-unknown-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-unknown-linux-gnu-'; \
else echo "***" 1>&2; \
echo "*** Error: Couldn't find a riscv64 version of GCC/binutils." 1>&2; \
echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \
echo "***" 1>&2; exit 1; fi)
endif我确信这是一个常量,因为我并没有跨平台修改代码的需求,可以把这段改为:
TOOLPREFIX = riscv64-unknown-elf-检查各个目标
对于 xv6 的 Makefile,第一个目标是这样的
$K/kernel: $(OBJS) $K/kernel.ld
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)
$(OBJDUMP) -S $K/kernel > $K/kernel.asm
$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym上面这一段 Makefile 的大意为:如果 $(OBJS) 或 $K/kernel.ld 有变化,就重新生成 $K/kernel,如果要生成$K/kernel,依次执行以下几步:
- 用链接器把所有目标文件
$(OBJS)按照$K/kernel.ld的布局规则链接成$K/kernel。 - 把
$K/kernel反汇编,生成一个方便调试和查看的$K/kernel.asm文件。 - 从
$K/kernel中提取符号表,整理成简洁格式,保存为$K/kernel.sym。
鉴于我根本看不懂 .asm 和 .sym 文件,所以这后两行其实可以先删了。
第二个目标是这样的
$K/%.o: $K/%.S
$(CC) -march=rv64gc -g -c -o $@ $<这句可以理解为,目标是所有的 .o 文件,每一个 .o 文件都可以通过同名的 .S 文件编译得到。
第三个目标是一个 tags 文件,但这个文件是给 Emacs 用的,作为 Vim 用户,我无法容忍这个目标的存在,将其删除。
tags: $(OBJS)
etags kernel/*.S kernel/*.c第四个目标,与第一个目标 kernel 类似
_%: %.o $(ULIB) $U/user.ld
$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $< $(ULIB)
$(OBJDUMP) -S $@ > $*.asm
$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym