Ubuntu 18.04.1 LTSでやる 30日OS本 〜20日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、20日目です。 他の章へのリンクはここにあります。

1. プログラムの整理

作業量が多いのでバグを埋め込んでしまわないか心配でした。

新しく関数を書いて、その中に自分が書いたものカット & ペーストして編集、とするとミスが減らせそうな気がします。 他に簡単にできる手段としては、こまめに実行してみて様子を見るとかでしょうか。

2. 一文字表示API(1)

hlt.asmの冒頭、本では[BITS 32]となっていますが、NASMの場合は角括弧を除いてbits 32とすれば良いみたいです。

bootpack.mapというファイルはリンカにオプションを渡すことで生成できます。 リンカはldなのでman ldでオプションを調べると、-Map [ファイル名]が欲しかったものだとわかります。 gccからやる場合もldのマニュアルに答えがあって

gcc (中略) -Wl,-Map=bootpack.map

のようになります。ちなみに-Xlinker -Map -Xlinker bootpack.mapのようにも書けます。

前回のMakefileの変更で中間ファイルを生成するようにしたため、bootpack.hrb生成の段階ではリンクしかしていません。 リンカオプションを渡したいのにGCCを経由するのは回りくどいと感じたので、次のようにリンカldを直接呼ぶようにしました。

bin/bootpack.hrb: $(OBJS) src/har.lds Makefile
    ld -o $@ $(OBJS) -e os_main -Map lst/bootpack.map -m elf_i386 -T src/har.lds

asm_cons_putcharglobalしておく必要があります。

3. 一文字表示API(2)

hlt.asmの冒頭にbits 32を入れないと、この節での修正を加えても異常終了してしまうようです。 出来上がったhlt.hrbを確認してみると、call 番地に当たる部分の長さが異なっているっぽいです。

4. アプリケーションの終了

再びbootpack.mapを見てアドレスを書き換えます。ここでも本に載っている例とは異なっているはずです。

5. OSのバージョンが変わっても変わらないAPI

set_gatedescの引数として渡したいので、asm_cons_putcharの宣言

void asm_cons_putchar(void);

bootpack.hに追記します。

6.・7.

はい

8. 文字列表示API

文字列表示用の関数を作ったことでコードがかなりわかりやすくなったと感じます。

また、今まではコマンドが不正なときのメッセージなどをputfonts8_asc_shtで直書きしていました。 つまり、「Bad command.」ではなく「command 'hoge' not found.」のような表示をしようとしたときに、hogeが長いと出力がはみ出す状態だったということです。 これが文字列表示用の関数(cons_putchar経由)で表示するようになったことでいい感じに折り返されるようになりました。安心ですね。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜19日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、19日目です。 他の章へのリンクはここにあります。

