手把手教你用Bochs和GCC搞定GeekOS Project0:从main.c修改到镜像运行

张开发
2026/4/21 11:57:31 15 分钟阅读
手把手教你用Bochs和GCC搞定GeekOS Project0:从main.c修改到镜像运行
从零开始玩转GeekOS Project0一个键盘交互内核线程的诞生第一次接触操作系统课程设计时面对满屏的C代码和Makefile那种手足无措的感觉我至今记忆犹新。本文将带你一步步完成GeekOS Project0的实现从环境搭建到内核线程运行每个环节都配有详细的操作指导和排错技巧。1. 环境准备搭建你的操作系统实验室在开始编码之前我们需要准备好开发环境。不同于普通的应用程序开发操作系统内核开发对工具链有特定要求。1.1 安装必备工具对于Linux用户推荐Ubuntu 20.04打开终端执行以下命令sudo apt update sudo apt install -y build-essential nasm bochs bochs-xWindows用户可以考虑使用WSL2Windows Subsystem for Linux来获得类似的开发体验。安装完成后验证工具是否就位gcc --version nasm -v bochs --help提示如果遇到权限问题可以在命令前加上sudo。对于网络连接较慢的情况可以考虑更换软件源。1.2 获取GeekOS源码GeekOS是一个教学用微型操作系统Project0是其入门项目。我们可以通过以下方式获取源码wget http://geekos.sourceforge.net/geekos-0.3.0.tar.gz tar -xzvf geekos-0.3.0.tar.gz cd geekos-0.3.0/src/project0项目目录结构说明geekos-0.3.0/ ├── src/ │ ├── geekos/ # 内核源代码 │ │ ├── main.c # 主要修改文件 │ ├── project0/ │ │ ├── build/ # 编译目录 │ │ ├── .bochsrc # 模拟器配置文件2. 代码修改创建你的第一个内核线程现在我们来修改main.c文件实现一个能响应键盘输入的内核线程。2.1 添加project0函数打开src/geekos/main.c在文件合适位置建议在Main函数前添加以下函数void project0() { Print(To Exit hit Ctrl d.\n); Keycode keycode; while(1) { if(Read_Key(keycode)) { // 过滤特殊键和键释放事件 if(!((keycode KEY_SPECIAL_FLAG) || (keycode KEY_RELEASE_FLAG))) { int asciiCode keycode 0xff; // 检测CtrlD组合 if((keycode KEY_CTRL_FLAG) asciiCode d) { Print(\n---------BYE!---------\n); Exit(1); } else { // 将回车转换为换行显示 char displayChar (asciiCode \r) ? \n : asciiCode; Print(%c, displayChar); } } } } }这段代码实现了一个简单的键盘交互打印欢迎信息进入无限循环读取键盘输入显示普通字符识别CtrlD组合键退出2.2 启动内核线程在Main函数中找到合适位置通常在初始化代码之后添加线程启动代码struct Kernel_Thread *thread; thread Start_Kernel_Thread(project0, 0, PRIORITY_NORMAL, false);同时注释掉原有的TODO提示// TODO(Start a kernel thread to echo pressed keys and print counts);注意Start_Kernel_Thread的第四个参数false表示这不是一个后台线程。如果设置为true线程将在后台运行。3. 编译与排错构建你的操作系统镜像代码修改完成后我们需要将其编译为可运行的镜像文件。3.1 编译流程进入build目录执行编译cd build make depend # 建立依赖关系 make # 编译整个项目编译成功后你会在build目录下看到生成的镜像文件fd.img软盘镜像文件geekos内核可执行文件常见编译错误及解决方案错误类型可能原因解决方法nasm not foundNASM汇编器未安装执行sudo apt install nasm头文件找不到路径问题检查include路径是否正确链接错误函数未实现检查所有TODO是否已处理3.2 配置Bochs模拟器Bochs需要一个配置文件来运行。进入build目录编辑或创建.bochsrc文件# 基本配置 megs: 32 romimage: file$BXSHARE/BIOS-bochs-latest vgaromimage: file$BXSHARE/VGABIOS-lgpl-latest # 启动设备配置 boot: floppy floppya: 1_44fd.img, statusinserted # 日志和调试 log: bochsout.txt panic: actionask error: actionreport debug: actionignore关键配置说明megs: 设置模拟内存大小32MB足够boot: 指定从软盘启动floppya: 指定镜像文件路径提示如果遇到VGABIOS错误可以尝试注释掉vgaromimage行Bochs会使用内置的VGA BIOS。4. 运行与调试见证你的内核线程一切就绪后就可以运行你的操作系统了。4.1 启动Bochs在build目录下执行bochs -q # -q表示跳过启动菜单如果一切正常你将看到Bochs窗口弹出并显示GeekOS的启动信息。4.2 常见运行问题问题1Bochs窗口黑屏这可能是因为模拟器停在了调试模式。在启动Bochs的终端中输入c回车问题2键盘输入无响应检查以下几点确认project0线程已正确启动检查键盘初始化代码是否被执行确保没有其他线程阻塞了键盘中断问题3CtrlD无法退出检查project0函数中的条件判断if((keycode KEY_CTRL_FLAG) asciiCode d)4.3 调试技巧Bochs内置了调试功能可以在启动时进入调试模式bochs -q debugger: enabled1常用调试命令c继续执行s单步执行break Main在Main函数设置断点info registers查看寄存器状态5. 深入理解内核线程工作原理完成基本功能后让我们深入了解一下GeekOS的线程机制。5.1 线程创建流程Start_Kernel_Thread函数的内部工作流程调用Allocate_Kernel_Thread分配线程结构体设置线程的初始上下文包括栈指针、指令指针等将线程加入就绪队列如果调度器已初始化可能触发线程切换5.2 线程调度GeekOS使用简单的优先级调度算法。关键数据结构struct Kernel_Thread { void* stackPointer; int priority; bool alive; // 其他成员... };调度器会从就绪队列中选择优先级最高的线程执行。我们的project0线程使用PRIORITY_NORMAL这是默认优先级。5.3 键盘中断处理键盘输入通过中断机制传递给系统硬件产生键盘中断IRQ1CPU跳转到中断处理程序从键盘控制器读取扫描码转换为Keycode并存入缓冲区Read_Key函数从缓冲区读取键值6. 扩展实验进一步提升你的Project0基础功能实现后可以尝试以下扩展6.1 添加命令历史功能修改project0函数实现上下箭头查看历史命令#define MAX_HISTORY 10 static char history[MAX_HISTORY][80]; static int history_count 0; void handle_up_arrow() { if(current_history 0) { current_history--; Clear_Line(); Print(history[current_history]); } }6.2 实现简单的行编辑添加退格键处理if(asciiCode \b) { Move_Cursor(-1, 0); Print( ); Move_Cursor(-1, 0); }6.3 多线程测试创建第二个线程观察调度行为void counter() { int i 0; while(1) { Print(Counter: %d\n, i); Yield(); } } // 在Main中启动 Start_Kernel_Thread(counter, 0, PRIORITY_NORMAL, false);记得在project0函数中也适当加入Yield()调用让出CPU。完成Project0只是GeekOS之旅的第一步。当你看到自己修改的内核成功运行那种成就感是难以言表的。建议在确保基础功能稳定后多尝试一些扩展实验这能帮助你更深入地理解操作系统的工作原理。如果在实验过程中遇到问题GeekOS的源码和Bochs的调试工具是你最好的老师——学会阅读源码和利用调试工具这比单纯完成作业更有价值。

更多文章