自作OSに挑戦する日記 26日目
「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。
Chapter 26 「ウィンドウ移動高速化」
今回やった内容
- ウィンドウ移動高速化
- 複数コンソール
- コンソールを閉じれるように
- start コマンド
- open(ncst) コマンド
環境
- メインPC
- MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
- macOS Mojave 10.14
- 自作OSを動作させる環境
作業記録
ウィンドウ移動高速化
HariboteOSではウィンドウをシートという単位で管理し、描画はシートの重なり情報を記録した配列を元に行なっています。このシートの重なりを記録する処理なのですが、シートの枚数 x シート幅 x シート高さ = 0(n^3)
で3重ループを回しています。この重なり情報はウィンドウを少しでも移動する度に記録し直しているため、ウィンドウのサイズが大きくなるほど、ウィンドウの数が増えるほどとてももっさりとした動きになってしまっていました。
これではいけない…ということで処理を修正していきます。
無駄な分岐をなくす
重なり判定は1要素ずつ行われ、毎回透明色であるかどうか(=描画する必要があるか)確認しています。もし、シート内に透明色が含まれない場合はこの確認処理は無駄であるため、処理前に1度だけ分岐を入れて無駄な処理を行わないようにします。コードがちょっと長くなりますが…許容範囲です。
/* 載せれる量のコードではないため… */ if(シート内に透明色が含まれる){ 透明色判定なし処理 }else{ 透明色判定あり処理 }
…この状態で実行してもそこまで高速になったとは感じませんが、内部処理の無駄をなくすことが出来ました。
書き込み速度を上げる
シートの重なり状態は以下のような配列によって保存されています。
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 3, 3, 3, 2, 2, 2, 2, 2, 0, 0, 3, 3, 3, 2, 2, 2, 2, 2, 0, 0, 3, 3, 3, 2, 2, 2, 2, 2, 0]
この配列の状態を実際のシート配置で表すとこうなります。
重なり状態は1要素ずつ丁寧に更新をしているのですが、これでは無駄が多すぎるのでいくつかまとめて更新出来るようにします。
重なり状態を保存している配列はchar型なので1つの要素のサイズは1バイトです。この配列に対してint型(4バイト)を使って更新すると、一度にchar型の要素を4つ分更新することが出来ます。普通の代入では不可能ですが、ポインタを使うことで可能になります。速度も1バイト分書き込むのと同じなので、4倍の速度で更新を行うことが出来ます。
/* sheet.c */ int sid4 = sheet_id | sheet_id << 8 | sheet_id << 16 | sheet_id << 24; ~~ // sheet_ctl->g_mapはchar型の配列 int *wp = (int *) &sheet_ctl->g_map[vram_y * sheet_ctl->width + vram_x]; for(int x = 0; x < buf_x1; ++ x){ wp[x] = sid4; }
重なり状態を保持しているchar型の配列をint型のポインタにキャストしてから更新を行なっています。
シートの重なり状態更新処理だけでなく、実際に画面描画を行なう処理でも4バイトまとめて書き込む方法を使うようにしました。
これで実行すると、以前とは比べ物にならないくらいウィンドウ移動が早くなりました…!400 x 300
とかなり大きいサイズのコンソールでも移動が楽になりました。
マウスの移動遅延をなくす
ここまででもかなり早くなったのですが、まだマウスを止めた時にもっさりするような感じがあります。これは、マウスの移動速度に対してウィンドウの更新処理が追いついていないから…らしいです(本より)。マウスやウィンドウの移動は、FIFOにマウスのデータが到着次第すぐ行なっています。これを変えて、FIFOに溜まっているデータが無くなって移動が確定した段階で描画を行うようにします。
/* bootpack.c */ // FIFOバッファが空だった if(fifo32_status(&osfifo) == 0){ if(mouse_draw_x >= 0){ io_sti(); sheet_slide(sheet_mouse, mouse_draw_x, mouse_draw_y); mouse_draw_x = -1; }else if(win_draw_x != 0x7fffffff){ io_sti(); sheet_slide(key_to_window, win_draw_x, win_draw_y); win_draw_x = 0x7fffffff; }else{ task_sleep(task_main); io_sti(); } continue; }
draw_mouse_*
と draw_win_*
にそれぞれ描画する座標を入れるようにして、FIFOバッファがからだった時にまとめて更新するようにしています。
複数コンソール
前回の改造で表示できるコンソールの数を増やせるようになりましたが、実行中に増やすことは出来ませんでした(コードを直接変更する必要があった)。また、起動直後に2枚コンソールが表示されているのも少し邪魔です。…ということで、キーボードやマウスを使って自由に枚数を操作できるようにします。
コンソールを増やす
shift + Tab
を押すことで新規コンソールが立ち上がるようにします。これに合わせ、起動処理に書かれていたコンソール立ち上げ処理を関数にして、いつでも呼べるようにします。
/* bootpack.c */ struct SHEET *open_console(struct SHEET_CTL *sheet_ctl, unsigned int mem_size){ struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = task_alloc(); int *fifo_console = (int *) memman_alloc_4k(memman, 128 * 4); struct SHEET *sheet = sheet_alloc(sheet_ctl); unsigned char *buf = (unsigned char *) memman_alloc_4k(memman, 400 * 300); ~~ タスクやシートの設定 ~~ return sheet; }
shift + Tab
が押された時の処理も書きます。
/* bootpack.c */ // コンソールを生成する if(data == 256 + 0x0f && key_shift){ key_window_off(key_to_window); key_to_window = open_console(sheet_ctl, mem_size); key_window_on(key_to_window); sheet_slide(key_to_window, 300, 240); sheet_updown(key_to_window, sheet_ctl->top); }
これでコンソールを自由に増やせるようになりました。それぞれのコンソールは独立して動いているので、好きなだけアプリを実行できるようになりました。
ズラッ pic.twitter.com/pQ0Y2DBvyc
— ゆん (@yn0014) May 16, 2019
コンソールを閉じれるように
アプリと同じく、コンソールも閉じれるようにします。コンソールを閉じるためにはメモリ解放やタスク構造体の解放など必ずやらなければいけない処理があります。これを close_console
にまとめます。
/* bootpack.c */ // コンソールタスクを終了 void kill_console_task(struct TASK *task){ struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; task_sleep(task); memman_free_4k(memman, task->stack_addr, 64 * 1024); memman_free_4k(memman, (int) task->fifo.buf, 128 * 4); task->flags = 0; // task_freeの代わり return; } // コンソールを閉じる void close_console(struct SHEET *sheet){ struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct TASK *task = task_now(); memman_free_4k(memman, (int) sheet->buf, 400 * 300); sheet_free(sheet); kill_console_task(sheet->task); return; }
コンソールウィンドウを閉じる処理と、タスク終了とメモリ解放を行う処理は別々の関数にします(タスクだけを閉じる処理を後で書くため)。スタックやFIFOを安全に解放できるように、処理中はタスクをスリープさせます。
kill_console_task
内でタスクをスリープしている都合上、以下のような流れでコンソールを閉じる処理を行います。
コンソールの「x」が押されるか exit
コマンドがコンソールで実行されると、コンソールタスク自体で出来る処理を行った後、終了処理要請をFIFOにputするようにします(コンソールタスク自体で出来る処理:タイマーキャンセルなど)。
これでウィンドウ関連の実装が終わりました〜!ウィンドウの移動も早くなり、コンソールを増やしたり減らしたり…も自由に行えるようになりました。
start コマンド
引数に指定したコマンドを新規コンソールで実行するコマンド start
を実装します。処理自体はとても単純で、新規コンソールを立ち上げた後、そのFIFOに対してコマンドを送り込むだけです。
/* console.c */ void command_start(struct CONSOLE *console, char command[]){ // 新規コンソール立ち上げ struct SHEET_CTL *sheet_ctl = (struct SHEET_CTL *) *((int *) 0xfe4); struct SHEET *sheet = open_console(sheet_ctl, mem_size); struct FIFO32 *fifo = &sheet->task->fifo; sheet_slide(sheet, 300, 300); sheet_updown(sheet, sheet_ctl->top); // 引数に指定されたコマンドを実行 for(int idx = 6; command[idx] != 0; ++ idx){ fifo32_put(fifo, command[idx] + 256); } fifo32_put(fifo, 256 + 0x0a); console_newline(console); return; }
open(ncst) コマンド
start
とは少し違う動きをする open
コマンドを実装します(本だと ncst
ですが open
の方が馴染みがあるためこうしました)。open
コマンドは引数に指定したコマンドを、新規コンソールを立ち上げずに実行するものです。
/* console.c */ void command_open(struct CONSOLE *console, char command[]){ // 新規コンソールタスク立ち上げ struct TASK *task = open_console_task(0); struct FIFO32 *fifo = &task->fifo; // 引数に指定されたコマンドを実行 for(int idx = 5; command[idx] != 0; ++ idx){ fifo32_put(fifo, command[idx] + 256); } fifo32_put(fifo, 256 + 0x0a); console_newline(console); return; }
基本的には start
と変わりません。コンソールを立ち上げずに実行するという特別な動作をするので、他のコードも一部変更しました。
これで26日目完成です!実行すると以下のようになります。
26日目〜! pic.twitter.com/VFxzMCwDng
— ゆん (@yn0014) May 16, 2019
OSっぽい感じがさらに増した…
まとめ
26日目は変更した箇所が多く実装も少し重かったですが、無事動いて良かったです。
ウィンドウ操作も楽になりコマンドも増えて、段々と快適なOSに近づいている感じがします。