Githubソースコードを公開しました(https://github.com/wisteria0410ss/os)。

1. typeコマンド

再び文字列比較が微妙です。前回同様の流れで次の節でstrncmpを使うことになるのですが、これも自分で書くことになります。 strcmpのn文字目で打ち切るようにすればいいのでほぼコピペですが。 どうせ自分で書くのでstrncmpにこだわる必要はなく、starts_withをなどを作るのも手だと思います。私は呼び出し時のnの指定をサボりたかったので starts_withを書きました。

また、typeコマンドを試すにあたって何らかのテキストファイルをディスクイメージに放り込む必要がありますが、これはharibote.sysを書き込むのと 全く同様の方法でできます。

2. typeコマンド改良

表示できても画面が小さいとうまくいっているかもわかりにくいです。解像度を上げてコンソールの大きさを広げたほうが良い気がしてきました。

私はここまで実機で1920×1080、エミュレータだと(対応していないので)320×200というようにしていましたが、 エミュレータでもある程度高解像度で動くよう1920×1080→1024×768→320×200の三段構えに変更しました。 これに合わせてコンソールのウィンドウサイズも大きくしました。

日本語は化けてしまいますが、英数字に関しては問題なく表示されていそうなことが確認できました。

f:id:wisteria0410ss:20190222181027p:plain
高解像度にしてstrcmp.cの内容を表示させた様子

また、画面を大きくしたら入力可能な最大文字数も増えるので、配列のサイズを溢れない程度まで広げました。

3. FATに対応

はい。本文の通り、うまくいっていると「信じる」ことにしました。

4. ソースの整理

ファイル分割をします。ところで、ファイルを増やすたびにMakefileに書き足すのは正直面倒です。 ワイルドカードなどを使ってやればその手間を省けるようなので、Makefileにも手を加えることにしました。今まではgccにCのファイルを指定しまくって 中間ファイルをあまり出さないようにしていましたが、少しの変更のたびに全体をコンパイルするのも良くないということで*.oを生成する段階を挟むようにします。 ただ、中間ファイルが散らかるのも気になるのでディレクトリを分けることにしました。

ここでのディレクトリ構造はこんな感じです。

src
ソースコードを入れる。ヘッダファイルもアセンブリもフォントデータのテキストもリンカスクリプトも考えるのが面倒なので全部ここ。
obj
コンパイルしてできたオブジェクトファイルを入れる。
bin
イメージファイル以外のバイナリを入れる。
lst
アセンブルしたときに生成していた`*.lst`の置き場。
util
ツールっぽいものを置こうと思ったが、フォントデータから`hankaku.c`を作るやつくらいしか置くものがない。

haribote.imgは何となくトップに直接生成することにしました。ごちゃごちゃしたものが編集するものと隔離されてちょっといい感じになりました。 このもとでのMakefileを貼りたいのですが、縦に長いのでgithubに公開したものを見てください。 .gitignoreの関係で一部ディレクトリは見えていません。

ファイル分割のたびに書き足す手間を省いてくれる部分は

OBJS := $(patsubst src/%.c,obj/%.o,$(filter-out src/hankaku.c src/test.c,$(wildcard src/*.c))) obj/hankaku.o obj/func.o

で、これはbootpack.hrbを生成するのに必要なオブジェクトファイルを列挙するものです。 何をしているかというと

  1. wildcardでCのソースファイルを全列挙
  2. filter-outで一部を除く。test.csprintfstrcmpなど自分で書いた関数のテスト用のため、 hankaku.cは自動生成されるファイルで存在が保証されないために除いています。
  3. src/なんとか.cobj/なんとか.oに置換
  4. 上で挙げたCのプログラムからできるのではないオブジェクトファイル(hankaku.ofunc.o)を追加

という感じです。

5. ついに初アプリ

アプリ関係はappというディレクトリに入れることにしました。コンパイル~~~.asmから~~~.binを作るのと同様にすればよく、 できたファイルはイメージに放り込みます。なので、Makefileには

bin/hlt.hrb: app/hlt.asm Makefile
    nasm $< -o $@

を追記し、ディスクイメージに入れるファイルを列挙しているFILESbin/hlt.hrbを加えました。 アプリが増えてきたらオブジェクトファイル同様ワイルドカードで入れるようにするつもりです。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜18日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、18日目です。 他の章へのリンクはここにあります。

1.〜2. カーソル点滅制御

はい

3. Enterキーに対応

ウィンドウサイズを決め打ちするのは気持ち悪いので、ここでも書き換えをしました。以下同様です。 make_window8を読んで枠の太さを調べるか、大きめの値を指定してはみ出した部分を数えるかすれば良いです。

4. スクロールにも対応

塗りつぶしなどの処理がウィンドウサイズのキリが悪い場合でも動くか一応確かめました(上のような書き換えの影響があるので)。 また、ここでは改行の処理をベタで書いていますが次の節でに関数として分離することになります。

5. memコマンド

コマンドラインのところの文字列比較がとても気持ち悪いですが、次の節でstrcmpを使うようになるのでひとまず見逃すか、ここからstrcmpを使うかしましょう。

6. clsコマンド

ここでstrcmpを使うようになるのですが、なんとsprintf同様使えないことが発覚しました。 仕方ないので自分で書きました。ただし、これは非常に簡単で

int strcmp(const char *s1, const char *s2){
    for(int i=0;;i++){
        if(s1[i] > s2[i]) return 1;
        if(s1[i] < s2[i]) return -1;
        if(s1[i] == 0 && s2[i] == 0) return 0;
    }
}

で良いはずです。

7. dirコマンド

ディスクイメージへの書き込みは、haribote.sysと同様に

mcopy [ファイル名] -i haribote.img ::

で行います。適当なファイルを書き込むコマンドを、Makefileharibote.imgのところに追加しておきましょう。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜17日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、17日目です。 他の章へのリンクはここにあります。

1.〜6.

それっぽい見た目になってきました。いい感じです。 解像度は決め打ちできないはずなので、ウィンドウの幅は定数として持たせておくかbinfo->scrnxなどから算出するようにしておくと良い気がします (数値を直接書き込んでいくのは何となく嫌な感じがするので)。 ウィンドウの幅を可変にする場合、文字入力の制限の部分はウィンドウ幅を8で割って切り上げして比較、のような感じになると思います。

上下左右キーや[Insert]キーなどは対応するテンキーの文字が出てきてしまいますが、これは0xe0が送られてきたときの処理をやっていないからのようです。 (参考:http://oswiki.osask.jp/?(AT)keyboard

また非本質的ですが、keytable0keytable1keytable[2][0x80]のようにまとめるとkeytable[key_shift!=0][i-256]と書けて分岐が1つ減ります。

7. Lockキー対応

適当な変数でLockキーの状態を保持し、それに関する処理の際に参照できるようにしておきます。 最初にランプの状態を取得しておいて、Lockキーが押されるたびにLockの状態に合わせてそれを更新、キーボードのランプに反映という処理をします。

QEMUで実行する際、キーボードのLEDはホストOS側のLock状態と一致するようになっているようです。 うまくいっているかを確認するには実機で動かすことになります。

おまけ

キーボードのランプを好きに操作できるようになりました。タイマでの処理と組み合わせることで一定の周期でキーボードのランプを点滅させることも可能です。

たのしいですね。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜16日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、16日目です。 他の章へのリンクはここにあります。

1. タスク管理の自動化

ごちゃごちゃした処理をまとめてしまうとスッキリして良いですね。

2. スリープしてみる

HLTする代わりに他のタスクを実行するようにします。ここでは何もなければタスクAをスリープさせてタスクBに注力するようにしています。

マウスやキーボードに触れない状態で実機で測定を行い、30回分の平均を取ると下の表のようになりました。「表示あり」は 1/100 秒ごとにその時点のカウント数を表示させる状態で、表示なしとあるのはtimer_settime(timer_put, 1)を消す方法で測定を行ったものです。 なお、比は13日目最後(マルチタスクなし)の場合のカウント数を1としたときの値を示しています。

カウント数
表示あり 1.663157(6)×108 0.989190
表示なし 1.681270(6)×108 0.999962

本文にあるような理由で単純な比較はできませんが、ほぼマルチタスクなしの場合と同じ値を達成しています。 タスクAのスリープはうまくいっているようです。

3. ウィンドウを増やそう

ウィンドウを増やします。本ではここでテキスト入力のできるウィンドウの幅を小さくしているので、キー入力→文字表示の処理のところに微修正が必要です。 あるいは、高解像度にしているならウィンドウ幅をそのままにしておいても良さそうです。

実機で動かしてみると、3つのウィンドウの数字のずれはだいたい103のオーダーになりました。大体揃っています。

4. 優先順位をつけよう(1)

タスクB0〜B2に優先度をそれぞれ1, 2, 3と付けたもとでカウント数の測定を実機で行いました。30回分の平均は下の表のとおりです。 タスクB0に対するカウント数の比もともに示しています。

タスク カウント数
B0 3.175(9)×107 1
B1 5.715067(3)×107 1.80
B2 8.32(2)×107 2.62

タスクB0, B2はばらつきがかなり大きくなっています。原因はよくわかりませんが、タスクAの影響でしょうか。 比も2倍、3倍からは少し離れています。

5. 優先順位をつけよう(2)

前節の状態だと確かにマウスの動きが少し遅い気がします。本にはタスクAの優先度を上げれば解決するとありますが、試してみたところ却ってカクつく ようになってしまいました。おそらくスリープ時にtask_timerを特にいじっていないことが原因です。タスクAがスリープしてタスクB0に移行すると、 タスクB0から次に移るまでの時間がタスクAの優先度によって決まってしまいます。つまり、Aの優先度を10とすると、 キーボードやマウスの入力に対する処理が約100 msごとにしか行われないことになります。

実際、task_b_mainをその時点でのcountを表示するように変更してタスクAの優先度を100程度まで大きくすると上記のような挙動が確認できます (タスクB0のカウントがタスクAの優先度を反映して1秒程度増え続ける)。タスクAの優先度を大きくしたもとでスリープをしないようにすればマウスの動きなどは快適にはなりますが… 一応改善を試みましたが、タイマの実装に手を加える必要がある気がして面倒になったので諦めました。スリープしうるタスクに極端に大きい優先度を指定するのは良くなさそうです。

なお、この節でレベル分けを導入すれば上のような問題は起こらなくなるはずなので、気にせず先に進むことにしました。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜15日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、15日目です。 他の章へのリンクはここにあります。

1. ・2.

10秒だと長いと感じたら、3秒でスイッチするようにしても良いかも知れません。うまく動かない可能性を考えると早く結果が分かるに越したことは無いので… タスクBから帰ってくる時間の5秒についても同様です。

3.・4. 簡単なマルチタスクをやってみる

575。

task_b_maincountを本ではint型で持っていますがunsigned intの方が良いと思います。sprintf中の%d%uに変更。

5. ・6.

符号なし32 bit整数の取りうる範囲は0〜4,294,967,295なので、表示桁数は10桁で十分です。 実機で測定を行い、30回分の平均を取ると8.39086(2)×107カウントでした。

7. もっとマルチタスク

「Harimainからタスクスイッチ関係の部分を消す」とあるのは、farjmptimer_tsあたりを消すということです。tss_atss_bに関する記述は 残しておきます。また、bootpack.cからmt_initを呼ぶ必要があります。

前節と同様に実機で測定を行い、30回分の平均を取ると8.39106(2)×107カウントでした。確かに速くなっています。 13日目最後の測定結果と比較すると、次の表のようになりました。

カウント数
13日目最後 1.6813335(3)×108 1
15日目-6 8.39086(2)×107 0.499060
15日目-7 8.39106(2)×107 0.499072

大体1/2程度のカウント数になっています。CPUの処理速度が高いせいか、本に書かれているほど大きな差は出ませんでした。

Ubuntu 18.04.1 LTSでやる 30日OS本 〜14日目〜

Ubuntu 18.04.1 LTSでやる 30日OS本、14日目です。 他の章へのリンクはここにあります。

1. また性能を測定してみる

タイマを490個追加して実機で測定を行った結果、以下のようになりました。 それぞれ5回測定し、その平均を示しています。

ずらしあり ずらしなし・番兵なし ずらしなし・番兵あり
1.1769302(2)×109 1.1769335(2)×109 1.1769336(1)×109

番兵の有無による違いは誤差の範囲内ですが、前回の作業で確かに改善できているようです。

2. 高解像度にしよう(1)

本の通りに変更を行いQEMUで実行したのですが、真っ黒な画面しか出ませんでした。 原因はVRAMのベースアドレスが合っていないことのようです。

ここや次の節を参考にして正しいVRAMベースアドレスを取得すれば良いのですが、 とりあえずQEMUで動かしたいという場合はasmhead.asm

MOV     DWORD [VRAM],0xe0000000

となっているところを

MOV     DWORD [VRAM],0xfd000000

とすれば動作します。

3. 高解像度にしよう(2)

以前にも似たようなことがありましたが、参考URLは本に書かれているもの(ttp://community.osdev.info/?VESA)から http://oswiki.osask.jp/?VESAに変わっているようです。

本では4種類の画面モード番号が紹介されていますが、実機で動かす場合は画面サイズに合った解像度にしたくなりますね。 そうした場合は英語版WikipediaのVBEの記事にある番号を指定すると 良さそうです。なお、リンク先の表で数字の末尾にhとあるのは16進数、そうでないものは10進数っぽいです。 また、画面モード番号の指定によってはエミュレータでは動かない(scrn320にジャンプして320x200になる)ことがあります。

私が使っているノートPCでは、画面モード番号に893を指定することで1920x1080の解像度で実行できました。

4.・5. キー入力

Haribote OSを起動し実際にキーを押して調べると、表にある値の一部は別のキーを押したときにも出るように見えます(テンキーの0の番号であるはずの52がInsertを押しても出る、など)。 参考ページ(http://oswiki.osask.jp/?(AT)keyboard)によると、E0拡張キーコードのように2バイト以上送られてくるものの末尾の1バイトが他のキーコードと重複している、ということのようです。

とりあえず英数字と一部の記号に対応するだけならこのことを考慮する必要はなさそうなので、本の通りにやって大丈夫そうです。 また、本にはQEMUでは「@」などのキーがうまく動かないと書かれていますが、問題なく動作しました。

6.・7. おまけ

動く。楽しい。

f:id:wisteria0410ss:20190213052721p:plain
左クリックするとその位置にウィンドウが移動する。
テキストボックスのようなものにはキー入力に応じて文字が出る。[Back Space]で消せる。