跳转至

Makefile 基础教程

本节目标

理解课程 Makefile 的核心结构,掌握常用构建命令,并能定位常见配置问题。

这份教程面向第一次接触 Makefile 的同学。目标是先把项目“跑起来”,再逐步理解它背后的规则。

1 Makefile 是什么

Makefile 用来描述“如何从源文件生成可执行结果”。

在当前项目里,可以把它理解成这样一条流水线:

  1. 编译 .c/.S 得到 .o
  2. 链接得到 kernel.elf
  3. 打包文件系统镜像 fs.img
  4. qemu 运行内核

2 先掌握最常用命令

建议同学们先记住下面几条,实验里会反复使用:

make                 # 默认目标,等价于 make all
make qemu            # 编译并启动 QEMU
make qemu-gdb        # 启动可调试模式
make clean           # 清理构建产物

这个 Makefile 支持临时改参数:

make qemu CPUS=2 RAM=256M

对应定义是:

CPUS ?= 4
RAM ?= 128M

?= 表示“如果外部没有给值,就使用默认值”。

3 Makefile 的基本结构

核心格式是:

目标: 依赖
    命令

例如:

all: kernel.elf $(COMDB)

意思是:all 依赖 kernel.elf$(COMDB)。执行 make 时,会先把依赖准备好。

再看一个链接规则:

kernel.elf: $(KLD)/kernel.ld $(KOBJS) | $(COMDB)
    $(LD) $(LDFLAGS) -T $(KLD)/kernel.ld -o $@ $(KOBJS)

这条规则负责把对象文件链接成 kernel.elf

4 变量写法(入门够用)

当前文件里主要用了 4 种赋值方式:

  • =:普通赋值(递归展开)
  • :=:立即展开赋值(常和 $(shell ...) 配合)
  • +=:在原变量后追加
  • ?=:变量未定义时才赋值

示例:

K = kernel
TOOLPREFIX := $(shell ...)
CFLAGS += -Wall -Werror
CPUS ?= 4

5 模式规则与自动变量

下面这条是批量编译的关键:

$(BUILD)/%.o: %.c
    @mkdir -p $(@D) $(COMDBDIR)
    COMPDB_DIR=$(COMDBDIR) COMPDB_FILE=$< $(CC) $(CFLAGS) -c -o $@ $<

重点看这几个符号:

  • %.c -> %.o:同名源文件都可以套用这条规则
  • $@:当前目标文件
  • $<:第一个依赖文件
  • $(@D):目标文件所在目录

有了模式规则,就不用给每个 .c 文件重复写编译命令。

6 条件判断:检查环境是否可用

当前 Makefile 会自动检查工具链和 QEMU:

ifndef TOOLPREFIX
TOOLPREFIX := $(shell ...)
endif

ifeq ($(TOOLPREFIX),***)
$(error "Couldn't find a RISC-V toolchain in PATH")
endif

如果环境没配好,make 会直接报错并停止。这是正常行为,先按报错补环境即可。

7 .PHONY 与依赖自动包含

文件末尾有两句值得认识:

.PHONY: all qemu fs fsimg qemu-gdb gdb clean compdb
-include $(KOBJS:.o=.d) $(UOBJS:.o=.d)
  • .PHONY:声明这些目标是“命令名”,不是实际文件名。
  • -include ...d:包含自动生成的头文件依赖,改了 .h 后也能触发重编译。

8 一个最常见的修改场景

如果你新增了一个用户程序(例如 user/hello2.c),通常需要改两处:

  1. UPROGS 里加入 hello2
  2. fsimg 目标中加入 --add hello2=$(UBUILD)/hello2.elf

然后执行:

make fsimg
make qemu

9 常见问题提醒

  • 命令前缩进不是 Tab(Makefile 命令行必须是 Tab)
  • 新程序写好了,但忘了加入 UPROGS
  • 工具链或 qemu-system-riscv64 没装好
  • 只看最后一行报错,忽略了前面的关键信息

建议提问时附上完整命令和完整报错输出,这样助教和同学更容易定位问题。

补充材料