Yabu.log

ITなどの雑記

30日OS自作本28日目1ファイルAPI編

28日目の内容です。 - ファイルAPIの作成 - hariboteOSを日本語対応させます。

OS自体の開発はここでおしまい。なのです。😭 記事が長くなるので前半後半で分けます。本記事ではとりあえずpart1としてファイルAPIについて書きます。

ファイルAPI

ファイルAPIの具体的なイメージなどよくわからず、また本書掲載のコードが5つ用意したうち2つしか使っていなかったので、以下2点に挑戦して見ました。

  • APIの実装コードにコメントをつけて理解する
  • 全てのファイルAPIを使うアプリを作る

では、ファイル操作のためのAPIを新たにOSに作ります。

struct TASK {
  //・・・無関係メンバ略

  //新規追加
  struct FILEHANDLE *fhandle;
    int *fat;
};
struct FILEHANDLE {
    char *buf; //メモリにロードしたファイル内容への参照
    int size;  //ファイルサイズ
    int pos;   //シークヘッダの位置(読み込み、書き込みに利用)
};
edx api
21 ファイルオープン
22 ファイルクローズ
23 ファイルシーク
24 ファイルサイズの取得
25 ファイルの読み込み

コード解説

コードは大体、console.cより抜粋。(定義などは適当に.hファイルから拝借)

ファイルオープン

//定義メモ
#define ADR_DISKIMG        0x00100000 //フロッピーの内容がロードされるメモリの位置
struct FILEINFO *file_search(char *name, struct FILEINFO *finfo, int max);
void file_loadfile(int clustno, int size, char *buf, int *fat, char *img);

・・・省略・・・

//API引数
// EDX = 21
// EBX = ファイル名
//
//API戻り値
// EAX = ファイルハンドル(失敗の場合0)

} else if (edx == 21) {
  //空きのファイルバッファを探す
  for (i = 0; i < 8; i++) {
    //ここのiの最大値が8なのがよくわからない。
    //一つのタスクが1度に扱えるファイル数が8個までという制限の意味?
    if (task->fhandle[i].buf == 0) {
      break;
    }
  }

  fh = &task->fhandle[i];
  reg[7] = 0; //失敗時の戻り値で初期化

  if (i < 8) {

    //file_search関数の引数について整理します。
    //1.edx=文字列のアドレス、そこにタスクのベースアドレスを加算してリニアアドレスを算出
    //2.ADR_DISKIMG + 0x002600で、フロッピーの内容がコピーされた場所の内、ルートディレクトリの開始位置ということになります。
    //3.224 = ルートディレクト領域の大きさ(IPLで定義)
    finfo = file_search(
      (char *) ebx + ds_base,
      (struct FILEINFO *) (ADR_DISKIMG + 0x002600),
      224
    );

    //file_search()の戻り値=0はファイルが見つからなかった時

    if (finfo != 0) {

      //最終的にreg[7]はapiを読んだ時の戻り値(EAX)になります。
      //そこにこれから作成するファイルハンドラを代入します。
      reg[7] = (int) fh;

      //ファイルハンドラ初期化
      fh->buf = (char *) memman_alloc_4k(memman, finfo->size);
      fh->size = finfo->size;
      fh->pos = 0;

      //ファイルハンドラのbufの位置にデータ読み込み
      //void file_loadfile(int clustno, int size, char *buf, int *fat, char *img)の引数整理
      //引数1:finfo->clustno,そのファイルがどのクラスタ内にあるか、
      //引数2:finfo->size,そのファイルのサイズ
      //引数3:fh->buf読み込んだファイルを格納するアドレス
      //引数4:task->fat fatの全データが格納されている
      //引数5:0x003e00,フロッピーからロードしたファイル実態が格納されているアドレス

      file_loadfile(
        finfo->clustno,
        finfo->size,
        fh->buf,
        task->fat,
        (char *) (ADR_DISKIMG + 0x003e00));
    }
  }

ファイル名でヒットしたfatを元に、メモリ上にブート時に配置されたファイルをコピーしています。

ファイルクローズ

//API引数
// EDX = 22
// EAX = ファイルハンドル

} else if (edx == 22) {
  fh = (struct FILEHANDLE *) eax;

  memman_free_4k(memman, (int) fh->buf, fh->size);
  fh->buf = 0;

コピーしていたファイル内容を破棄します。(メモリの解放) buf=0のものはクローズ扱いです。 逆にコマンドの処理が完了時にbuf=0になっていないものはOSが勝手に解放します。

OSによる自動解放

start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0));

・・・省略・・・

for (i = 0; i < 8; i++) {    /* クローズしてないファイルをクローズ */
  if (task->fhandle[i].buf != 0) {
    memman_free_4k(memman, (int) task->fhandle[i].buf, task->fhandle[i].size);
    task->fhandle[i].buf = 0;
  }
}

コードの重複がありますね〜30日終わったらうまく抽象化したいですね〜

ファイルシーク

