VGA Text Mode

This commit is contained in:
Sainnhe Park 2021-12-09 10:34:40 +08:00
parent a5c98f5732
commit aab190baea
6 changed files with 212 additions and 11 deletions

View File

@ -4,3 +4,6 @@ target = "x86_64-anos.json"
[unstable]
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]
[target.'cfg(target_os = "none")']
runner = "bootimage runner"

24
Cargo.lock generated
View File

@ -7,6 +7,9 @@ name = "anos"
version = "0.1.0"
dependencies = [
"bootloader",
"lazy_static",
"spin",
"volatile",
]
[[package]]
@ -14,3 +17,24 @@ name = "bootloader"
version = "0.9.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c452074efc3c0bfb241fb7bc87df04741c7c85e926f6a07c05f8fbd6008240"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
"spin",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "volatile"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945"

View File

@ -12,3 +12,9 @@ panic = "abort" # 禁用 panic 时的栈展开
[dependencies]
bootloader = "0.9.8"
volatile = "0.2.6"
spin = "0.5.2"
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]

4
setup.sh Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
rustup component list --installed
cargo install bootimage

View File

@ -3,6 +3,8 @@
use core::panic::PanicInfo;
mod vga_buffer;
// 定义一个 byte string 类型的静态变量
static HELLO: &[u8] = b"Hello World!";
@ -11,16 +13,7 @@ static HELLO: &[u8] = b"Hello World!";
// 函数名为 _start 是因为大多数系统默认用这个名字作为入口点名称
// -> ! 代表这是一个发散函数,不允许有任何返回值,因为它不会被任何函数调用,而是将直接被 bootloader 调用
pub extern "C" fn _start() -> ! {
// VGA Buffer 从 0xb8000 开始
let vga_buffer = 0xb8000 as *mut u8;
// 迭代 HELLO, 通过裸指针 vga_buffer 来定位待写入的缓冲区地址
for (i, &byte) in HELLO.iter().enumerate() {
unsafe {
*vga_buffer.offset(i as isize * 2) = byte; // ASCII 码字节
*vga_buffer.offset(i as isize * 2 + 1) = 0xb; // 颜色字节, 0xb 代表青色
}
}
println!("Hello World{}", "!");
loop {}
}
@ -28,6 +21,7 @@ pub extern "C" fn _start() -> ! {
#[panic_handler]
// panic! 时将会进行栈展开,执行各种 drop 函数来回收垃圾
// 禁用标准库之后,这些东西就都没有了,我们需要把它重写一遍
fn panic(_info: &PanicInfo) -> ! {
fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}

170
src/vga_buffer.rs Normal file
View File

@ -0,0 +1,170 @@
// 用 volatile 以防止我们针对内存的操作被优化掉
// 因为我们是通过对内存地址 0xb8000 写入来操作 VGA Buffer 的
// 这实际上是通过内存操作的副作用来实现的
// 但由于我们从未访问过这段内存,所以它可能会在未来的 Rust 编译器中被优化掉
// 为了告诉编译器,这部分针对内存的操作是我们故意的,就需要用到 volatile crate
use volatile::Volatile;
use core::fmt;
use lazy_static::lazy_static;
use spin::Mutex;
#[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(())
}
}
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) }
});
}
pub fn print_something() {
use core::fmt::Write;
let mut writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
// VGA Buffer 从 0xb8000 开始
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
writer.write_byte(b'H');
writer.write_string("ello ");
writer.write_string("Wörld!");
write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap();
}
// 将宏导出到 crate 的根,这样我们就可以用
// use crate::println
// 来使用它
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! 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;
WRITER.lock().write_fmt(args).unwrap();
}