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_putchar
はglobal
しておく必要があります。
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の三段構えに変更しました。 これに合わせてコンソールのウィンドウサイズも大きくしました。
日本語は化けてしまいますが、英数字に関しては問題なく表示されていそうなことが確認できました。
また、画面を大きくしたら入力可能な最大文字数も増えるので、配列のサイズを溢れない程度まで広げました。
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
を生成するのに必要なオブジェクトファイルを列挙するものです。
何をしているかというと
wildcard
でCのソースファイルを全列挙filter-out
で一部を除く。test.c
はsprintf
やstrcmp
など自分で書いた関数のテスト用のため、hankaku.c
は自動生成されるファイルで存在が保証されないために除いています。src/なんとか.c
をobj/なんとか.o
に置換- 上で挙げたCのプログラムからできるのではないオブジェクトファイル(
hankaku.o
とfunc.o
)を追加
という感じです。
5. ついに初アプリ
アプリ関係はapp
というディレクトリに入れることにしました。コンパイルは~~~.asm
から~~~.bin
を作るのと同様にすればよく、
できたファイルはイメージに放り込みます。なので、Makefile
には
bin/hlt.hrb: app/hlt.asm Makefile nasm $< -o $@
を追記し、ディスクイメージに入れるファイルを列挙しているFILES
にbin/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 ::
で行います。適当なファイルを書き込むコマンドを、Makefile
のharibote.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)
また非本質的ですが、keytable0
とkeytable1
はkeytable[2][0x80]
のようにまとめるとkeytable[key_shift!=0][i-256]
と書けて分岐が1つ減ります。
7. Lockキー対応
適当な変数でLockキーの状態を保持し、それに関する処理の際に参照できるようにしておきます。 最初にランプの状態を取得しておいて、Lockキーが押されるたびにLockの状態に合わせてそれを更新、キーボードのランプに反映という処理をします。
QEMUで実行する際、キーボードのLEDはホストOS側のLock状態と一致するようになっているようです。 うまくいっているかを確認するには実機で動かすことになります。
おまけ
キーボードのランプを好きに操作できるようになりました。タイマでの処理と組み合わせることで一定の周期でキーボードのランプを点滅させることも可能です。
0.1秒周期 pic.twitter.com/imQusPzBvI
— 里旬 (@wisteria0410ss) February 14, 2019
たのしいですね。
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_main
でcount
を本ではint
型で持っていますがunsigned int
の方が良いと思います。sprintf中の%d
も%u
に変更。
5. ・6.
符号なし32 bit整数の取りうる範囲は0〜4,294,967,295なので、表示桁数は10桁で十分です。 実機で測定を行い、30回分の平均を取ると8.39086(2)×107カウントでした。
7. もっとマルチタスク
「Harimainからタスクスイッチ関係の部分を消す」とあるのは、farjmp
やtimer_ts
あたりを消すということです。tss_a
やtss_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の解像度で実行できました。
画面のサイズに合わせた解像度(1920x1080)にして実機で起動 pic.twitter.com/Gb4rPeD4ic
— 里旬 (@wisteria0410ss) February 11, 2019
4.・5. キー入力
Haribote OSを起動し実際にキーを押して調べると、表にある値の一部は別のキーを押したときにも出るように見えます(テンキーの0の番号であるはずの52がInsertを押しても出る、など)。 参考ページ(http://oswiki.osask.jp/?(AT)keyboard)によると、E0拡張キーコードのように2バイト以上送られてくるものの末尾の1バイトが他のキーコードと重複している、ということのようです。
とりあえず英数字と一部の記号に対応するだけならこのことを考慮する必要はなさそうなので、本の通りにやって大丈夫そうです。 また、本にはQEMUでは「@」などのキーがうまく動かないと書かれていますが、問題なく動作しました。
6.・7. おまけ
動く。楽しい。