构建 EOS

又到了哄编译器开心的时候了.

有了之前的 Makefile 基础, 相信你也可以很快总结出构建 EOS 的思路:

  1. 了解 EOS 构建完成后会生成哪些文件;

  2. 找到生成这些文件的规则, 并且指定生成时依赖的文件;

  3. 使用变量和函数让 Makefile 脚本更为灵活.

那么我们接下来就用这种自顶向下的思路来编写构建 EOS 的 Makefile.

生成的文件

根据 EOS 官方教程, EOS 编译完成之后会生成以下文件:

  • boot.bin: EOS 的引导程序, 将会被写入软盘的引导扇区, 负责执行最初的引导工作;

  • loader.bin: EOS 的内核加载程序, 被 boot.bin 加载后, 负责切换处理器状态并加载内核;

  • kernel.dll: EOS 的内核, 操作系统本体;

  • libkernel.a: EOS 内核的导入库 (import library), 用于给 EOS 用户应用程序提供内核接口信息.

其中, boot.bin 可以由 boot/boot.asm 直接汇编得到; loader.bin 可以由 boot/loader.asm 直接汇编得到; 其余文件需要单独编译为目标文件, 然后使用链接器链接为 DLL 同时生成导入库.

在 EOS 的构建过程中, 所有 .asm 文件均需要使用 nasm 汇编器进行汇编; .c 文件需要使用 MinGW GCC (i686-w64-mingw32-gcc) 进行编译; .o 文件需要使用 MinGW LD (i686-w64-mingw32-ld) 进行链接.

构建规则示例

为了方便调试, 我们需要为编译器和汇编器指定 -g 选项来生成调试信息.

.asm 构建为 .bin

例如将 boot.asm 构建为 boot.bin:

 nasm -g boot.asm -o boot.bin -l boot.lst

-l *.lst 表示生成列表文件, 其中包含了生成的二进制与汇编源文件的对应关系.

.asm 构建为 .o

例如将 cpu.asm 构建为 cpu.o:

 nasm -g cpu.asm -o cpu.bin -l cpu.lst -f win32

-f win32 表示指定输出的目标格式为 win32.

.c 构建为 .o

例如将 start.c 构建为 start.o:

i686-w64-mingw32-gcc  -g start.c -o start.o                  \
                      -c -m32 -nostdlib -nostdinc            \
                      -fsigned-char -pipe -fno-builtin       \
                      -fno-omit-frame-pointer -ffreestanding \
                      -D_KERNEL_ -D_I386 -D_DEBUG            \
                      -Isrc/inc -Isrc/ke -Isrc/ob -Isrc/io   \
                      -Isrc/io/driver -Isrc/ps -Isrc/mm      \
                      -Isrc/mm/i386

其中:

  • -c: 仅编译到目标文件;

  • -m32, ..., -ffreestanding: 自行 RTFM;

  • -D_KERNEL_, ...: 预定义宏, 相当于在源码中 #define _KERNEL_, 目的是控制源码的表达;

  • -Isrc/inc, ..., -Isrc/mm/i386: 设置头文件搜索目录, 告诉编译器到这些目录中寻找头文件, 因为 EOS 的头文件 (*.h) 只出现在了这些地方.

.o 构建为 .dll, 同时生成导入库:

i686-w64-mingw32-ld *.o -o kernel.dll             \
                        -nostdlib -shared         \
                        --image-base 0x80010000   \
                        -e _KiSystemStartup       \
                        --out-implib libkernel.a

其中:

  • -nostdlib: 不使用标准库;

  • -shared: 生成 shared library;

  • --image-base 0x80010000: EOS 内核将会被 loader.bin 加载到虚地址 0x80010000 处, 所以此处指定 DLL 映像的基地址为 0x80010000. 详情请参考官方教程并 RTFSC;

  • -e _KiSystemStartup: 指定 DLL 入口点为 _KiSystemStartup, 即 EOS 内核的初始化函数 (位于 ke/start.c);

  • --out-implib libkernel.a: 生成导入库 libkernel.a.

编写 Makefile

可以得到编译 EOS 内核的 Makefile 如下:

在此之前, 你能尝试自己完成这个 Makefile 脚本吗?

export CROSS_PREFIX = i686-w64-mingw32-
DEBUG_ARG = -D_DEBUG -g

