「30日でできる!OS自作入門」をRustで。22日目

Posted on August 1, 2019 , Tags: OS自作入門, OS, Rust

「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は22日目の内容。

スタック例外を表示する

以下のようなコードをアプリケーションとして実行することを考える。

a は通常の配列の値だが、bcは無理やり配列からはみだしたメモリにアクセスしている。
実行してみるとAのみ表示された。

bug1

スタック例外を表示できるようにして、このようなアプリケーションを作ってしまった場合に気づけるようにする

QEMUでしか起動していないが、本にある通り、QEMUのバグのせいなのか、動作確認はできていない。動いていることにして、次にいく。

アプリケーションを強制停止できるようにする

アプリケーションが無限ループになっている場合など、OSに強制的に制御を戻したい場合に強制停止できるようにする。 これまでの資産が使えるので本にならってShift+F1で強制停止できるようにした。

アプリが動いてないときに誤動作しないように、アプリからOSにもどるときはss0がかならず0になるようにする。

動作確認

試しにアプリケーションコードとしてRustで以下のようにしてみる。

するとhelloを表示した時点で表示が止まるもののShift+F1でコンソールにもどってこれる。

強制停止

アプリケーションのメタ情報を読み込む

例えば、以下のようなアプリケーションを実行する。

すると何も表示されない。
これはデータセグメントに本来あるべきデータが現状ないからである。これを修正する。

// console.rs
    pub fn cmd_app<'a>(&mut self, filename: &'a [u8], fat: &[u32; MAX_FAT]) {
        let memman = unsafe { &mut *(MEMMAN_ADDR as *mut MemMan) };
        let mut finfo = search_file(filename);
        if finfo.is_none() && filename.len() > 1 && filename[filename.len() - 2] != b'.' {
            let mut filename_ext = [b' '; MAX_CMD + 4];
            let filename_ext = &mut filename_ext[0..(filename.len() + 4)];
            filename_ext[..filename.len()].copy_from_slice(filename);
            filename_ext[filename.len()] = b'.';
            filename_ext[filename.len() + 1] = b'h';
            filename_ext[filename.len() + 2] = b'r';
            filename_ext[filename.len() + 3] = b'b';
            finfo = search_file(filename_ext);
        }
        if finfo.is_none() {
            self.display_error("Bad Command");
            return;
        }
        let finfo = finfo.unwrap();
        let content_addr = memman.alloc_4k(finfo.size).unwrap() as usize;
        finfo.load_file(content_addr, fat, ADR_DISKIMG + 0x003e00);

        // kernel.ldを使ってリンクされたファイルのみ実行可能
        let mut app_eip = 0;
        let content_gdt = 1003;
        let app_gdt = 1004;
        let mut app_mem_addr = 0;
        if finfo.size >= 8 {
            // 4から7バイト目で判定
            let bytes = unsafe { *((content_addr + 4) as *const [u8; 4]) };
            if bytes == *b"Hari" {
                app_eip = 0x1b;
                // データセグメントの大きさ指定などメタ情報の読み込み
                let segment_size = unsafe { *((content_addr + 0x0000) as *const usize) };
                let esp = unsafe { *((content_addr + 0x000c) as *const usize) };
                let data_size = unsafe { *((content_addr + 0x0010) as *const usize) };
                let content_data_addr = unsafe { *((content_addr + 0x0014) as *const usize) };

                let app_mem_addr = memman.alloc_4k(segment_size as u32).unwrap() as usize;
                let ptr = unsafe { &mut *(CS_BASE_ADDR as *mut usize) };
                *ptr = app_mem_addr;

                let gdt = unsafe { &mut *((ADR_GDT + content_gdt * 8) as *mut SegmentDescriptor) };
                *gdt = SegmentDescriptor::new(
                    finfo.size - 1,
                    content_addr as i32,
                    AR_CODE32_ER + 0x60,
                );
                let gdt = unsafe { &mut *((ADR_GDT + app_gdt * 8) as *mut SegmentDescriptor) };
                *gdt = SegmentDescriptor::new(
                    segment_size as u32 - 1,
                    app_mem_addr as i32,
                    AR_DATA32_RW + 0x60,
                );

                // ファイル内のデータをデータセグメントにコピー
                for i in 0..data_size {
                    let app_ptr = unsafe { &mut *((app_mem_addr + esp + i) as *mut u8) };
                    *app_ptr = unsafe { *((content_addr + content_data_addr + i) as *const u8) };
                }
            }
        }

        // hrb形式の場合のみ実行
        if app_eip > 0 {
            let esp0_addr: usize;
            let task_manager = unsafe { &mut *(TASK_MANAGER_ADDR as *mut TaskManager) };
            let task_index = task_manager.now_index();

            let task = &task_manager.tasks_data[task_index];
            let esp0_addr = unsafe { &(task.tss.esp0) } as *const i32 as usize;
            unsafe {
                _start_app(
                    app_eip,
                    content_gdt * 8,
                    APP_MEM_SIZE as i32,
                    app_gdt * 8,
                    esp0_addr as i32,
                );
            }
            self.newline();
        } else {
            self.display_error("Bad Format");
        }
        memman.free_4k(content_addr as u32, finfo.size).unwrap();
        if app_mem_addr > 0 {
            memman
                .free_4k(app_mem_addr as u32, APP_MEM_SIZE as u32)
                .unwrap();
        }
    }

