30日OS自作入門17日目(Win10)

はじめに

コンソール…要は端末を作っていくみたいですね…

目次

コンソール

アイドルタスク(harib14a)

前回のマルチタスクの続きとなっていて、もしタスク1個になってしまった場合にタスクスイッチしようとしてしまうと、「CPUが拒否して大変なことになると」自作OS本のP326に述べられていると思います。ですが、今後タスクが1個、もしくは0個という可能性もあるわけで、そういうときにHLTを行える番兵をタスクレベル9にセットし、エラーが起きないようにするのが目的みたいです。

修正内容は、HLTを無限ループで割り込みがくるまで回す関数task_idleをセットし、それをレベル9で優先度は0.1秒でセットしておくことみたいです。
task_idle()

void task_idle(void)  /* 番兵用 */
{
  for (;;) {
    io_hlt();
  }
}

タスクidleの登録内容

  /* 以下番兵のアイドルタスク */
  idle = task_alloc();
  idle->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
  idle->tss.eip = (int) &task_idle; /* タスクのセット */
  idle->tss.es = 1 * 8;
  idle->tss.cs = 2 * 8;
  idle->tss.ss = 1 * 8;
  idle->tss.ds = 1 * 8;
  idle->tss.fs = 1 * 8;
  idle->tss.gs = 1 * 8;
  task_run(idle, MAX_TASKLEVELS - 1, 1); /* レベル;9, 優先度:0.1秒 */

Harimainのtask_bをコメントアウトして、実行すると

ちゃんとエラー起こさずにいてくれてますね。

コンソールを作ろう(harib14b)

いつもUbuntuとかで端末と表示されているCUIのあれを作るようです。といっても、いきなり作るのではなく、形をつくってから文字を入力できるようにしていく感じで、徐々に作っていくみたいです。

タスクBを消して、コンソール用タスクを作るみたいです。コンソール用タスクのレベルは2で優先度は0.02秒のようです。登録の仕方もタスクBとあまり変わらないようです。
で、コンソール用タスクがどのような処理を行うかというと

void console_task(struct SHEET *sheet)
{
    struct FIFO32 fifo;     /* FIFOバッファ */
    struct TIMER *timer;    /* タイマ */
    struct TASK *task = task_now(); /* スリープから復帰する際にtask_nowから参照する */

    int i, fifobuf[128], cursor_x = 8, cursor_c = COL8_000000;
    fifo32_init(&fifo, 128, fifobuf, task); /* バッファの初期化 */

    timer = timer_alloc();
    timer_init(timer, &fifo, 1);
    timer_settime(timer, 50);   /* カーソル用(0.5秒) */

    for (;;) {
        io_cli();   /* IF=0(割り込み無効) */
        if (fifo32_status(&fifo) == 0) {    /* バッファにデータが来ていなければ */
            task_sleep(task);   /* スリープ */
            io_sti();   /* IF=1 */
        } else {
            i = fifo32_get(&fifo);  /* バッファからデータを1バイト引き出す */
            io_sti();   /* IF=1 */
            if (i <= 1) {   /* カーソル用タイマ */
                if (i != 0) {
                    timer_init(timer, &fifo, 0); /* 次は0を */
                    cursor_c = COL8_FFFFFF;
                } else {
                    timer_init(timer, &fifo, 1); /* 次は1を */
                    cursor_c = COL8_000000;
                }
                timer_settime(timer, 50);
                boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
                sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
            }
        }
    }
}

カーソルが点滅しているときと同じ処理ですね。

実行すると
f:id:No000:20200201173355p:plain コンソールだ!

入力切り替えをやってみる(harib14c)

このままだとテキストボックスのウィンドウにしか文字入力ができないので、コンソールも選択できるようにしようということみたいです。その時選択されたことを確認するためにタイトルの色が変わるようにしているようです。

要点は、タイトルとウィンドウの描画の処理を別の関数に分離させることと、変数key_toのようです。

分離のところの処理は単純っぽいです。make_windowの中でボックスを先にかいた後に、タイトルを描画しているようです。

HariMainは以下のようにTabのキーデータを処理しています。

if (i == 256 + 0x0f) {  /* Tab */
    if (key_to == 0) {
      key_to = 1;  /* コンソールタスク */
      make_wtitle8(buf_winm  sht_win->bxsize,  "task_a",  0); /* actは1で標準色、0で灰色 */
      make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1);
    } else {
      key_to = 0;  /* タスクA */
      make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
      make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
    }
    sheet_refresh(sht_win,  0, 0, sht_win->bxsize,  21);
    sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}
/* カーソルの再表示 */
boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);

変数iはキーデータを受け取っているので、最初の条件分岐でTabの場合で分岐しています。その後、key_toのデータをフラグにしてどちらのタイトルを灰色にするかを選択しています。

実行してみると

文字入力をできるようにしてみる(harib14d)

文字入力をできるようにするために、構造体TASKの方でもFIFOバッファが扱えるようにしています。また、一個前で作った変数key_toをコンソールとテキストボックスのフラグとして使用し、キーデータを送るタスクを分岐させています。その後、コンソールタスクをHariMainのように、FIFOバッファから来たデータをさばけるようにしているようです。

