4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで作るx86_64 自作OS入門シリーズ
Part1【環境構築】 | Part2【no_std】 | Part3【Hello World】| Part4【VGA】 | Part5【割り込み】 | Part6【ページング】 | Part7【ヒープ】 | Part8【マルチタスク】| Part9【ファイルシステム】 | Part10【ELFローダー】 | Part11【シェル】 | Part12【完結】

はじめに

ここまでVGAテキストバッファに文字を出してきましたが、まだ基本的な機能しかありません。

今回は80x25の世界をもっと深掘りして、カーソル制御やより高度な画面制御を実装していきます。

目次

  1. VGAの仕組みをもっと深く
  2. ハードウェアカーソル
  3. 色のデモ
  4. 画面制御の改良
  5. 起動画面を作る

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)では、いよいよ割り込み処理を実装します。キーボードが押されても何も起きない状態から脱却!

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?