四、VGA 文本缓冲区基础

学习内容

  • VGA 文本模式原理
  • 内存映射 I/O
  • 颜色编码系统
  • 字符显示机制

学习目标 🎯

  • 实现基本的字符输出函数
  • 创建彩色文本显示

第一部分:创建 VGA 缓冲区模块

1、创建 VGA 缓冲模块代码

rust 复制代码
// 简单的VGA文本缓冲区实现
// 不依赖外部库,直接使用内联汇编

#![no_std]
#![feature(asm)]

use core::arch::asm;
use core::fmt;

// VGA文本缓冲区的内存地址
const VGA_BUFFER: *mut u8 = 0xb8000 as *mut u8;

// 屏幕尺寸
const SCREEN_WIDTH: usize = 80;
const SCREEN_HEIGHT: usize = 25;

// 颜色代码
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Color {
    Black = 0,
    Blue = 1,
    Green = 2,
    Cyan = 3,
    Red = 4,
    Magenta = 5,
    Brown = 6,
    LightGray = 7,
    DarkGray = 8,
    LightBlue = 9,
    LightGreen = 10,
    LightCyan = 11,
    LightRed = 12,
    Pink = 13,
    Yellow = 14,
    White = 15,
}

// 颜色组合
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);

impl ColorCode {
    const fn new(foreground: Color, background: Color) -> Self {
        ColorCode((background as u8) << 4 | (foreground as u8))
    }
}

// VGA文本字符
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
struct ScreenChar {
    ascii_character: u8,
    color_code: ColorCode,
}

// VGA文本缓冲区
#[repr(transparent)]
struct Buffer {
    chars: [[ScreenChar; SCREEN_WIDTH]; SCREEN_HEIGHT],
}

// VGA写入器
pub struct VgaWriter {
    column_position: usize,
    color_code: ColorCode,
    buffer: &'static mut Buffer,
}

impl VgaWriter {
    // 创建新的VGA写入器
    pub fn new() -> Self {
        VgaWriter {
            column_position: 0,
            color_code: ColorCode::new(Color::White, Color::Black),
            buffer: unsafe { &mut *(VGA_BUFFER as *mut Buffer) },
        }
    }

    // 写入一个字符
    pub fn write_char(&mut self, c: u8) {
        match c {
            b'\n' => self.new_line(),
            c => {
                if self.column_position >= SCREEN_WIDTH {
                    self.new_line();
                }

                let row = SCREEN_HEIGHT - 1;
                let col = self.column_position;

                self.buffer.chars[row][col] = ScreenChar {
                    ascii_character: c,
                    color_code: self.color_code,
                };
                self.column_position += 1;
            }
        }
    }

    // 写入字符串
    pub fn write_str(&mut self, s: &str) {
        for byte in s.bytes() {
            self.write_char(byte);
        }
    }

    // 换行
    fn new_line(&mut self) {
        for row in 1..SCREEN_HEIGHT {
            for col in 0..SCREEN_WIDTH {
                let character = self.buffer.chars[row][col];
                self.buffer.chars[row - 1][col] = character;
            }
        }
        self.clear_row(SCREEN_HEIGHT - 1);
        self.column_position = 0;
    }

    // 清除一行
    fn clear_row(&mut self, row: usize) {
        let blank = ScreenChar {
            ascii_character: b' ',
            color_code: self.color_code,
        };
        for col in 0..SCREEN_WIDTH {
            self.buffer.chars[row][col] = blank;
        }
    }

    // 设置颜色
    pub fn set_color(&mut self, foreground: Color, background: Color) {
        self.color_code = ColorCode::new(foreground, background);
    }

    // 移动光标
    pub fn set_cursor_position(&mut self, x: usize, y: usize) {
        if x < SCREEN_WIDTH && y < SCREEN_HEIGHT {
            self.column_position = x;
            // 注意:这里只更新了x位置,完整实现还需要更新y位置
            // 并且需要与硬件光标同步
            let pos = y * SCREEN_WIDTH + x;
            unsafe {
                // 发送光标位置到VGA控制器
                // 高位
                write_port(0x3D4, 0x0E);
                write_port(0x3D5, ((pos >> 8) & 0xFF) as u8);
                // 低位
                write_port(0x3D4, 0x0F);
                write_port(0x3D5, (pos & 0xFF) as u8);
            }
        }
    }
}

// 实现fmt::Write trait
impl fmt::Write for VgaWriter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.write_str(s);
        Ok(())
    }
}

// 写入端口
unsafe fn write_port(port: u16, data: u8) {
    asm!("out dx, al", in("dx") port, in("al") data);
}