まず、これまでアプリケーションファイルの拡張子も.binにしていたが、紛らわしくなってきたので、.hrbと本にそろえるようにした。
次に、ファイル内のメタ情報を読み込み、データセグメントを読み込めるようにした。
また、これからはkernel.ldでリンクした.hrbファイルしか実行できないようになったので、エントリポイント名もharibote_osからhrmainにしておく。

Rustで書いたアプリケーションはrs.hrbにリネームされるようにた。

元々のhello.asmも変更して以下のようにkernel.ldでリンクできるようにした。

動作確認

実行してみると、Rustで書いたアプリケーションでも文字列を描画するシステムコールを呼べるようになったことがわかる。

Rustアプリケーションでの文字列表示

アプリケーションからウィンドウ描画

システムコールの種類を増やし、アプリケーションからウィンドウの描画、ウィンドウ内に文字描画、矩形描画ができるようにする。

#[no_mangle]
pub extern "C" fn hrb_api( // <- bin_apiから名前変更
    edi: i32,
    esi: i32,
    ebp: i32,
    esp: i32,
    ebx: i32,
    edx: i32,
    ecx: i32,
    eax: i32,
) -> usize {
    // 省略
    let sheet_manager = unsafe { &mut *(console.sheet_manager_addr as *mut SheetManager) };
    // POPADでアプリケーションに値を渡せるように、registerの番地をとっておく
    let reg = &eax as *const i32 as usize + 4;
    // 省略
    } else if edx == 5 {
        // ウィンドウの描画
        let sheet_index = sheet_manager.alloc().unwrap();
        {
            let new_sheet = &mut sheet_manager.sheets_data[sheet_index];
            new_sheet.set(ebx as usize + ds_base, esi, edi, to_color(eax as i8));
        }
        let title = unsafe { *((ecx as usize + ds_base) as *const [u8; 30]) };
        let mut t = title.iter().take_while(|t| **t != 0);
        let mut i = 0;
        for n in 0..30 {
            i = n;
            if t.next().is_none() {
                break;
            }
        }
        make_window(
            ebx as usize + ds_base,
            esi as isize,
            edi as isize,
            from_utf8(&title[0..i]).unwrap(),
            false,
        );
        sheet_manager.slide(sheet_index, 100, 50);
        sheet_manager.updown(sheet_index, Some(3));
        // アプリケーションにsheet_indexを返す
        let reg_eax = unsafe { &mut *((reg + 7 * 4) as *mut i32) };
        *reg_eax = sheet_index as i32;
    } else if edx == 6 {
        // ウィンドウ内に文字描画
        let sheet_index = ebx as usize;
        let sheet = sheet_manager.sheets_data[sheet_index];
        let string = unsafe { *((ebp as usize + ds_base) as *const [u8; 30]) };
        use crate::vga::ScreenWriter;
        use core::fmt::Write;
        let mut writer = ScreenWriter::new(
            Some(sheet.buf_addr),
            to_color(eax as i8).unwrap(),
            esi as usize,
            edi as usize,
            sheet.width as usize,
            sheet.height as usize,
        );
        write!(writer, "{}", from_utf8(&string[0..(ecx as usize)]).unwrap()).unwrap();
        sheet_manager.refresh(sheet_index, esi, edi, esi + ecx * 8, edi + 16);
    } else if edx == 7 {
        // ウィンドウ内に矩形描画
        let sheet_index = ebx as usize;
        let sheet = sheet_manager.sheets_data[sheet_index];
        boxfill(
            sheet.buf_addr,
            sheet.width as isize,
            to_color(ebp as i8).unwrap(),
            eax as isize,
            ecx as isize,
            esi as isize,
            edi as isize,
        );
        sheet_manager.refresh(sheet_index, eax, ecx, esi + 1, edi + 1);
    }

OSからアプリケーション側への値の受け渡し以外は難しいところはない。
これを使って、アプリケーション側では以下のようにしてみる。

本の中ではアプリケーションのシステムコール前と後に PUSHPOP してレジスタの値を保存していたが、インラインアセンブリで書くと、(nakedをつけない限り)勝手に前後にPUSH POPをつけてくれるので、明示的に書く必要はなかった。

実行結果

以下の通り、新しいウィンドウと文字や矩形を描画できた。

Rustアプリケーションでのウィンドウ表示

22日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday22としてタグを打ってある。