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

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

Chapter 28「ファイルと日本語表示」

今回やった内容

  • alloca
  • ファイルAPI
  • 日本語表示

環境

作業記録

alloca

 allocaの実装をします。

alloca はスタック上にメモリ領域を確保するための関数のことで、(初めて知った)スタック上に4KB以上のメモリ領域を確保しようとするとコンパイラalloca を使おうとするらしいです。

 …との記述が本にあったのですが、alloca を実装せずに4KB以上の領域を使う変数を宣言するアプリを実行してみたところ、特にエラーは起きませんでした。使っているコンパイラが呼ばないようになっているのか、それとも別に原因があるのか…自作OS本が終わってから詳しく調べなおしたいと思います(コードを変えたりしながら1時間程度格闘したのですが分かりませんでした…)。

 エラーが起きなかったとはいえ、今後バグが出てくるのも怖いのでとりあえず実装することにしました。実装はとても簡単で、ESPを要求されたバイトだけ操作するだけです。

[BITS 32]

        GLOBAL      __alloca

[SECTION .text]

__alloca:
    SUB     ESP, EAX
    ADD     EAX, 4
    JMP     DWORD [ESP + EAX - 4]       ; RETの代わり

 ESPの値が変更されていてRETが使えないため、いつもとは違う方法でアドレスを指定しJMPするようにしています。

ファイルAPI

 ファイルを操作するAPIを実装します。

## ファイルオープン

### 引数

- EDX : 21
- EBX : ファイル名

### 返り値

- EAX : ファイルハンドル(読み込み失敗時には0)

## ファイルクローズ

### 引数

- EDX : 22
- EAX : ファイルハンドル

## ファイルシーク

### 引数

- EDX : 23
- EAX : ファイルハンドル
- ECX : シークモード
    - 0 => ファイルの先頭を原点にとする
    - 1 => 現在読み込み中の場所を原点とする
    - 2 => ファイルの終端を原点とする
- EBX : シーク量

## ファイルサイズ取得

### 引数

- EDX : 24
- EAX : ファイルハンドル
- ECX : ファイルサイズ取得モード
    - 0 : ファイルサイズ
    - 1 : 現在読み込み位置は先頭から何バイトの位置か
    - 2 : 現在読み込み位置からファイルの終端は何バイトあるか

### 返り値

- EAX : ファイルサイズ

## ファイル読み込み

### 引数

- EDX : 25
- EAX : ファイルハンドル
- EBX : バッファの番地
- ECX : 最大読み込みバイト数

### 返り値

- EAX : 今回読み込めたバイト数

## コマンドライン取得

### 引数

- EDX : 26
- EBX : コマンドラインを格納するバッファのアドレス
- ECX : 何バイトまで格納するか

### 返り値

- EAX : 何バイト格納されたか

## 言語モード取得

### 引数

- EDX : 27

### 返り値

- EAX : 言語モード
    - 0 : ASCII English
    - 1 : Shift-JIS
    - 2 : EUC-JP

 それぞれの操作のイメージはこんな感じです。図内の黒三角はファイルハンドルを、中央の並んだ箱のようなものはディスクを表しています。

f:id:guguru0014:20190520204957p:plain
ファイルを操作するイメージ


 これでアプリからファイルの読み込みが出来るようになったので、OSの標準コマンドとして実装していた cat コマンドをアプリとして置き換えます。アプリとして動作させると強制終了が出来るので利便性がUPします。
 また、cat のためにコマンドライン取得APIも作りました。このAPIの動作は名前の通りで、実行直前に入力されたコマンドを取得することができます。

/* cat.c */

#include "api_link_head.h"