// 读取端口
unsafe fn read_port(port: u16) -> u8 {
    let result: u8;
    asm!("in al, dx", out("al") result, in("dx") port);
    result
}

// 打印宏
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => {{
        use core::fmt::Write;
        let mut writer = $crate::vga_buffer_simple::VgaWriter::new();
        let _ = write!(writer, $($arg)*);
    }};
}

#[macro_export]
macro_rules! println {
    () => {{
        $crate::print!("\n")
    }};
    ($($arg:tt)*) => {{
        $crate::print!($($arg)*);
        $crate::print!("\n")
    }};
}

// 初始化函数
pub fn init() {
    // 清除屏幕
    let mut writer = VgaWriter::new();
    for row in 0..SCREEN_HEIGHT {
        writer.clear_row(row);
    }
    writer.column_position = 0;
}

2、更新主代码

rust 复制代码
// 1. 禁用标准库
#![no_std] 
#![feature(asm)]
#![feature(panic_info_message)]

// 2. 禁用Rust运行时(无main函数)
#![no_main]

// 3. 导入必要的模块
mod kernel_module;
mod vga_buffer_simple;

use core::arch::asm;
use core::fmt::Write;
use core::panic::PanicInfo;
use kernel_module::*;
use vga_buffer_simple::*;

// 自定义入口点
#[no_mangle]  // 防止 Rust 改变函数名
pub extern "C" fn _start() -> ! {
    // 初始化 VGA 缓冲区
    vga_buffer_simple::init();

    // 输出基本文本
    println!("Welcome to Blog OS!");
    println!("This is a simple operating system written in Rust.");

    // 演示彩色文本
    {{
        let mut writer = vga_buffer_simple::VgaWriter::new();
        writer.set_color(vga_buffer_simple::Color::Red, vga_buffer_simple::Color::Black);
        writer.write_str("Red ");
        writer.set_color(vga_buffer_simple::Color::Green, vga_buffer_simple::Color::Black);
        writer.write_str("Green ");
        writer.set_color(vga_buffer_simple::Color::Blue, vga_buffer_simple::Color::Black);
        writer.write_str("Blue ");
        writer.set_color(vga_buffer_simple::Color::White, vga_buffer_simple::Color::Black);
        writeln!(writer, "");
    }}

    // 演示光标位置控制
    {{
        let mut writer = vga_buffer_simple::VgaWriter::new();
        writer.set_cursor_position(10, 5);
        writer.write_str("这里是光标控制的演示");
    }}

    // 内核模块演示
    kernel_module_demo();

    // 内核主循环
    loop {{
        // 处理中断、调度任务等
        unsafe {{
            // 简单的hlt指令
            asm!("hlt");
        }}
    }}
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // 输出 panic 信息到 VGA 缓冲区
    println!("\n!! 内核崩溃 !!");
    if let Some(location) = info.location() {
        println!("位置: {}:{}:{}",
            location.file(),
            location.line(),
            location.column());
    }
    if let Some(message) = info.message() {
        println!("信息: {}", message);
    }

    // 无限循环
    loop {}
}

fn kernel_module_demo() {
    let vga_module = KernelModule::new("VGA_DRIVER");
    
    match register_module(vga_module) {
        Ok(()) => {
            // 模块注册成功
        }
        Err(e) => {
            // 处理错误
        }
    }
    
    match initialize_all_modules() {
        Ok(()) => {
            // 所有模块初始化成功
        }
        Err(e) => {
            // 处理初始化错误
        }
    }
}

3、更新依赖

toml 复制代码
[package]
name = "blog_os"
version = "0.1.0"
edition = "2021"

# [package.metadata.bootloader]
# target configuration moved to command line arguments

[dependencies]
bootloader = "0.9.8"

[build-dependencies]
bootimage = "0.10.3"         # 构建时依赖

# 配置构建标准库
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

[unstable]
build-std = ["core", "compiler_builtins"]

[target.'cfg(target_os = "none")']
runner = "bootimage runner"

4、更新 x86_64-blog_os.json

之前使用的 x86_64-blog_os.json 和我安装的 bootloader 版本并不兼容,经过尝试后,现在的 x86_64-blog_os.json 如下:

json 复制代码
{
  "llvm-target": "x86_64-unknown-none",
  "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
  "arch": "x86_64",
  "target-endian": "little",
  "target-pointer-width": "64",
  "target-c-int-width": "32",
  "os": "none",
  "executables": true,
  "linker-flavor": "ld.lld",
  "linker": "rust-lld",
  "panic-strategy": "abort",
  "disable-redzone": true,
  "features": "-mmx,-sse,+soft-float"
}

