xv6-riscv Makefile 详解与重塑教学

这是一个典型的用于构建嵌入式操作系统(如 xv6)的 Makefile,它处理了:

  • 交叉编译
  • 依赖管理
  • 用户程序构建
  • 文件系统打包
  • 一键运行等功能

✦ Makefile 总体目标

  • 构建内核镜像:kernel/kernel
  • 构建文件系统镜像:fs.img
  • 提供如 make qemu 这样的便捷命令,直接启动 QEMU 模拟器运行系统。

1. 变量定义:方便管理路径和对象文件

# 基本变量
K = kernel
U = user

OBJS = \
  $K/entry.o \
  $K/start.o \
  ... \
  $K/virtio_disk.o

✅ 说明:

  • KU 是目录别名,方便后续引用。
  • OBJS 是内核需要的所有目标文件列表。写 .o 文件时要写上路径前缀 $K/

2. 工具链配置:处理交叉编译

ifndef TOOLPREFIX
TOOLPREFIX := $(shell \
  if riscv64-unknown-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null; then \
    echo 'riscv64-unknown-elf-'; \
  elif ...; then \
    echo '...'; \
  else \
    echo "*** Error: Can't find toolchain" 1>&2; \
    exit 1; \
  fi)
endif

CC = $(TOOLPREFIX)gcc
AS = $(TOOLPREFIX)as
LD = $(TOOLPREFIX)ld
OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump
QEMU = qemu-system-riscv64

✅ 说明:

  • 自动检测合适的交叉工具链前缀。
  • 设置编译器、汇编器、链接器、objcopy 和 objdump。
  • 支持用户通过 make TOOLPREFIX=xxx- 手动指定。

3. 编译器与链接器参数

CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2
CFLAGS += -MD -fno-builtin-printf -I.
LDFLAGS = -z max-page-size=4096

✅ 说明:

  • -nostdlib-fno-builtin-* 是内核开发的必要参数。
  • -MD 用于自动生成 .d 依赖文件。
  • -I. 指定头文件搜索路径。

4. 规则定义:Makefile 的核心逻辑

内核构建

$K/kernel: $(OBJS) $K/kernel.ld $U/initcode
	$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)
	$(OBJDUMP) -S $K/kernel > $K/kernel.asm
	$(OBJDUMP) -t $K/kernel | sed ... > $K/kernel.sym

用户程序构建(模式规则)

ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o

_%: %.o $(ULIB)
	$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^
	$(OBJDUMP) -S $@ > $*.asm
	$(OBJDUMP) -t $@ | sed ... > $*.sym

UPROGS = \
  $U/_cat \
  $U/_ls \
  $U/_sh \
  ...

文件系统镜像构建

fs.img: mkfs/mkfs README $(UPROGS)
	mkfs/mkfs fs.img README $(UPROGS)

5. 自动依赖管理

-include kernel/*.d user/*.d

✅ 说明:

  • 自动包含 .d 依赖文件,让头文件变动能触发相应 .c 文件的重编译。
  • -include 不会因 .d 文件不存在而报错。

6. 伪目标(Phony Targets)

.PHONY: clean qemu

clean:
	rm -f *.o *.d fs.img kernel/kernel user/_* *.sym *.asm

qemu: $K/kernel fs.img
	$(QEMU) $(QEMUOPTS)

💡 从零重塑 Makefile 的流程思路

  1. 定义变量:如 K=kernel,列出 OBJS
  2. 配置工具链:写死或自动检测 TOOLPREFIX
  3. 设定编译参数CFLAGS, LDFLAGS
  4. 编写构建规则

    • 内核构建:kernel/kernel
    • 用户程序模式规则
    • fs.img 构建
  5. 添加自动依赖管理:用 -MD + -include
  6. 伪目标:如 clean, qemu,增强易用性。