自作OSに挑戦する日記 9日目

 「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
 この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。

Chapter 9 「メモリ管理」

今回やった内容

  • メモリ容量をチェックする
  • メモリ管理をする

今回(も)短いです。

環境

作業記録

 メモリ管理をするためには、対象のメモリの容量がわかっていないといけないので、そのチェックを行います。
 正常に読み書きができるまでメモリに順に値を書き込んでいって、読み書きができなくなった時点のアドレスをメモリの終端とし、メモリ容量の判定を行います。

(かなり原始的な方法だ…)

 また、CPUのキャッシュ機能を有効にしていると正常にメモリチェックができないため、キャッシュ機能を無効にする必要があります。キャッシュ機能を制御にするためにはCPUのフラグを制御する必要があるので、アセンブリで関数を書いてあげます。

 メモリチェックを行うコードは以下のようになりました。

// bootpack.c(一部)

#define EFLAGS_AC_BIT 0x00040000
#define CR0_CACHE_DISABLE 0x60000000

// メモリチェック
unsigned int memtest(unsigned int start, unsigned int end){
    int flag486 = 0;

    // CPUがi386か486かを確認する
    unsigned int eflag = io_load_eflags();
    eflag |= EFLAGS_AC_BIT; // eflagのACフラグを1にする
    io_store_eflags(eflag);
    eflag = io_load_eflags();
    if((eflag & EFLAGS_AC_BIT) != 0){ // i386環境だとACフラグがないため自動的に0になる
        flag486 = 1;
    }
    eflag &= ~EFLAGS_AC_BIT;
    io_store_eflags(eflag);

    // キャッシュを禁止にする
    if(flag486 == 1){
        unsigned int cr0 = load_cr0();
        cr0 |= CR0_CACHE_DISABLE;
        store_cr0(cr0);
    }

    unsigned int mem_addr = memtest_sub(start, end);

    // キャッシュを許可する
    if(flag486 == 1){
        unsigned int cr0 = load_cr0();
        cr0 &= ~CR0_CACHE_DISABLE;
        store_cr0(cr0);
    }

    return mem_addr;
}

unsigned int memtest_sub(unsigned int start, unsigned int end){
    unsigned int pat0 = 0xaa55aa55;
    unsigned int pat1 = 0x55aa55aa;

    for(unsigned int check_addr = start; check_addr <= end; check_addr += 0x1000){
        unsigned int *p = (unsigned int *)(check_addr + 0xffc);
        unsigned int old_val = *p;
        *p = pat0;
        *p ^= 0xffffffff;

        // いじった値が正しく読めるかどうか判定
        if(*p != pat1){
not_memory:
            *p= old_val;
            return check_addr;
        }

        // もう一度チェック
        *p ^= 0xffffffff;
        if(*p != pat0){
            goto not_memory;
        }

        *p = old_val;
    }
}
// nasmfunc.asm

load_cr0:       ; int load_cr0(void)
        MOV     EAX, CR0
        RET

store_cr0:      ; void store_cr0(int cr0)
        MOV     EAX, [ESP+4]
        MOV     CR0, EAX
        RET

 起動時にメモリチェックを行うようにして、その情報を画面に表示させてみました。

f:id:guguru0014:20190214220453p:plain
メモリチェックの結果

 ちゃんとメモリチェックができました〜!


 メモリチェックができたのでメモリ管理をします。空きメモリを管理するための構造体を2つ作って、それで管理するような仕組みになっています。
 1つ目の構造体は、それぞれ個別の空き領域の先頭アドレスと大きさを保持するようにします。2つ目の構造体は、個別の空き情報の構造体をまとめて扱うようにし、この2つ目の構造体にアクセスすることでメモリ管理を行います。

(メモリ管理って結構原始的な方法でやるんだ…)

 メモリを新たに確保する際には、空き情報のアドレスとその大きさを変更します。確保は簡単な処理なのですが、メモリの解放を行う処理は少し複雑になり、前後の空き情報と合わせることができるかを全て判定してあげる必要があるみたいです。空き情報が断片的になってしまうと大きい範囲の確保ができなくなるので、まぁ当たり前ですね…。

 メモリの確保・解放を行うコードは以下のようになりました。

// bootpack.c

struct FREEINFO{    // メモリの空き情報
    unsigned int addr, size;
};

struct MEMMAN{      // メモリ管理くん
    int frees, maxfrees, lostsize, losts;
    struct FREEINFO free[MEMMAN_FREES];
};

// メモリ確保
unsigned int memman_alloc(struct MEMMAN *man, unsigned int size){
    unsigned int alloc_addr;
    for(int idx = 0; idx < man->frees; idx++){
        // 十分な空きがあるか
        if(man->free[idx].size >= size){
            alloc_addr = man->free[idx].addr;
            man->free[idx].addr += size;
            man->free[idx].size -= size;

            // idx番目の空きがなくなったら後ろの空き情報を前に詰めてくる
            if(man->free[idx].size == 0){
                man->frees --;
                for(; idx < man->frees; idx ++){
                    man->free[idx] = man->free[idx+1];
                }
            }

            return alloc_addr;
        }
    }

    return 0;
}

// メモリ解放
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size){
    // free[i]をaddr順となるように保ちたいのでどこに挿入すべきか決める
    int idx = 0;
    for(; idx < man->frees; idx++){
        if(addr < man->free[idx].addr) break;
    }

    // idx-1と合わせることができる場合
    if(idx > 0 && man->free[idx-1].addr + man->free[idx].size == addr){
        man->free[idx-1].size += size;

        // idx+1と合わせることができる
        if(idx < man->frees && addr + size == man->free[idx].addr){
            man->free[idx-1].size += man->free[idx+1].size;
            man->frees --;
            for(; idx < man->frees; idx++){
                man->free[idx] = man->free[idx + 1];
            }
        }
        return 0;
    }

    // idx+1とだけ合わせることができる場合
    if(idx < man->frees && addr + size == man->free[idx].addr){
        man->free[idx].addr = addr;
        man->free[idx].size += size;
        return 0;
    }

    // 前後の空き領域と合わせることができなかった場合
    if(man->frees < MEMMAN_FREES){
        // 後ろにずらす
        for(int idx_b = man->frees; idx_b > idx; idx_b--){
            man->free[idx_b] = man->free[idx_b - 1];
        }

        // 空き情報更新
        man->frees ++;
        if(man->maxfrees < man->frees){
            man->maxfrees = man->frees;
        }
        man->free[idx].addr = addr;
        man->free[idx].size = size;
        return 0;
    }

    // 解放失敗
    man->losts ++;
    man->lostsize += size;
    return -1;
}

 そして、実際にメモリを確保してみました(適当なサイズ)。

f:id:guguru0014:20190214221959p:plain
メモリ確保

ちゃんと確保できているみたいです。メモリ管理成功!

まとめ

 メモリ管理のコードは少し間違うと後が怖いので慎重に書いてました…。最終的に問題なく動いてるみたいなので良かったです。メモリ管理ができるようになって、このはりぼてOSくんも完成にまた一歩近づけたような気が…?