当前 bootloader 版本 0.9.8

第二部分:测试与验证

构建指令

bash 复制代码
cargo bootimage --target x86_64-blog_os.json

使用QEMU运行生成的bootimage镜像文件

bash 复制代码
qemu-system-x86_64 -drive format=raw,file=target/x86_64-blog_os/debug/bootimage-blog_os.bin

这时我们得到了

乱码是中文hhh

第三部分:相关知识

VGA 文本模式

VGA 文本模式是一种早期计算机显示标准,也是我们在开发操作系统时经常接触的基础显示模式。以下是其核心原理:

1. 内存映射机制

VGA 文本模式使用固定的内存地址 0xB8000 作为显示缓冲区。当我们向这个内存区域写入数据时,内容会直接显示在屏幕上。

2. 字符表示方式

每个字符由 两个字节 组成:

  • 第一个字节:字符的 ASCII 码(这就是为什么它不支持中文等多字节字符)
  • 第二个字节:字符的属性(前景色、背景色、闪烁等)
3. 屏幕布局

标准 VGA 文本模式的分辨率为 80 列 × 25 行,共 2000 个字符位置。整个显示缓冲区大小为 4000 字节(2000 × 2)。

4. 颜色机制

属性字节的高 4 位表示背景色,低 4 位表示前景色,总共支持 16 种背景色和 16 种前景色。

5. 光标控制

通过向 VGA 控制器的特定端口发送命令,可以控制光标的位置和外观。

6. 为什么中文显示乱码?

因为每个字符只能用一个字节表示(ASCII 码),而中文等非ASCII字符通常需要多个字节,所以在VGA文本模式下会显示为乱码。

内存映射 I/O

内存映射 I/O(Memory-Mapped I/O,简称 MMIO)是一种计算机系统中 CPU 与外部设备(如显卡、网卡等)通信的重要方式。它的核心原理是:

1. 基本概念

将外部设备的寄存器或缓冲区映射到 CPU 的内存地址空间 ,使得 CPU 可以像访问普通内存一样读写这些设备寄存器,而不需要使用专门的 I/O 指令(如 x86 架构的 IN/OUT 指令)。

2. 工作原理
  • 硬件层面:北桥芯片(或内存控制器)负责将特定的内存地址范围重定向到外部设备,而不是物理内存。
  • 软件层面:当 CPU 访问这些映射的地址时,实际上是在与外部设备通信,而非操作内存。
3. 与 VGA 文本模式的关系

我们之前讨论的 VGA 文本模式就是内存映射 I/O 的典型应用:

  • VGA 控制器将显示缓冲区映射到地址 0xB8000
  • CPU 向 0xB8000 地址写入数据,实际上是在向 VGA 控制器发送显示命令
  • VGA 控制器会自动将这些数据渲染到屏幕上
4. 优势
  • 编程简化 :可以使用普通的内存访问指令(如 mov)操作设备,无需学习专门的 I/O 指令
  • 性能优化:可以利用 CPU 的缓存机制提高访问效率
  • 灵活性更高:设备寄存器可以被映射到任意内存地址,不受 I/O 端口数量限制
5. 与端口映射 I/O 的对比
  • 端口映射 I/O :使用专门的 I/O 指令和独立的 I/O 地址空间(如 x86 的 IN/OUT 指令)

  • 内存映射 I/O:使用普通内存访问指令和统一的内存地址空间

相关推荐
寻月隐君16 小时前
Rust 实战:从零构建一个多线程 Web 服务器
后端·rust·github
受之以蒙19 小时前
Rust & WebAssembly 性能调优指南:从毫秒级加速到KB级瘦身
笔记·rust·webassembly
Vallelonga1 天前
关于 Rust 异步(无栈协程)的相关疑问
开发语言·后端·rust
q567315231 天前
Rust爬虫与代理池技术解析
开发语言·爬虫·python·rust
RustFS1 天前
如何用 Trae + RustFS MCP 实现自然语言对对象存储的操作?
rust·trae
浪费笔墨1 天前
基于rust的RGBA颜色混合
rust
咸甜适中2 天前
Rust语言序列化和反序列化vec<u8>,serde库Serialize, Deserialize,bincode库(2025年最新解决方案详细使用)
开发语言·后端·rust
受之以蒙2 天前
web-sys进阶:事件处理、异步操作与 Web API 实践
笔记·rust·webassembly
寻月隐君2 天前
Rust NFT 开发实战:构建生产级的 Pinata IPFS 自动化上传工具
后端·rust·github