学习内容
- 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:使用普通内存访问指令和统一的内存地址空间