# os **Repository Path**: gsool/os ## Basic Information - **Project Name**: os - **Description**: 从零开始写一个操作系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-07 - **Last Updated**: 2026-06-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 从零开始写一个操作系统 — 俄罗斯方块引导游戏 > 一个运行在 x86 实模式下的俄罗斯方块游戏,从引导扇区开始,完全用 NASM 汇编编写。 --- ## 1. 项目简介 本项目实现了一个可以在计算机启动时直接运行的俄罗斯方块游戏。按下电源键后,经过 BIOS 引导 → 二级加载 → 游戏启动,无需任何操作系统。 ### 1.1 项目目标 - 理解计算机从按下电源键到执行自定义代码的完整流程 - 掌握 BIOS 中断调用(视频、磁盘、键盘、时钟) - 理解 MBR 引导扇区结构和二级引导原理 - 学习 NASM 汇编语言和 16 位实模式编程 - 实践双缓冲渲染、碰撞检测、伪随机数生成等游戏编程技术 ### 1.2 项目文件结构 ``` os/ ├── boot.asm # 二级引导加载器(512 字节,MBR) ├── boot.bin # 编译后的引导扇区 ├── tetris.asm # 俄罗斯方块游戏主程序(~860 行汇编) ├── tetris.bin # 编译后的游戏二进制 ├── tetris.img # 最终可启动镜像(boot.bin + tetris.bin) ├── build.bat # 编译脚本 ├── run.bat # 编译 + QEMU 运行 ├── flash-boot.c # 磁盘烧录工具(支持多扇区写入) ├── flash-boot.exe # 编译后的烧录工具 └── nasm.exe # NASM 汇编器 ``` --- ## 2. 预备知识 ### 2.1 x86 实模式 - 计算机刚开机时处于 **16 位实模式**(Real Mode) - 只能访问 1MB 内存(地址线 20 位) - 使用 16 位寄存器:`AX`, `BX`, `CX`, `DX`, `SI`, `DI`, `BP`, `SP` ### 2.2 BIOS 中断 BIOS 提供了基本的硬件操作接口,通过 `int` 指令调用: | 中断号 | 用途 | 本项目中的使用 | |--------|------|---------------| | `int 0x10` | 视频服务 | 设置文本模式、隐藏光标、输出字符 | | `int 0x13` | 磁盘服务 | 加载游戏代码到内存 | | `int 0x16` | 键盘服务 | 读取玩家输入 | ### 2.3 MBR 引导扇区 - **大小**:正好 512 字节 - **签名**:最后两个字节必须是 `0x55` `0xAA` - **加载地址**:BIOS 会把它加载到内存地址 `0x7C00` --- ## 3. 启动流程 ``` 按下电源键 ↓ BIOS 自检(POST),初始化硬件 ↓ 读取磁盘第一个扇区(boot.bin,512 字节)→ 内存 0x7C00 ↓ boot.asm(二级加载器)用 int 0x13 加载扇区 2-4 → 内存 0x7E00 ↓ 跳转到 0x7E00,执行 tetris.asm(游戏主程序) ↓ 游戏初始化:设置 80×25 文本模式、隐藏光标、初始化棋盘 ↓ 进入主循环:处理输入 → 重力下落 → 渲染画面 ``` ### 为什么需要二级引导? 引导扇区只有 512 字节,而俄罗斯方块游戏约 3.5KB。因此 `boot.asm` 的职责是**加载更大的代码**到内存,再跳转过去执行——这就是经典的二级引导模式。 --- ## 4. boot.asm — 二级加载器 `boot.asm` 是 BIOS 加载的第一个程序,运行在 `0x7C00`,职责很简单: ```asm [org 0x7C00] [bits 16] xor ax, ax ; 设置段寄存器 mov ds, ax mov es, ax mov ss, ax mov sp, 0x7C00 ; 栈向下生长 mov [drive], dl ; 保存 BIOS 传入的驱动器号 ; 用 int 0x13 从磁盘读取扇区 2-4 到内存 0x7E00 mov ah, 0x02 ; AH=0x02: 磁盘读取 mov al, 3 ; 读 3 个扇区 mov ch, 0 ; 柱面 0 mov cl, 2 ; 从扇区 2 开始(扇区 1 是自身) mov dh, 0 ; 磁头 0 mov dl, [drive] ; 驱动器号 mov bx, 0x7E00 ; 目标地址 int 0x13 jnc .ok ; 读取成功则跳转 ; 读取失败:在屏幕上显示 "EO" 提示错误 ... .ok: jmp 0x0000:0x7E00 ; 跳转到游戏代码 ``` 关键点: - `int 0x13, AH=0x02`:磁盘读取功能,通过寄存器指定读取位置和目标地址 - 加载地址 `0x7E00` 紧接在引导扇区之后(`0x7C00 + 512 = 0x7E00`) - 最后两字节填充 `dw 0xAA55` 形成 512 字节的引导签名 --- ## 5. tetris.asm — 俄罗斯方块游戏 游戏主程序运行在 `0x7E00`,是一个完整的 16 位汇编俄罗斯方块。 ### 5.1 核心特性 | 特性 | 说明 | |------|------| | 棋盘 | 10×20 格,每格占 2 个字符宽 | | 方块 | 7 种标准方块(I/O/T/S/Z/J/L),每种 4 个旋转状态 | | 渲染 | 双缓冲技术,无闪烁 | | 计分 | 1行=100, 2行=300, 3行=500, 4行=800 | | 定时 | 基于 BIOS tick 计数器(~18.2Hz)的自动下落 | ### 5.2 操作方式 | 按键 | 功能 | |------|------| | `← →` | 左右移动 | | `↑` | 旋转(含墙踢:尝试左移/右移后旋转) | | `↓` | 软降(加速下落) | | `空格` | 硬降(直接落底) | | `Q` | 重新开始 | ### 5.3 程序架构 ``` start(初始化) ↓ loop(主循环)──→ input(键盘输入)──→ gravity(重力下落)──→ render(渲染)──→ loop │ │ │ ├── 检测碰撞 → place(固定方块)→ clear_lines(消行)→ new_piece │ │ │ └── 非碰撞 → 方块下移一格 │ ├── ← → 移动 ├── ↑ 旋转(4 种方案尝试) ├── ↓ 软降 ├── 空格 硬降(循环下落直到碰撞) └── Q 重开 ``` ### 5.4 关键技术模块 **方块表示**:每种方块用 16 位掩码表示 4×4 矩阵,从高位到低位,1=有块,0=空。存储在 `pieces` 表中,共 7 种 × 4 旋转 × 2 字节 = 56 字节。 **碰撞检测**(`check_col`):将掩码的每一位映射到棋盘坐标,检查是否越界或与已有方块重叠。 **双缓冲渲染**(`render`):先将显存 `0xB800` 复制到缓冲区 `0x0600`,在缓冲区中绘制全部内容后,一次性写回显存,避免画面闪烁。 **消行处理**(`clear_lines`):两遍扫描——第一遍从底向上标记满行为 `0xFF`,第二遍压缩非满行下移并清零顶部空行。 **随机数生成**:线性同余法 `rng = rng × 25173 + 13849`,种子取自 BIOS tick 计数器。 ### 5.5 内存布局 ``` 0x00000 - 0x003FF 中断向量表 + BIOS 数据区 0x06000 - 0x06FFF 双缓冲区(BUFFER,4KB) 0x7C000 - 0x7C1FF 引导扇区(boot.asm) 0x7E000 - 0x7Exxx 游戏代码(tetris.asm) 0x8C000 - 0x8CD00 棋盘数据(BOARD,400 字节) 0xB8000 - 0xB8F9F 显存(VIDEO,80×25 文本模式) ``` --- ## 6. 编译与运行 ### 6.1 快速开始(推荐) 双击 `run.bat` 或在命令行执行: ```cmd run.bat ``` 它会自动编译并启动 QEMU 虚拟机运行游戏。 ### 6.2 手动编译 ```cmd build.bat ``` 执行以下步骤: ```bash nasm boot.asm -f bin -o boot.bin # 编译引导加载器 nasm tetris.asm -f bin -o tetris.bin # 编译游戏程序 ``` ### 6.3 生成可启动镜像 ```cmd copy /b boot.bin + tetris.bin tetris.img ``` `/b` 表示二进制拼接,将 boot.bin(512 字节)和 tetris.bin 拼接成 tetris.img。 ### 6.4 QEMU 运行 ```bash qemu-system-i386 -drive file=tetris.img,format=raw,if=floppy ``` `if=floppy` 模拟软盘,BIOS 会从软盘的第一个扇区引导。 ### 6.5 写入真实磁盘 > ⚠️ **警告:此操作会覆盖目标磁盘的前 N 个扇区,可能导致数据丢失!** ```cmd :: 以管理员身份运行 flash-boot.exe tetris.img 1 # 写入磁盘 1 ``` 工具会列出所有磁盘供选择,自动备份原有扇区,确认后写入。 --- ## 7. flash-boot.c — 磁盘烧录工具 将镜像文件(如 `tetris.img`)写入物理磁盘起始位置,支持多扇区写入。 ### 7.1 功能 - 列出系统所有物理磁盘(型号、大小) - 自动验证镜像的引导签名(`0x55AA`) - 写入前自动备份原扇区到文件 - 支持命令行参数和交互式两种模式 ### 7.2 写入流程 ``` 读取镜像文件 → 扇区对齐 → 验证签名 → 用户确认 → 备份原扇区 → 写入磁盘 ``` ### 7.3 编译 ```bash gcc flash-boot.c -o flash-boot.exe ``` 需要 Windows API(`CreateFileA`、`DeviceIoControl` 等),仅支持 Windows 平台。 --- ## 8. 常见问题 **Q: 为什么要用 NASM 而不是 GCC?** 引导扇区和游戏都必须是**纯二进制**格式,没有 ELF/PE 等格式头。C 编译器产出的目标文件无法直接作为引导代码。 **Q: 为什么用软盘格式而不是硬盘?** `if=floppy` 让 QEMU 将镜像视为软盘,BIOS 会从软盘引导。使用 `if=ide` 也可以模拟硬盘,但需要确保镜像的第一个扇区是有效的 MBR。 **Q: 512 字节的引导扇区能做什么?** 引导扇区本身只做一件事——**加载后续代码**。真正的游戏逻辑在 tetris.bin 中,通过二级引导加载到内存后执行。这是所有现代操作系统引导的基础模式。 **Q: 为什么需要双缓冲?** 直接写显存会导致画面撕裂和闪烁。先在内存中绘制完整的一帧,再一次性复制到显存,画面过渡更平滑。 **Q: 游戏卡顿怎么办?** 调整 `DROP_INIT` 值可以改变初始下落速度。值越大,下落间隔越长(速度越慢)。 --- ## 9. 学习路线 本项目涵盖的知识点可以延伸到操作系统开发的多个方向: 1. **已完成** — 引导扇区、二级加载、BIOS 中断、文本模式显示、键盘输入、定时器 2. **下一步** — 进入 32 位保护模式(设置 GDT、打开 A20 地址线) 3. **内核开发** — 用 C 语言编写内核主体,汇编作为入口 4. **内存管理** — 实现分页和内存分配器 5. **文件系统** — 实现 FAT12/ext2 等简单文件系统 6. **图形模式** — 从文本模式切换到 VGA 图形模式 --- > 从一个 512 字节的引导扇区到一个可玩的俄罗斯方块——这就是底层编程的魅力。