void HariMain(){
    char str[40];

    // ファイル名をコマンドラインから取得
    char command[40], *command_p;
    api_get_command(command, 40);
    for(command_p = command; *command_p > ' '; ++ command_p);    // スペースまで読み飛ばす(コマンド名無視)
    for(; *command_p == ' '; ++ command_p);                      // スペースを読み飛ばす(区切り無視)

    // ファイルを開く
    int fhandle = api_fopen(command_p);
    char read_c;
    if(fhandle != 0){
        while(1){
            if(api_fread(fhandle, &read_c, 1) == 0){
                break;
            }
            api_putchar(read_c);
        }
    }else{
        msprintf(str, "File Not Found...\n");
        api_putstr(str);
    }

    api_putchar('\n');
    api_end();
}

日本語表示

 ついにHariboteOSも日本語対応です。全角文字の文字コードの表現方法が少し特殊でした(「面」「区」「点」…)。
 日本語フォントファイルは「OSASK」で使われているデータをありがたく使わさせて貰うことにします…!OS本についてくるCD内からフォントデータを取り出し、少し手を加えればフォントファイル完成です。

 フォントファイルは容量節約のため、非漢字・第一標準漢字・第三標準漢字の3種類のフォントのみが含まれています(本が全部終わった後に全漢字に対応させます)。

 本来より小さいフォントファイルですが、それでも145KBもあるのでOS自体には含めず、ファイルとしてOSから読み込むようにします。

/* bootpack.c */

// 日本語フォントファイル(japanese.fnt)を読み込む
extern char hankaku[4096];
unsigned char *japanese = (unsigned char *) memman_alloc_4k(memman, 16 * 256 + 32 * 94 * 47);
struct FILEINFO *finfo = file_search("japanese.fnt", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
int *fat = (int *) memman_alloc_4k(memman, 4 * 2880);
read_fat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));

// フォント設定
if(finfo != 0){
    load_file(finfo->clustno, finfo->size, japanese, fat, (char *) (ADR_DISKIMG + 0x003e00));
}else{
    for(int idx = 0; idx < 16 * 256; ++ idx){   // 半角部分のみを既存のフォントで置き換える
        japanese[idx] = hankaku[idx];
    }
    for(int idx = 16 * 256; idx < 16 * 256 + 32 * 94 * 47; ++ idx){     // 日本語部分は0xffで埋める
        japanese[idx] = 0xff;
    }
}
*((int *) 0x0fe8) = (int) japanese;
memman_free_4k(memman, (int) fat, 4 * 2880);

 フォントデータはメモリの 0x0fe8 番地に置くようにします。


 フォントが用意できたので実際に表示できるようにします。

/* graphic.c */

// EUC
if(task->langmode == 2){
    for(; *str != 0x00; ++ str){
        if(task->langbyte1 == 0){       // 1バイト目
            if(0x81 <= *str && *str <= 0xfe){
                task->langbyte1 = *str;
            }else{
                putfont8(vram, width, x, y, color, japanese + *str * 16);
            }
        }else{                          // 2バイト目
            int k = task->langbyte1 - 0xa1;
            int t = *str - 0xa1;
            task->langbyte1 = 0;
            char *font = japanese + 256 * 16 + (k * 94 + t) * 32;
            putfont8(vram, width, x - 8, y, color, font);
            putfont8(vram, width, x, y, color, font + 16);
        }
    }
}

 これはEUCエンコードされた全角文字1文字を日本語フォントで表示するコードです。全角文字は連続する2バイトで表されるため半角文字のように1バイト読み込んですぐ表示…はできません。なので、1バイト目を読んだときはそのデータを task->langbyte1 に一旦格納し、2バイト目を読み込んだ時点で表示するようにしています。

 また、HariboteOSではEUCだけでなくShift-JISでエンコードされた文字も表示出来るようにしました(UTF-8にも対応させたい)。


 ということで28日目完成です!(バグの修正のために29日目の内容が少し入ってますが…)

 ついにHariboteOSでも日本語表示が出来るようになりました!

まとめ

   表示部分だけですが、HariboteOSが遂に日本語に対応しました! 将来的には入力にも対応させたいです。
 進化が大きかった分、今回は詰まったところが少し多かったです。特に alloca についてはもう少し調べてみたいと思います。