FIFOバッファを扱うようにした構造体TASK

struct TASK {
    int sel, flags; /* sel(selector)はGDTの番号のこと */
    int level,priority;   /* priority:優先度 */
    struct FIFO32 fifo;   /* タスクに渡すFIFOバッファ */
    struct TSS32 tss;   /* バックアップする各種レジスタ */
};

key_toをフラグとしての分岐

if (i < 0x54 + 256 && keytable[i - 256] != 0) { /* 通常文字 */
    if (key_to == 0) {    /* タスクAへ */
        if (cursor_x < 128){
            /* 一文字を表示してから、カーソルを一つ進める */
            s[0] = keytable[i - 256];   /* i = 0x1e + 256 */
            s[1] = 0;   /*  */
            putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
            cursor_x += 8;
        }
    } else {    /* コンソールへバッファ経由で */
        fifo32_put(&task_cons->fifo, keytable[i - 256] + 256);  /* 先に文字コードの値で渡してる */
    }
}
if ( i == 256 + 0x0e) { /* バックスペース */
    if (key_to == 0) {
        if (cursor_x > 8) {
            /* カーソルをスペースで消してから、カーソルを1つ戻す */
            putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
            cursor_x -= 8;
        }
    } else {    /* コンソールへ */
        fifo32_put(&task_cons->fifo, 8 + 256);  /* コンソール用タスクにバッファ経由で */
    }
}

実行してみると

記号入力(harib14e)

記号…いわゆるShiftキーを押しっぱなしにして入力する文字に対応させるようです。
まずは、Shiftキーを押しっぱなしの状態の時を変数key_shiftをフラグとして利用することで、処理を分けています。0だったら何も押していない、1であれば右シフト、2であれば左シフト、3であれば両方のシフトを押している状態です。

また、Shiftキーを押している間は専用の文字コードのテーブルkeytable1を利用するように、フラグで条件分岐することで書き分けを実現しているようです。そして、条件分岐により別々の文字コードとして生成されたデータはs[0]にいったん格納することでコンソール等(コンソールへはFIFOバッファを利用してデータを送っているので、先に文字コードに直せばコンソールタスクをいじらなくていい)にデータを送っています。

実行してみると
f:id:No000:20200201230125p:plain
しっかりと変換できてます。

大文字と小文字(harib14f)

アルファベットで大文字と小文字を分けて入力できるようにするそうです。これで、キャメルケースとかでかけるゾ。ということで大文字と小文字に分ける条件として2つあるようです。それは、ShiftキーとCapsLockの二つです。Shiftキーは前章でやりましたが、CapsLockはどうするのか?それは、asmhead.asmでBIOSからの情報としてbinfoの中に保管してあるようです。CapsLockの情報はbinfo->ledsの4~6ビット目になるようです。これを||や&&を使ってうまく条件分岐させて、分けるようです。

CapsLock OFF && シフト OFF ->小文字
CapsLock OFF && シフト ON  ->大文字
CapsLock OFF && シフト OFF ->小文字
CapsLock ON  && シフト ON  ->大文字

といった条件です。

ledsの4->ScrollLock, 5->NumLock, 6->CapsLock

のようなフラグのようです。

実行すると
f:id:No000:20200201234808p:plain AtcoderのNimコンパイラが1系になるとかで騒がれてましたね。楽しみ

Lockキーの対応(harib14g)

CapsLockを実現している仕組みは、キーコード表にあるCapsLock等に該当する信号がキーボードから送られてきたらbinfo->ledsを書き変えればいいようです。なので、keycmdというバッファを作りこれをキーボードへ送るデータの緩衝材にしています。他にも、keycmd_waitという関数をキーボードコントローラへのデータ送信状況を記録するフラグとなっています。あとは、無限ループの前にランプの初期状態を設定することで、食い違いがおきないようにしているみたいです。

条件分岐は以下のようにするようです

if (i == 256 + 0x3a) {  /* CapsLock */
    key_shift ^= 4;
    fifo32_put(&keycmd, KEYCMD_LED);
    fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x45) {  /* NumLock */
    key_leds ^= 2;
    fifo32_put(&keycmd, KEYCMD_LED);
    fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x46) {  /* ScrollLock */
    key_leds ^= 1;
    fifo32_put(&keycmd, KEYCMD_LED);
    fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0xfa) {  /* キーボードがデータを無事に受け取った */
    keycmd_wait = -1;
}
if (i == 256 + 0xfe) {  /* キーボードがデータを無事に受け取れなかった */
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, keycmd_wait);
}

また、LEDライトを点灯させる方法は以下のサイトに載っているようです。

(AT)keyboard - os-wiki

ここもわかりやすいかも?

0から作るOS開発 キーボードドライバその2

実行してみると

メモ

・やはり番兵は優秀
セミコロンのミスはパースエラーで死にかけるので注意する
・基本はフラグの変数を用意して、それで分岐 ・写経の時に似通った部分があっても、インデント単位で書き換える

終わりに

ほんとにOSらしくなってきましたね!次の日にちをチラ見した感じコマンドを実装するようです。楽しみ