Rustで作るx86_64 自作OS入門シリーズ
Part1【環境構築】 | Part2【no_std】 | Part3【Hello World】| Part4【VGA】 | Part5【割り込み】 | Part6【ページング】 | Part7【ヒープ】 | Part8【マルチタスク】| Part9【ファイルシステム】 | Part10【ELFローダー】 | Part11【シェル】 | Part12【完結】
はじめに
ここまでVGAテキストバッファに文字を出してきましたが、まだ基本的な機能しかありません。
今回は80x25の世界をもっと深掘りして、カーソル制御やより高度な画面制御を実装していきます。
目次
VGAの仕組みをもっと深く
VGAコントローラ
VGAはI/Oポートを通じて制御します。主要なポート:
| ポート | 用途 |
|---|---|
| 0x3C0 | 属性コントローラ |
| 0x3C4 | シーケンサ |
| 0x3CE | グラフィックスコントローラ |
| 0x3D4 | CRTCアドレス |
| 0x3D5 | CRTCデータ |
CRTコントローラ(CRTC)
CRTCは画面の走査を制御します。カーソル位置もCRTCで管理されています。
主なレジスタ:
| インデックス | レジスタ名 | 用途 |
|---|---|---|
| 0x0A | Cursor Start | カーソル開始行 |
| 0x0B | Cursor End | カーソル終了行 |
| 0x0E | Cursor Location High | カーソル位置(上位バイト) |
| 0x0F | Cursor Location Low | カーソル位置(下位バイト) |
ハードウェアカーソル
VGAにはハードウェアカーソルがあります。点滅するやつです。
カーソル位置の設定
/// カーソル位置を設定
pub fn set_cursor_position(x: usize, y: usize) {
let pos = y * VGA_WIDTH + x;
unsafe {
// CRTCアドレスポートに0x0F(カーソル位置下位)を書き込む
outb(0x3D4, 0x0F);
// CRTCデータポートに下位バイトを書き込む
outb(0x3D5, (pos & 0xFF) as u8);
// CRTCアドレスポートに0x0E(カーソル位置上位)を書き込む
outb(0x3D4, 0x0E);
// CRTCデータポートに上位バイトを書き込む
outb(0x3D5, ((pos >> 8) & 0xFF) as u8);
}
}
unsafe fn outb(port: u16, value: u8) {
core::arch::asm!("out dx, al", in("dx") port, in("al") value);
}
カーソルの有効化/無効化
/// カーソルを有効化
pub fn enable_cursor(cursor_start: u8, cursor_end: u8) {
unsafe {
outb(0x3D4, 0x0A);
outb(0x3D5, (inb(0x3D5) & 0xC0) | cursor_start);
outb(0x3D4, 0x0B);
outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}
}
/// カーソルを無効化
pub fn disable_cursor() {
unsafe {
outb(0x3D4, 0x0A);
outb(0x3D5, 0x20); // ビット5を設定するとカーソル非表示
}
}
unsafe fn inb(port: u16) -> u8 {
let value: u8;
core::arch::asm!("in al, dx", in("dx") port, out("al") value);
value
}
Writerにカーソル連動を追加
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
if self.column_position >= VGA_WIDTH {
self.new_line();
}
let row = self.row_position;
let col = self.column_position;
unsafe {
core::ptr::write_volatile(
&mut self.buffer.chars[row][col] as *mut ScreenChar,
ScreenChar {
ascii_character: byte,
color_code: self.color_code,
}
);
}
self.column_position += 1;
// カーソル位置を更新
set_cursor_position(self.column_position, self.row_position);
}
}
}
fn new_line(&mut self) {
// ... スクロール処理 ...
self.column_position = 0;
// カーソル位置を更新
set_cursor_position(self.column_position, self.row_position);
}
}
色のデモ
16色全部表示してみましょう!
fn color_demo() {
println!("=== Color Demo ===");
let colors = [
(Color::Black, "Black"),
(Color::Blue, "Blue"),
(Color::Green, "Green"),
(Color::Cyan, "Cyan"),
(Color::Red, "Red"),
(Color::Magenta, "Magenta"),
(Color::Brown, "Brown"),
(Color::LightGray, "LightGray"),
(Color::DarkGray, "DarkGray"),
(Color::LightBlue, "LightBlue"),
(Color::LightGreen, "LightGreen"),
(Color::LightCyan, "LightCyan"),
(Color::LightRed, "LightRed"),
(Color::Pink, "Pink"),
(Color::Yellow, "Yellow"),
(Color::White, "White"),
];
for (color, name) in colors.iter() {
{
let mut writer = WRITER.lock();
writer.set_color(*color, Color::Black);
}
println!("{}", name);
}
// 白に戻す
{
let mut writer = WRITER.lock();
writer.set_color(Color::White, Color::Black);
}
}
背景色も変えてみる
fn background_demo() {
let backgrounds = [
Color::Blue,
Color::Green,
Color::Cyan,
Color::Red,
];
for bg in backgrounds.iter() {
{
let mut writer = WRITER.lock();
writer.set_color(Color::White, *bg);
}
print!(" "); // 背景色が見えるように空白を出力
}
{
let mut writer = WRITER.lock();
writer.set_color(Color::White, Color::Black);
}
println!();
}
画面制御の改良
位置指定出力
特定の位置に文字を出力する機能を追加:
impl Writer {
/// 指定位置に文字を出力
pub fn write_at(&mut self, x: usize, y: usize, byte: u8) {
if x >= VGA_WIDTH || y >= VGA_HEIGHT {
return;
}
unsafe {
core::ptr::write_volatile(
&mut self.buffer.chars[y][x] as *mut ScreenChar,
ScreenChar {
ascii_character: byte,
color_code: self.color_code,
}
);
}
}
/// 指定位置に文字列を出力
pub fn write_string_at(&mut self, x: usize, y: usize, s: &str) {
let mut current_x = x;
for byte in s.bytes() {
if current_x >= VGA_WIDTH {
break;
}
match byte {
0x20..=0x7e => {
self.write_at(current_x, y, byte);
current_x += 1;
}
_ => {
self.write_at(current_x, y, 0xfe);
current_x += 1;
}
}
}
}
/// カーソル位置を設定
pub fn set_position(&mut self, x: usize, y: usize) {
self.column_position = x.min(VGA_WIDTH - 1);
self.row_position = y.min(VGA_HEIGHT - 1);
set_cursor_position(self.column_position, self.row_position);
}
}
矩形描画
impl Writer {
/// 矩形を描画(ボックス)
pub fn draw_box(&mut self, x: usize, y: usize, width: usize, height: usize) {
// 角
self.write_at(x, y, 0xC9); // ╔
self.write_at(x + width - 1, y, 0xBB); // ╗
self.write_at(x, y + height - 1, 0xC8); // ╚
self.write_at(x + width - 1, y + height - 1, 0xBC); // ╝
// 上下の線
for i in 1..width-1 {
self.write_at(x + i, y, 0xCD); // ═
self.write_at(x + i, y + height - 1, 0xCD); // ═
}
// 左右の線
for i in 1..height-1 {
self.write_at(x, y + i, 0xBA); // ║
self.write_at(x + width - 1, y + i, 0xBA); // ║
}
}
}
起動画面を作る
かっこいい起動画面を作りましょう!
fn boot_screen() {
vga::init();
// タイトルを中央に表示
let title = "My OS";
let subtitle = "Version 0.1.0";
{
let mut writer = vga::WRITER.lock();
// 画面をクリア
writer.clear_screen();
// ボックスを描画
writer.draw_box(20, 8, 40, 10);
// タイトル(中央揃え)
writer.set_color(vga::Color::LightCyan, vga::Color::Black);
let title_x = 40 - title.len() / 2;
writer.write_string_at(title_x, 10, title);
// サブタイトル
writer.set_color(vga::Color::LightGray, vga::Color::Black);
let subtitle_x = 40 - subtitle.len() / 2;
writer.write_string_at(subtitle_x, 12, subtitle);
// 著作権表示
writer.set_color(vga::Color::DarkGray, vga::Color::Black);
writer.write_string_at(25, 14, "(C) 2025 Your Name");
// 白に戻す
writer.set_color(vga::Color::White, vga::Color::Black);
}
// 起動メッセージ
{
let mut writer = vga::WRITER.lock();
writer.set_position(0, 20);
}
println!("Booting...");
println!("[OK] VGA initialized");
println!("[OK] Serial port initialized");
println!("[ ] Interrupt handlers...");
}
80x25の制約
VGAテキストモードは80列×25行固定です。この制約の中で工夫する必要があります。
使える文字数
- 1行あたり: 80文字
- 総文字数: 80 × 25 = 2000文字
- メモリ: 2000 × 2 = 4000バイト(文字 + 属性)
拡張ASCII文字
コードページ437には罫線文字があります:
┌─┐ ╔═╗
│ │ ║ ║
└─┘ ╚═╝
これらを使うとそれなりにUIっぽくなります。
まとめ
Part4では以下を達成しました:
- VGAコントローラの仕組み
- ハードウェアカーソルの制御
- 16色カラー表示
- 位置指定出力
- 矩形(ボックス)描画
- 起動画面の作成
次回(Part5)では、いよいよ割り込み処理を実装します。キーボードが押されても何も起きない状態から脱却!
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!