30日OSをRustで書く(15日目)

マルチタスク実装

15,16日目ではマルチタスクを実装する.

マルチタスクを実装するために必要なものとして,タスクのレジスタの内容を
保存する領域が必要.これをTSS(Task Status segment)という.16ビット版と32ビット版があり,
今回は32ビット版を扱う.

#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
pub struct TSS {
    pub backlink: i32,
    pub esp0: i32,
    pub ss0: i32,
    pub esp1: i32,
    pub ss1: i32,
    pub esp2: i32,
    pub ss2: i32,
    pub cr3: i32,
    pub eip: i32,
    pub eflags: i32,
    pub eax: i32,
    pub ecx: i32,
    pub edx: i32,
    pub ebx: i32,
    pub esp: i32,
    pub ebp: i32,
    pub esi: i32,
    pub edi: i32,
    pub es: i32,
    pub cs: i32,
    pub ss: i32,
    pub ds: i32,
    pub fs: i32,
    pub gs: i32,
    pub ldtr: i32,
    pub iomap: i32,
}

全部で104バイト.

これをそれぞれのタスクごとに用意し,GDT(Global Descriptor table)に登録する.
この時,セグメントの管理用属性を0x89(32ビットのTSS)とすることでbaseアドレスを先頭とするセグメントにはTSSが格納されていることをコンピュータに伝えることができる.
タスクスイッチの時に使うJMP命令で指定したセグメントが実行可能なセグメントではなくTSSだった場合は,CPUはEIPやCSを書き換えるのをやめて,タスクスイッチ命令だと判断し,そのTSSで指定されたタスクに切り替わる.

詰まったところ

基本的に本の内容通りにコーディングして問題なかったが,一部素直にできない部分があった.
本の中では,タスク切り替えの際にJMP FAR命令を使っていた.

_farjmp:
                JMP    FAR  [ESP+4]
                RET

はじめ,これをそのままインラインアセンブリで再現しようとしたが,intel構文を指定してもうまくいかなかったため
「30日でできる!OS自作入門」をRustで。15日目 - TSUGULOGの記事を参考にさせて頂いた.

#[repr(C, packed)]
struct Jump {
    eip: i32,
    cs: i32,
}


#[naked]
#[no_mangle]
pub extern "C" fn farjmp(eip: i32, cs: i32) {
    unsafe {
        asm!("LJMPL *($0)" :: "r"(&Jump {eip, cs}) :: "volatile");
    }
}

Jumpという構造体をLJMPLで指定し,タスクスイッチしている.

参考にさせて頂いた記事で起こっていたタイマ割り込みの処理関数の中でタスクスイッチを呼ぶとうまく動かない件については,自分のOSにも起こったが原因や解決方法はわからなかった.

f:id:udon-yuya:20200516020903p:plain
15日目の完成品
タイマ割り込みを使ったタスクスイッチのうち前半の明示的なタスクスイッチはうまくいったが,後半の暗黙的なタスクスイッチは画像ではうまくできているようだが,マウスの動きが極端に悪くなり,大きく動かすとマウスのバッファを溢れてしまいパニックを起こすようになった.割り込み禁止の時間が長くなってマウスの割り込みが起きても処理が追いつかなくなっていると推測される.