//API引数
// EDX = 23
// EAX = ファイルハンドルのアドレス
// ECX = シークモード
//  0:シークの原点はファイルの先頭
//  1:シークの原点は現在のアクセス位置
//  2:シークの原点はファイルの終端
// EBX = シーク量

} else if (edx == 23) {
  fh = (struct FILEHANDLE *) eax;
  if (ecx == 0) {
    //ECX = 0:シークの原点はファイルの先頭
    //ファイル先頭位置から指定サイズ分(ebx)シーク
    fh->pos = ebx;
  } else if (ecx == 1) {
    //ECX = 1:シークの原点は現在のアクセス位置    
    //現在のposから指定サイズ分(ebx)シーク
    fh->pos += ebx;
  } else if (ecx == 2) {
    //2:シークの原点はファイルの終端
    //ファイル末尾(fh->size)から指定サイズ分(ebx)シーク
    fh->pos = fh->size + ebx;
  }
  if (fh->pos < 0) {
    //posが0以下?なにこれ?オーバーフロー対策?
    fh->pos = 0;
  }
  if (fh->pos > fh->size) {
    //ファイルサイズを超えるようなシークを無効化している?
    //ecx==2の時のシーク意味ないかこれ?
    fh->pos = fh->size;
  }

ファイルサイズの取得

特に解説することがない。

//API引数
// EAX = ファイルハンドル
// ECX = ファイルサイズ取得モード
//  0:普通のファイルサイズ
//  1:現在の読み込み位置はファイル先頭から何バイト目か
//  2:ファイル終端から見た現在位置までのバイト数
//
//API戻り値
// EAX = ファイルサイズ

} else if (edx == 24) {
  fh = (struct FILEHANDLE *) eax;
  if (ecx == 0) {
    reg[7] = fh->size;
  } else if (ecx == 1) {
    reg[7] = fh->pos;
  } else if (ecx == 2) {
    reg[7] = fh->pos - fh->size;
  }

ファイルの読み込み

特に難しくないでしょう。

//API引数
// EDX = 25
// EAX = ファイルハンドル
// EBX = バッファの番地
// ECX = 最大読み込みバイト数
//
//API戻り値
// EAX = 今回読み込めたバイト数

} else if (edx == 25) {
  fh = (struct FILEHANDLE *) eax;
  for (i = 0; i < ecx; i++) {
    if (fh->pos == fh->size) {
      break;
    }
    *((char *) ebx + ds_base + i) = fh->buf[fh->pos];
    fh->pos++;
  }
  reg[7] = i;
}
  • 現在のhariboteOSにはファイル書き込みの機能がないのでwriteは作らない
  • そういえばhariboteOSにファイルシステムではないらしい。
  • オートクローズの機構がある。
    • コマンド実行終了後、開いたままのファイルがあればOSがクローズする

ファイル操作APIの利用

apiのライブラリに登録されているこれらのメソッドを読んで見ます。 ちなみに本記事で解説したAPIアセンブリに定義されている以下の関数経由で読んでいます。

int api_fopen(char *fname);
void api_fclose(int fhandle);
void api_fseek(int fhandle, int offset, int mode);
int api_fsize(int fhandle, int mode);
int api_fread(char *buf, int maxsize, int fhandle);

サンプルとしてabc.txtというファイルを作ったのでこれで実験します。

$ cat txt/abc.txt
ABCDEFGHIJKLMNOPQRSTUVWXYZ

こちらをhariboteOSのディレクトリに投入します。

f:id:yuyubu:20180628225014p:plain

本書掲載のプログラムでは

  • ファイルシーク
  • ファイルクローズ
  • ファイルサイズ

が使われていなかったので、使って見ました。

#include "apilib.h"

void HariMain(void)
{
    int fh;

    //ファイルオープン
    fh = api_fopen("abc.txt");
    char s[20];

    //ファイルサイズを出力
    int size = api_fsize(fh,0);
    sprintf(s,"size:%d\n",size);
    api_putstr0(s);

    //ファイルシーク(読み込み位置を10文字進める)
    api_fseek(fh,10,0);

    if (fh != 0) {
        char c; //読み取り文字格納
        for (;;) {
            //ファイルリード(1文字づつ)
            if (api_fread(&c, 1, fh) == 0) {
                break;
            }
            api_putchar(c);
        }
    }
    //ファイルクローズ
    api_fclose(fh);
    api_end();
}

このアプリの実行結果は以下のようになりました。アプリ名はtypeiplです。(本書のipl.nasを表示するアプリのガワをそのまま流用しています。)

f:id:yuyubu:20180628225428p:plain

  • ファイルサイズは27バイトです。ローマ字は26文字ですが、ヌル文字を含んでいる?のでしょうか
  • ファイルシークにより11字目から出力が始まります。10文字目がなので次のKから始まっていることが確認できます。
  • クローズしなくてもOSがファイルクローズしますが、一応明示的にAPIを使って見ました。

まぁざっとこんな感じです。

感想

サンプルコード中にマジックナンバーなどよくわからない値が多数存在しています。 このアドレスの破片っぽいものは何だったっけ?と自分のブログを検索してみると以外と過去にメモった情報が見つかって助かることがあります。 今回は一番このブログに助けられた章になりました。 皆様も最終日が近付くと、初日辺りの細かいアドレスなど忘れてしまうので、メモなど取りながら進めることをお勧めします。

電子書籍の中をアドレスや変数名で検索できるとさらに良いのですが。