内核未加载的问题

problems with kernel not being loaded

我正在创建一个 OS,当我编译代码时什么也没发生,什么都没有(没有错误、警告或任何东西)我认为 make 文件有一些问题。

生成文件:

build_kernel:
    echo "Building kernel..."
    ${ASM} ./src/kernel/kernel_entry.asm -f elf64 -o ${BUILD_DIR}/kernel_entry.o
    ${C_COMPILER} -c ./src/kernel/kernel.c -o ${BUILD_DIR}/kernel_start.o
    ${C_COMPILER} -c ./src/kernel/drivers/printutils.c -o ${BUILD_DIR}/kernel_printutils.o
    ${C_COMPILER} -c ./src/kernel/drivers/port.c -o ${BUILD_DIR}/kernel_ports.o
    echo "kernel build complete."

link:
    echo "Linking..."
    ${LINKER} -o ${BUILD_DIR}/kernel.bin ${BUILD_DIR}/kernel_ports.o \
        ${BUILD_DIR}/kernel_printutils.o ${BUILD_DIR}/kernel_start.o \
        ${BUILD_DIR}/kernel_entry.o -Ttext 0x1000 --oformat binary
    echo "Linking complete"

run:
    echo "Running qemu..."
    qemu-system-x86_64 -fda ${BUILD_DIR}/os.bin

merge_binary:
    echo "Merging binary..."
    cat ${BUILD_DIR}/boot.bin ${BUILD_DIR}/kernel.bin > ${BUILD_DIR}/os.bin
    echo "Binary merged."

post_build:
    rm -f ${BUILD_DIR}/boot.bin
    rm -f ${BUILD_DIR}/kernel.bin
    rm -f ${BUILD_DIR}/kernel.o
    rm -f ${BUILD_DIR}/kernel_entry.o
    rm -f ${BUILD_DIR}/kernel_ports.o
    rm -f ${BUILD_DIR}/kernel_printutils.o
    rm -f ${BUILD_DIR}/kernel_start.o

我想知道 makefile 发生了什么,这是将所有代码编译成目标文件和 link 它们的正确方法吗。

任何帮助将不胜感激。

我只是评论你的 make 风格——这并没有回答你为什么什么都不输出(而且你的意思还不清楚——如果你 运行 make,它应该至少输出 echo "Building kernel..."...)。就 makefile 风格而言,这似乎是使用脚本思维而不是 make 思维构建的。考虑你的第一部分:

build_kernel:
    echo "Building kernel..."
    ${ASM} ./src/kernel/kernel_entry.asm -f elf64 -o ${BUILD_DIR}/kernel_entry.o
    ${C_COMPILER} -c ./src/kernel/kernel.c -o ${BUILD_DIR}/kernel_start.o
    ${C_COMPILER} -c ./src/kernel/drivers/printutils.c -o ${BUILD_DIR}/kernel_printutils.o
    ${C_COMPILER} -c ./src/kernel/drivers/port.c -o ${BUILD_DIR}/kernel_ports.o
    echo "kernel build complete."

这有几个问题。首先是名称——这看起来是构建一堆工件而不是构建内核。此外,该配方从不生成名为 build_kernel 的文件,因此这应该是一个虚假目标。接下来,这实际上是一个脚本,它构建了四个独立的东西。这些可以分成四个独立的规则,每个规则构建一个东西,然后主要目标将依赖于此。因此,它可能看起来像:

.PHONY: build_kernel_objs
build_kernel_objs: ${C_OBJS} ${ASM_OBJS}
  @echo done building $@

${BUILD_DIR}/kernel_start.o : ./src/kernel/kernel.c
  ${C_COMPILER} -c $< -o $@

${BUILD_DIR}/kernel_printutils.o : ./src/kernel/kernel_printutils.c
  ${C_COMPILER} -c $< -o $@

${BUILD_DIR}/kernel_ports.o : ./src/kernel/kernel_ports.c
  ${C_COMPILER} -c $< -o $@

注意上面是重复的,如果你有几百个文件,会很快。这也可以使用 static pattern 规则来完成:

C_FILES := \
  ./src/kernel/kernel_start.c
  ./src/kernel/kernel_printutils.c
  ./src/kernel/kernel_ports.c

ASM_FILES := \
  ./src/kernel/kernel_entry.asm

C_OBJS := ${C_FILES :./src/kernel/%.c=${BUILD_DIR}/%.o}
ASM_OBJS := ${ASM_FILES :./src/kernel/%.asm=${BUILD_DIR}/%.o}

${C_OBJS} : ${BUILD_DIR}/%.o : ./src/kernel/%.c
  ${C_COMPILER} -c $< $@

.PHONY: build_kernel_objs

build_kernel_objs: ${C_OBJS} ${ASM_OBJS}
  @echo "done building $@"

与您所做的相比,这些有几个优点 -- 首先,make 只会构建过时的对象,因此它不会做不必要的工作。如果在 make 命令行上指定了 -j 选项,它也可以并行构建文件。其次,它更易于维护——如果您必须添加额外的文件,您可以在一个地方完成,一切都会顺利进行。此外,如果您的 make 目录中恰好有一个名为 build_kernel_objs 的文件,.PHONY 可以防止 make 失败。最后,echo 行前面的 @ 防止回显实际的 echo 命令,这样看起来会更好。

需要注意的是,它不处理头文件的修改(如所写,如果更新头文件,则不会重建依赖于它的 c 文件。请参阅 here 了解一些说明关于解决这个问题。

下一节 link,makefile 配方应反映目标。

.PHONY: link
link : ${BUILD_DIR}/kernel.bin

${BUILD_DIR}/kernel.bin: ${C_OBJS} ${ASM_OBJS}
    ${LINKER} -o $@ $^ -Ttext 0x1000 --oformat binary   

这会创建一个虚假目标 link,因此您可以键入 make link。如果任何 C 对象或 ASM 对象已更新,它只会执行 link。同样的概念适用于您的 merge_binary 目标

对于run,这似乎有些争议,但一个普遍的经验法则是 make 应该被用来 制作 一个可执行文件,不是为了 运行 吧。如果您想使用特定参数调用构建的目标,则单独的 shell 脚本更适合。

最后,您的 post_build 规则可能应该重命名为 CLEAN,并声明为虚假规则。