# directories
export TOP_DIR = $(shell if [ "$$PWD" != "" ]; then echo $$PWD; else pwd; fi)
TARGET_DIR = $(TOP_DIR)/build
OBJ_DIR = $(TOP_DIR)/build/obj
LST_DIR = $(TOP_DIR)/build/lst
KERNEL_SRC_DIR = $(TOP_DIR)/src
INCLUDE_ARG := -I$(KERNEL_SRC_DIR)/inc
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/ke
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/ob
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/io
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/io/driver
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/ps
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/mm
INCLUDE_ARG += -I$(KERNEL_SRC_DIR)/mm/i386

# files
KERNEL_TARGETS := $(patsubst $(KERNEL_SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(wildcard $(KERNEL_SRC_DIR)/**/*.c))
KERNEL_TARGETS += $(patsubst $(KERNEL_SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(wildcard $(KERNEL_SRC_DIR)/**/**/*.c))
KERNEL_TARGETS += $(patsubst $(KERNEL_SRC_DIR)/%.asm, $(OBJ_DIR)/%.o, $(wildcard $(KERNEL_SRC_DIR)/**/**/*.asm))

# C compiler
CFLAGS := $(DEBUG_ARG)
CFLAGS += -m32 -c -nostdlib -nostdinc -fsigned-char -pipe
CFLAGS += -fno-builtin -fno-omit-frame-pointer -ffreestanding 
CFLAGS += -D_KERNEL_ -D_I386
CFLAGS += $(INCLUDE_ARG)
CC = $(CROSS_PREFIX)gcc $(CFLAGS)

# assembler
NASMFLAGS := $(DEBUG_ARG) -f win32
NASM = nasm $(NASMFLAGS)

# linker
LDFLAGS := -nostdlib
LDFLAGS += -shared --image-base 0x80010000 -e _KiSystemStartup
LDFLAGS += --out-implib $(TARGET_DIR)/libkernel.a
LD = $(CROSS_PREFIX)ld $(LDFLAGS)

.PHONY: all kernel clean

all: $(TARGET_DIR)/boot.bin $(TARGET_DIR)/loader.bin kernel

kernel: $(TARGET_DIR) $(OBJ_DIR) $(LST_DIR) $(TARGET_DIR)/kernel.dll

clean:
	-rm -rf $(OBJ_DIR)/*
	-rm -rf $(LST_DIR)/*
	-rm -f $(TARGET_DIR)/boot.bin
	-rm -f $(TARGET_DIR)/loader.bin
	-rm -f $(TARGET_DIR)/kernel.dll
	-rm -f $(TARGET_DIR)/libkernel.a

# directories
$(TARGET_DIR):
	mkdir $(TARGET_DIR)

$(OBJ_DIR):
	mkdir $(OBJ_DIR)

$(LST_DIR):
	mkdir $(LST_DIR)

# EOS kernel
$(TARGET_DIR)/kernel.dll: $(KERNEL_TARGETS)
	$(LD) -o $@ $(KERNEL_TARGETS)

# EOS object
$(OBJ_DIR)/%.o: $(KERNEL_SRC_DIR)/%.c
	-mkdir -p $(dir $@)
	$(CC) -o $@ $^

$(OBJ_DIR)/%.o: $(KERNEL_SRC_DIR)/%.asm
	-mkdir -p $(dir $@)
	$(NASM) -o $@ -l $(LST_DIR)/$(basename $(notdir $<)).lst $<

# bootloaders
$(TARGET_DIR)/%.bin: $(KERNEL_SRC_DIR)/boot/%.asm
	nasm $(DEBUG_ARG) -o $@ -l $(LST_DIR)/$(basename $(notdir $<)).lst $<

在你理解这个 Makefile 之后, 你可以思考一下这个脚本和上一节出现的那个 Makefile 之间, 在 C 语言文件的构建过程中有什么差异? 这会带来什么问题?

事实上, 这个 Makefile 没有考虑 C 源文件和头文件之间的依赖关系. 所以你也看到了, 作者并不是全能的 (其实是懒), 不如你来出个主意改进一下这个 Makefile, 这样下一次的 OS lab 教程中, 这部分可能就是你写的了.

将其放入 eos 目录中:

eos
├── build
├── src
├── vm
├── Makefile      # 构建脚本
└── License.txt

尝试构建 EOS 内核:

make

可以看到 build 目录中已经生成了我们需要的四个文件.

Last updated

Was this helpful?