编写一个操作系统是编程领域的“珠穆朗玛峰”,虽然这极其复杂,但我们可以从一个微型内核(Micro-kernel)开始,让它在虚拟机中启动并显示一条消息。
我们将创建一个名为 AuraOS 的迷你操作系统。它将是一个32位的操作系统,使用 GRUB 作为引导加载程序,并在 QEMU 虚拟机中运行。
这个项目将分为以下几个部分:
项目架构:我们将如何组织代码和工具。
环境搭建:安装和配置构建操作系统所需的全部工具(交叉编译器等)。
编写代码:编写汇编启动代码和C语言内核。
构建和运行:编译所有内容,创建一个可引导的ISO镜像,并在QEMU中启动它。
第1部分:AuraOS 架构与目标
目标:启动后,清空屏幕,并在屏幕顶部打印 "Welcome to AuraOS!"。
平台:i386 (32-bit x86)。32位比64位更简单,是入门的绝佳选择。
引导方式:我们将遵循 Multiboot 规范。这是一个标准,允许像GRUB这样的引导加载程序以一种可预测的方式加载我们的内核。
技术栈:
汇编 (NASM):用于编写最初的启动代码,设置堆栈并跳转到C代码。
C语言:用于编写我们的内核主逻辑。
Linker Script:告诉链接器如何组织我们的代码,把它放在内存的正确位置。
核心原理:我们将直接写入VGA视频内存(地址为 0xB8000)来在屏幕上显示字符。这是最底层的“打印”方式,因为此时没有任何操作系统提供的API可用。
第2部分:环境搭建 (Linux)
这是最关键的一步。我们需要一个“交叉编译器”,即一个在我们当前的操作系统(如Linux x86_64)上运行,但能生成在另一个平台(我们的AuraOS i386)上运行的代码的编译器。
在你的Linux系统(推荐Ubuntu/Debian)上打开终端,执行以下命令:
安装基础工具和QEMU
sudo apt update sudo apt install build-essential nasm qemu-system-x86 mtools xorriso
build-essential: 包含 gcc, make 等。
nasm: 我们需要的汇编器。
qemu-system-x86: 我们的虚拟机。
xorriso: 用于创建可引导的ISO文件。
构建i386交叉编译器我们将构建一个GCC交叉编译器,专门用于i386-elf目标。
# 设置一些变量 export PREFIX="$HOME/opt/cross" export TARGET=i686-elf export PATH="$PREFIX/bin:$PATH" # 创建目录 mkdir -p $HOME/src mkdir -p $PREFIX # 下载 Binutils 和 GCC cd $HOME/src wget https://ftp.gnu.org/gnu/binutils/binutils-2.39.tar.xz wget https://ftp.gnu.org/gnu/gcc/gcc-12.2.0/gcc-12.2.0.tar.xz # 解压 tar -xf binutils-2.39.tar.xz tar -xf gcc-12.2.0.tar.xz # 1. 构建 Binutils echo "Building Binutils..." mkdir build-binutils cd build-binutils ../binutils-2.39/configure --target=$TARGET --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror make make install cd .. # 2. 构建 GCC echo "Building GCC..." mkdir build-gcc cd build-gcc ../gcc-12.2.0/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --enable-languages=c --without-headers make all-gcc make all-target-libgcc make install-gcc make install-target-libgcc cd .. echo "Cross-compiler setup complete!"
注意:这个编译过程可能需要10-20分钟。
最后,将 export PATH="$HOME/opt/cross/bin:$PATH" 添加到你的 ~/.bashrc 或 ~/.zshrc 文件末尾,这样每次打开终端都能找到这个交叉编译器。然后运行 source ~/.bashrc。
验证一下是否成功:
i686-elf-gcc --version
如果能看到GCC的版本信息,说明你已成功!
第3部分:编写操作系统代码
现在,创建一个项目目录 auraos,并在其中创建以下文件。
mkdir auraos cd auraos1. boot.s (汇编启动文件)
这是GRUB加载后执行的第一个文件。
; boot.s ; 定义Multiboot规范的常量 MBALIGN equ 1<<0 ; Align loaded modules on page boundaries MEMINFO equ 1<<1 ; Provide memory map FLAGS equ MBALIGN | MEMINFO MAGIC equ 0x1BADB002 ; Multiboot magic number CHECKSUM equ -(MAGIC + FLAGS) ; Multiboot header section section .multiboot align 4 dd MAGIC dd FLAGS dd CHECKSUM ; 定义一个堆栈,大小为16KB section .bss align 16 stack_bottom: resb 16384 ; 16 KB stack_top: ; 内核的入口点 section .text global _start _start: ; CPU刚启动,中断是关闭的。我们现在没有中断处理程序,所以保持关闭。 cli ; 设置堆栈指针 mov esp, stack_top ; 调用我们的C语言内核主函数 extern kernel_main call kernel_main ; 如果内核返回,我们让CPU停机 hlt .hang: jmp .hang2. kernel.c (C语言内核)
这是操作系统的核心逻辑。
// kernel.c // 定义VGA文本模式的常量 #define VGA_WIDTH 80 #define VGA_HEIGHT 25 #define VGA_MEMORY (volatile unsigned short*)0xB8000 // 定义VGA颜色 enum vga_color { VGA_COLOR_BLACK = 0, VGA_COLOR_BLUE = 1, VGA_COLOR_GREEN = 2, VGA_COLOR_CYAN = 3, VGA_COLOR_RED = 4, VGA_COLOR_MAGENTA = 5, VGA_COLOR_BROWN = 6, VGA_COLOR_LIGHT_GREY = 7, VGA_COLOR_DARK_GREY = 8, VGA_COLOR_LIGHT_BLUE = 9, VGA_COLOR_LIGHT_GREEN = 10, VGA_COLOR_LIGHT_CYAN = 11, VGA_COLOR_LIGHT_RED = 12, VGA_COLOR_LIGHT_MAGENTA = 13, VGA_COLOR_LIGHT_BROWN = 14, VGA_COLOR_WHITE = 15, }; // 全局变量来跟踪光标位置 int term_row = 0; int term_col = 0; // 创建一个VGA字符的辅助函数 static inline unsigned short vga_entry(char c, enum vga_color fg, enum vga_color bg) { return (unsigned short)c | (unsigned short)((bg << 4) | fg) << 8; } // 在指定位置放置一个字符 void term_putchar_at(char c, int x, int y) { const int index = y * VGA_WIDTH + x; VGA_MEMORY[index] = vga_entry(c, VGA_COLOR_LIGHT_GREEN, VGA_COLOR_BLACK); } // 清空屏幕 void term_clear() { for (int y = 0; y < VGA_HEIGHT; y++) { for (int x = 0; x < VGA_WIDTH; x++) { term_putchar_at(' ', x, y); } } term_row = 0; term_col = 0; } // 打印一个字符串 void term_print(const char* str) { for (int i = 0; str[i] != '\0'; i++) { term_putchar_at(str[i], term_col, term_row); term_col++; if (term_col >= VGA_WIDTH) { term_col = 0; term_row++; // (我们在这里省略了滚动逻辑以保持简单) } } } // C内核的入口函数 void kernel_main(void) { term_clear(); term_row = 5; // 移动到第5行 term_col = 25; // 移动到第25列,使其居中一点 term_print("Welcome to AuraOS!"); }3. linker.ld (链接器脚本)
这个文件告诉链接器如何将编译好的代码片段组合成最终的内核文件。
/* linker.ld */ ENTRY(_start) /* 指定入口点 */ SECTIONS { /* 内核从1MB地址开始加载 */ . = 1M; .text : { *(.multiboot) /* 首先放置Multiboot头 */ *(.text) /* 然后是所有代码 */ } .rodata : { *(.rodata) } .data : { *(.data) } .bss : { *(.bss) } }
第4部分:构建系统和运行
手动编译这些文件很麻烦,所以我们使用 Makefile 来自动化这个过程。
1. Makefile在 auraos 目录下创建这个文件。
# Makefile # 使用我们构建的交叉编译器 CC = i686-elf-gcc AS = nasm LD = i686-elf-ld # 编译和链接标志 # -ffreestanding: 不使用标准库 # -fno-pie: 生成位置相关的代码 CFLAGS = -std=gnu99 -ffreestanding -g -Wall -Wextra -fno-pie ASFLAGS = -f elf32 LDFLAGS = -T linker.ld -ffreestanding -O2 -nostdlib # 源文件 C_SOURCES = kernel.c ASM_SOURCES = boot.s OBJ_FILES = $(ASM_SOURCES:.s=.o) $(C_SOURCES:.c=.o) KERNEL = auraos.bin # 默认目标 all: $(KERNEL) # 链接内核 $(KERNEL): $(OBJ_FILES) $(CC) $(LDFLAGS) -o $(KERNEL) $(OBJ_FILES) -lgcc # 编译C文件 %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ # 编译汇编文件 %.o: %.s $(AS) $(ASFLAGS) $< -o $@ # 创建ISO镜像 iso: $(KERNEL) grub.cfg mkdir -p isodir/boot/grub cp $(KERNEL) isodir/boot/ cp grub.cfg isodir/boot/grub/ xorriso -as mkisofs -o auraos.iso -b boot/grub/grub-eltorito.bin -no-emul-boot -boot-load-size 4 -boot-info-table --eltorito-catalog boot/grub/boot.cat -eltorito-boot boot/grub/grub-eltorito.bin -grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img -grub2-boot-info isodir # 运行QEMU run: iso qemu-system-i386 -cdrom auraos.iso # 清理构建文件 clean: rm -f $(KERNEL) $(OBJ_FILES) rm -rf isodir auraos.iso .PHONY: all clean iso run2. grub.cfg (GRUB配置文件)
这是放在ISO镜像里,告诉GRUB如何加载我们的内核的文件。
# grub.cfg set timeout=0 set default=0 menuentry "AuraOS" { multiboot /boot/auraos.bin boot }3. 运行!
你的 auraos 目录现在应该有以下文件:
boot.s
kernel.c
linker.ld
Makefile
grub.cfg
现在,在终端中执行:
make
这会编译你的代码并生成 auraos.bin 文件。
接着,执行:
make run
这条命令会:
创建一个 isodir 目录结构。
将你的内核和 grub.cfg 复制进去。
使用 xorriso 创建一个名为 auraos.iso 的可引导ISO文件。
启动QEMU虚拟机,并加载 auraos.iso。
如果一切顺利,你将看到一个QEMU窗口弹出,短暂的GRUB加载后,屏幕会被清空,并显示:
恭喜!你已经从零开始构建并运行了自己的操作系统!
第5部分:下一步(专业之路)
这是一个伟大的开始,但一个真正的操作系统还有很长的路要走。以下是你的下一步探索方向:
GDT和IDT:设置全局描述符表(GDT)和中断描述符表(IDT)。这是处理硬件中断(如键盘、定时器)和系统调用的基础。
键盘驱动:编写一个简单的键盘驱动程序,让你可以在AuraOS中输入文字。
内存管理:实现物理内存管理器和虚拟内存(分页),这是现代操作系统的核心。
系统调用:建立从用户程序进入内核的接口。
多任务处理:实现进程调度,让多个程序可以“同时”运行。
推荐资源:OSDev Wiki (https://wiki.osdev.org) 是操作系统开发者的圣经,你几乎可以在那里找到所有你需要的信息。
网友回复