anos/src/vga_buffer.rs

193 lines
5.9 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 用 volatile 以防止我们针对内存的操作被优化掉
// 因为我们是通过对内存地址 0xb8000 写入来操作 VGA Buffer 的
// 这实际上是通过内存操作的副作用来实现的
// 但由于我们从未访问过这段内存,所以它可能会在未来的 Rust 编译器中被优化掉
// 为了告诉编译器,这部分针对内存的操作是我们故意的,就需要用到 volatile crate
use core::fmt;
use lazy_static::lazy_static;
use spin::Mutex;
use volatile::Volatile;
#[allow(dead_code)] // 允许未使用的代码
#[derive(Debug, Clone, Copy, PartialEq, Eq)] // 启用 copy 语义
#[repr(u8)] // 每个 enum variant 都以 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)] // 为了保证 ColorCode 和 u8 有同样的数据布局
struct ColorCode(u8);
impl ColorCode {
fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)] // 使数据结构的布局和 C 一样,这样才能保证每个成员的顺序一致
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}
const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;
#[repr(transparent)]
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
pub struct Writer {
column_position: usize, // 当前光标所在列
color_code: ColorCode, // 当前颜色代码
buffer: &'static mut Buffer, // VGA Buffer 的引用,生命周期是 'static
}
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
// 如果碰到 '\n' 则创建新行
b'\n' => self.new_line(),
byte => {
// 如果列数超出限定的阈值,则创建新行
if self.column_position >= BUFFER_WIDTH {
self.new_line();
}
let row = BUFFER_HEIGHT - 1;
let col = self.column_position;
let color_code = self.color_code;
// 将 ScreenChar 写入到 Buffer 的相应位置
self.buffer.chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code,
});
self.column_position += 1;
}
}
}
fn new_line(&mut self) {
// 将所有字符上移一行
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
let character = self.buffer.chars[row][col].read();
self.buffer.chars[row - 1][col].write(character);
}
}
// 清空最下面的一行
self.clear_row(BUFFER_HEIGHT - 1);
// 将当前列的位置设为 0
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..BUFFER_WIDTH {
self.buffer.chars[row][col].write(blank);
}
}
pub fn write_string(&mut self, s: &str) {
for byte in s.bytes() {
// Rust 字符串是以 UTF-8 编码的,然而 VGA Buffer 只支持 ASCII字符所以有可能存在无法打印的字符
match byte {
// 当在 ASCII 字符范围内时,打印它
0x20..=0x7e | b'\n' => self.write_byte(byte),
// 否则打印 ■
_ => self.write_byte(0xfe),
}
}
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_string(s);
Ok(())
}
}
// 将 Writer 设置为静态以便调用
lazy_static! { // 内置的 static 会用到标准库里的一些东西,所以这里用 lazy_static
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer { // 用互斥锁来防止冲突
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }
});
}
// 将宏导出到 crate 的根,这样我们就可以用
// use crate::println
// 来使用它
#[macro_export]
macro_rules! print { // print!()
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println { // println!()
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
// 由于宏需要从外部调用这个函数,所以它必须是公开的
// 用 #[doc(hidden)] 来阻止它生成文档
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
use x86_64::instructions::interrupts;
interrupts::without_interrupts(|| {
WRITER.lock().write_fmt(args).unwrap();
});
}
#[test_case]
fn test_println_simple() {
// 测试单行 println!
println!("test_println_simple output");
}
#[test_case]
fn test_println_many() {
// 测试多行 println!
for _ in 0..10 {
println!("test_println_many output");
}
}
#[test_case]
fn test_println_output() {
// 测试字符是否真的打印到了屏幕上
use core::fmt::Write;
use x86_64::instructions::interrupts;
let s = "Some test string that fits on a single line";
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writeln!(writer, "\n{}", s).expect("writeln failed");
for (i, c) in s.chars().enumerate() {
let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read();
assert_eq!(char::from(screen_char.ascii_character), c);
}
});
}