Open49

WSL2で「30日でできる!OS自作入門」に取り組む

sassansassan

環境

WSL2
Ubuntu 22.04.4 LTS
CDドライブはないので出来るだけ手元やオンラインにあるものだけで頑張る
あんま関係ないけど
VSCode
Zsh

参考サイト

https://qiita.com/kamaboko123/items/f2f5b5511f717c3a55fb
これの中のリポジトリも参考にさせていただきました
https://zenn.dev/chorkaichan/scraps/28567f1358495b
https://github.com/zacfukuda/hariboteos

低レイヤに関する筆者のレベル

電子工作を趣味にしてて、PIC(マイコン)のアセンブリでフルスクラッチで色々(SDカードライブラリなど)書いたことがある。
CやC++の低レイヤの言語も割と出来て、C++は競プロ用のデバッグツールを作った経験がある。
ので、アセンブリが何かとか、コンパイルされたときにどんな感じにアセンブリになってメモリはどう使われるかとか、Makefileが何かとか、そういう基礎的なことはもともと知ってた。
(あと論理回路の仕組みは回路レベルで知ってるけど、それは今回関係ないと思う?)

ただ、所詮PICのアセンブリで、パソコンのCPUの命令セットは触ったことがないのと、言語は知っててもOSの仕組みはほとんど知らないって感じです!
それでも、僕が分からなかったところを書いていく感じなので、説明が端折られていたらごめんなさいmm

リポジトリ

https://github.com/philip82148/os-30days

submoduleは29日目までインストールしなくても使える。

sassansassan

1日目

sec. 1

大分前にやったのでこの投稿日時は全然違うのと、アプリのインストールなどで端折ってる手順があるかもしれない…。バイナリエディタのリンクは切れてた?気がするので窓の荘のリンクを使う。他にもいろいろ試してみたがこれが一番使いやすかった。

プロセッサエミュレータQEMU(キューエミュ(ム))をインストール。

sudo apt install qemu-kvm

この中のqemu-system-i386を使ってエミュレートする。

qemu-system-i386 -drive format=raw,if=floppy,file=helloos.img

普通にコマンドにファイル名を渡しても動くが、本と同じ環境を確実に再現するためにこの指定の仕方をする。まず、i386はiはインテルで、386は初めて32ビットを扱えるようになったCPU(インテルx86系列)(参考)。formatは指定しないとワーニングが出る。if(インターフェース)はディスクイメージを入れたことにするドライブ。この時点まででCPUの指定はなかったかもなのでなぜインテルx86系列なのかという話があるが、後々本が32ビットの話をしているのでこれでいいんじゃないかと。-cpuでCPUの型番が指定でき、公式ドキュメントによれば指定しなかった場合x86系列の全ての拡張が使えるようになるみたいだが、それは脆弱性があるのでやめた方がいいとのこと。でもこの本では恐らくどんなx86系でも動く命令しか使わないかなと思うのと、あまり本と違うことをして動かなかったら困るので、CPUの指定は一旦しないでおく。エミュレータはときどき失敗したように見えるが、ウィンドウの拡大とかリフレッシュさせると直ると思う。

とはいえqemu-system-i386じゃなくて他のCPUのエミュレータでも動くみたい。その互換性に関しては後で調べたい。

ディスクへ書き込みをしましょう、みたいなのがあるが、これはやってない。

sec.3

CDが読めずnaskが使えないのでnasmを使う。

sudo apt install nasm

NASMとはNetwide Assemblerの略でインテルx86を対象としたアセンブラ(Wiki)。アセンブリにディレクティブを加えることでCPUタイプを指定できる(公式ドキュメント)。こちらのCPUタイプも一旦指定しないでおく。デフォルトでは全ての拡張命令が使える。リポジトリのファイルとしてはhelloos2.nasmで、以下でコンパイル、実行。RESB命令はワーニングが出るのでTIMESに置き換えてあるのと、他にも変えてるとこがあるがそれは参考文献を参照。

nasm helloos2.nasm -o helloos2.img
qemu-system-i386 -drive format=raw,if=floppy,file=helloos2.img  

なお、VSCodeの拡張機能としてはNASM Language Supportと、CoolSpy3's Assembly Formatterを使っている。

レジスタの初期化にわざわざAXを使っているのは、レジスタによっては定数初期化の命令がないからみたいだ。試しにAX0とかにして保存すると、拡張機能に怒られると思う。

sassansassan

2日目

sec. 2

nasm helloos3.nasm -o helloos3.img
qemu-system-i386 -drive format=raw,if=floppy,file=helloos3.img

CD読み取れんのでブートセクタ以降のコードがどうアセンブリになってるかが分からん。と思ったけど、逆アセンブルしてもアセンブリにならんのでそもそも命令じゃないっぽい。まあ後でわかると信じよう。
ORG疑似命令を若干勘違いしていたのだが、この疑似命令により0x7c00に配置されるのではなく、0x7c00に配置されるのは決まっているからそれをアセンブラに教えるための命令だとわかった。このおかげで$による相対位置で指定ができる。

sec. 4

make helloos4

何やってるかはMakefile参照。ddコマンドはブロック単位でファイルをコピーするコマンド(参照)で、一つ目のddはただのコピー、二つ目はファイルサイズを1474560にしている。なお、空いた部分は0フィルされる(スパースファイル)。これを参考にstraceしてlseek関数について調べればなぜ0フィルされるかわかると思う(参考)。ただ、lseek関数ではファイルサイズが変更できないとのことだが、変更できてしまっている(statdiffを使って調べた)。これはバグなのか?もしバグだったら以下のように置き換えるといい。

# 2つ目のddはこうも書ける
dd if=/dev/zero of=$@ bs=1 count=1 seek=1474559
sassansassan

3日目

sec. 1-4

cd harib00*
make run

4つのフォルダはMakefile共通である。

sec. 5

sudo apt install mtools
cd harib00e
make run

Makefileが変更有、haribote.nasmが新規追加。
MakefileのmformatはMS-DOSフォーマット(参考1参考2)を行うものらしい。オプションは-f 1440がファイルシステムのサイズを"1440K, double-sided, 18 sectors per track, 80 cylinders"(man mformat参照) にする、-Bがブートセクタの指定、-CがMS-DOSファイルシステムの載ったディスクイメージファイルを作る、-i $@がフォーマットするファイルである。mcopyの方は最後に::がついているが、これはディスクイメージ内MS-DOSファイルシステム内のルートのパスだと思う。理由はman mtools上で::ファイル名みたいな例があるのと、mdirmmdを使って調べてみたから。他のサイトだとmformatの方でも::をつけているが、mformatの方はなくても動くので省略可能と判断した。mcopyの方で省略するとバグる。ここら辺の説明がマニュアルで乏しいのはなぜなのか😭。意味としては、Linuxのファイルシステムと、フロッピーディスクのファイルシステム(MS-DOS)が違うから、mtoolsを使って読み書きしないといけないということだろう。

sec. 6

cd harib00f
make run

ipl.nasmとharibote.nasmに変更有。

sec. 7

cd harib00g
make run

haribote.nasmに変更有。真っ黒な画面が出てくると思う。なんか最初調子悪かったが、ALの他のモードを試したりしていたら直った。

sec. 8

cd harib00h
make run

haribote.nasmに変更有。

sec. 9

cd harib00i
make run

ipl.nasm以外は変更有。
gccのコマンドはこの記事の中のリポジトリを参考にした。-nostdlibはシステムライブラリのリンクをしない、-m32は32ビット環境用のコードを生成する、-fno-pieはPIE(Position Independent Executable)にしないというオプションである。-Tはリンカスクリプトの指定で、このページを参考にしているみたい。このスクリプトの仕様は今度調べることにしよう。

sec. 10

cd harib00j
make run

nasmfunc.nasm追加、Makefile、bootpack.cに変更有。nasm-f elf32はLinux向けi386のコードを生成するオプションである。どう違うのかわからないが、Windows向けにしても動いた。MacOS向けは動かない。ここに関しても後で調べたい。

sassansassan

4日目

sec. 1

cd harib01a
make run

bootpack.cとnasmfunc.nasmに変更有。

sec. 6

cd harib01f
make run

bootpack.cとnasmfunc.nasmに変更有。

sec. 7-8

cd harib01*
make run

bootpack.cに変更有。

sassansassan

5日目

sec. 4

以降コマンドは省略する。いつも通りmake runでよい。また、今回からフォーマッタclang-formatを導入したのと、僕好みの書き方にちょいちょい変えてある。あと、unsigned charcharが混在していたが、文字('a'等)や文字列(const char *)を表す場合を除きunsigned charに統一した。なお、clang-formatはVSCodeの拡張機能があるのと、ルートの.clang-formatというファイルが設定ファイルである。ちなみにランゲージサーバーはclangdが僕のおすすめ(VSCodeの拡張機能にあるので入れてみよう)。
(ちょいちょい思うのだが時代のせい(そもそも言語自体やmake等が高機能でなかったか、ノウハウが蓄積されてなかった)なのか何なのかわからないが、可読性や書き方などこの本のコードの書き方には思うことがある。ただ、いちいち修正してたら時間がかかるので気になったところだけ変えることにする。)

sec. 5

hankakuフォルダ追加、bootpack.c、Makefileに変更有。
本ではhankakuの配列のオブジェクトをつくってリンクしているが、ここではhankaku.cというCファイルを作ってリンクさせている。

sec. 7

libフォルダ追加、bootpack.c、Makefileに変更有。
lib/my_spintf.cはこちらを参考にさせてもらった。

sec. 9

bootpack.c、nasmfunc.nasmに変更有。
なお、勘違いしてしまうがこの時点では画面上の変化はない。

sassansassan

6日目

sec. 3

ただソースファイルを分割しただけ。cフォルダの中身とMakefileのみ変更有。

sec. 6

bootpack.c、bootpack.h、dsctbl.c、int.c、nasmfunc.nasm、Makefileに変更有。

学んだこと

今度からコミットログ貼るようにしようかな。
C言語自体は元から知ってるし説明することがなくなってきた。だったら学んだことを書こうかな。
割り込みはPIC(Programable interrupt Controller)という別チップで監視されてる。割り込みがCPUに通知されるとIN,OUTの信号線でどの割り込みかを通知する。PICはマスタとスレーブの二つを使って15個の割り込みを監視する。それぞれの割り込みで呼び出される関数の番地はIDT(Interrupt Descriptor table)に登録しておく。IDTはメモリに配置し先頭アドレスを専用のレジスタに登録しておけばよい。そしてIDTの設定のためにはセグメントの設定が必要。これはなぜかというとIDTの一つ一つのデータは関数の番地(正確にはオフセット、32bit)、セグメント番号(16bit)、割り込みを表すトークン?(16bit)で構成されているから(8バイト)。セグメント番号もGDT(Global (Segment) Descriptor Table)にIDTと同じように登録する。GDTの一つのデータはセグメントの大きさ(20bit)、先頭アドレス(32bit)、セグメントの属性(書き込み・実行禁止、システム専用など、12bit)で構成され、8バイト。セグメントの数は8192個で、GDTのメモリ上のサイズは8*8192=64kB。セグメントの大きさを表すビットは20ビットしかないので、1MB以上の場合は属性のGフラグを立て、ページの数として解釈させる。その場合×4kBのサイズになる。よく言うSegmentation Faultとか、ページとかってこれのこと言ってるんだろうな。なるほどねぇ。

sassansassan

IDTの一つ一つのデータはGATEとプログラム上ではなっていたが、これは後で説明があるのか。それともただの割り込みのGATEという意味だろうか。GATEの構造体の詳しい説明がなかったが後で説明があるのか読み落としたのか。
メモリ容量の確認は今回は書き込みを行って本当に覚えているかでチェックした。その際にキャッシュがバッファにならないようにオフにした。キャッシュの管理ってOSがやってるんじゃなくてハードウェア由来なのか。

割り込み時のレジスタの内容はスタックに記録する。そのとき、PUSHADといった命令があるが、これのクロック数を調べたい。あと、これは全てのレジスタを記録できているのか?

sassansassan

7日目

sec. 1

コミットログ
ちなみにこの後編集していたりするのでフォルダのその後のログを確認すると良い。
なお、これのついでにmy_sprintf.cをリッチにしておいた。

sec. 5

コミットログ
今後言わないが、今後のコミットもこの後編集している可能性がある。

sec. 7

コミットログ

学んだこと

マウスもキーボードも複数バイトのデータを送ってくるのでFIFOにためて処理する。なお、それぞれのbyteで毎回割り込みが発生する。マウスは3byteそれぞれで押しているボタンの情報、x、yの移動量を送る。押しているボタンに関しては押したときと離したときに割り込みを発生させているようだ。送るデータには特に離したという情報はつけず、今押しているボタンの情報を送る(割り込みの発生した回数を表示させて調べた)。キーボードは2byteでキーを押しているか離したかの情報とキーコードを送る(p142)。押している間ずっと割り込みが発生するみたい(割り込みの発生した回数を表示させて調べた)。確かに何も押してないのに押したままと勘違いするみたいな誤動作を考えるとこれがいいかもだ。と思ったら、キーによって違うようだ。Ctrlキーは押したときと離したときにしか反応しない。キーコードは確かにCtrlは2byteだが、他のキーは同じByteを重ねるときもある。そうか、これで長押ししてるということを示してるのかな。また、長押しフェーズに入るまで割り込みは発生しないから、長押しの時間とかもハードウェア側で処理しているっぽい。

sassansassan

マウスやキーボードで1byteずつ割り込みが発生しているのは最新のCPUでもそうなのか?
長押しの時間とかキーのリピート速度って確かOS側で設定可能だった気がするがそれはBIOSの設定か何かか?

sassansassan

8日目

sec. 4

コミットログ

学んだこと

32ビットモードへの道はデバイスの調整や割り込みの禁止、メモリのコピー(転送)などのbootpackを実行できるだけの作業をしている。

sassansassan

メモリの転送についての理解が浅いが後でまた説明があるっぽいので放置する。

sassansassan

9日目

sec. 1

コミットログ
リファクタリングしただけ。

sec. 2

コミットログ
僕の環境(gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0)ではコンパイラの最適化は起こらなかった。qemuのメモリのデフォルト値はマニュアルに書いてある。

sec. 3

コミットログ

sec. 4

コミットログ

学んだこと

動的メモリ管理用の(空き領域の)表を配列(free[])で作った。解放時はそのメモリが前後の空き領域の間に入るようにO(N)のswap動作をするようにして入れ込む。もし前後がつながるようならつなげる。

sassansassan

確か普通はメモリはリンクリストで管理してるんじゃなかったっけ。
でもその場合順番に並べて管理するみたいなのは難しいな。そこはどうしてるんだろ。
free[]の4000個で十分らしいが本当だろうか。最近のOSはどうしてるんだろう。

sassansassan

10日目

sec. 1

コミットログ
毎回リファクタリングがだるいが、今回はリファクタリングだけじゃなく新しい関数の追加もある。
ちなみにもしかしたら次から最初からファイル分割してかけるかも。

sec. 2

コミットログ

sec. 4

コミットログ
コードが読みずらい。全部終わったら大リファクタリング祭りでもしようかしら。
CPUが早すぎで分かりづらかったが、確かに少しちらつきがなくなった気がする。

学んだこと

グラフィックはレイヤー化(この本ではSheet)して変更部分だけ四角形に更新。更新は全レイヤをイテレートする。

sassansassan

全レイヤのイテレートって遅くないのかな?そこの計算時間を後で見積もりたい。

sassansassan

11日目

sec. 2

コミットログ
Window外サポートって些細だけど興味深いな。シートで管理してるからWindow外を無限遠まで考えなくて良いという。

sec. 3

コミットログ

sec. 4

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

sec. 8

コミットログ
変更部分についてmapを更新してから書き込む。mapの計算量は変わらないが、mapを作り替えなくて良い場面(各シートのスライドがなく、不透明が透明になったりしていないとき。一枚のシートにバックグラウンドを書いてそのあと文字を書いたりしている場合など)はシート一枚分の書き込み量になる。mapを作り替える場面でも追加の書き込み量は書き換えなくて良いシート(変更があったシートより上のシート)が分かるので少ない。

学んだこと

やったこととしてはWindowを表示させてみたのと、mapなどを使い一回当たりの画面更新量を減らすかつレイヤが重なっている部分の再描画回数を減らしちらつきを抑える工夫。基本的に前回のグラフィックの続き。

sassansassan

計算量解析的に速度が怪しい。実際のPCもこうなっているのか確認。

sassansassan

12日目

sec. 2

コミットログ

sec. 4

コミットログ

sec. 7

コミットログ

学んだこと

PIT(Programmable Interval Timer)を使ってタイマーを作った。タイマーは複数同時に動かせるようになっていて、OSとはFIFOでやり取りしている。複数のタイマーは設定時に時間順にソートするようにして、先頭のものだけ見張っていればよいようにしている。

sassansassan

13日目

sec. 2

コミットログ

sec. 3

harib10cのコミットログ harib10dのコミットログ
一つ目はフォルダ名がharib10fとなっているが間違い。
恐らくエミュレータのせいだと思うが、カウントのたびに画面更新をしないと実行速度が遅くなるという問題が発生した。毎ループで割り込みの禁止・許可をしているから、その周期があまりに短すぎることが問題ではないかと疑い、空forループを入れることで実行速度を回復させることができた。

sec. 4

コミットログ バグ修正
本ほど早くならなかった。まあforループで1万も回しているから当たり前である。

sec. 5

コミットログ

sec. 6

コミットログ

学んだこと

今までのアルゴリズムの改善を行った。マウスやキーボード、タイマのFIFOを一つに統一した。その際、それぞれのコードをシフトすることでそれぞれのデータを表していて、そのためにFIFOを1データ4byteに拡張した。タイマはリンクリストに変更し、ずらし処理をなくした。(あと番兵を使ってコードを短くした。)最近リファクタリングばかりだな。

sassansassan

STIやCLI命令はCPU上で割り込みを禁止して、PICは監視を続けるイメージ。割り込みがスタックした場合、STI命令が実行された瞬間に割り込みされるものと予想しているが違うのか。でなければQEMUが割り込み禁止・許可の周期でPITのカウントが遅くなる理由(QEMU上のPITはクロック数のカウントで動いてるという前提だが)が分からない。

sassansassan

15日目

sec. 2

コミットログ バグ修正

sec. 4

コミットログ バグ修正

sec. 5

コミットログ
例の問題が発生するので空for文を追加している。

sec. 6

コミットログ

sec. 7

コミットログ

学んだこと

JMP命令で飛んだ先のセグメントが実行可能セグメントではなくTSS(Task Status Segment)だった場合、CPUは全レジスタを今行っているタスクのTSSに書き込む。また、JMP先のTSSセグメントが保存しているレジスタの情報を全てロードし、(拡張)命令ポインタ(EIP)もその保存された値になる。TR(Task Register)というレジスタがあり、今行っているTSSを覚えるレジスタだそうだ。これはタスクスイッチング時に自動的に変更されるようで、マルチタスクの初期化時にだけ明示的にセットしていた。スタックも新しく用意してESPに入れる。自動で定期的なタスクスイッチングをするためには、定期的に割り込んで、割り込みハンドラの中でJMP命令を行う(次タスクスイッチングをして戻ってくる場所は割り込みハンドラ内のその次の命令である)。なお、ここで使うJMP命令はfar-JMPといって、CS(コードセグメントレジスタ)とEIPの両方に書き込む命令である。それと、今更気づいたがこのOSやC上ではアドレスが32bitで表される。なんかおかしいとずっと思っていたがそういうことか。

sassansassan

セグメントレジスタはコード用(CS)とスタック用(SS)とデータ用(DS)があるらしい(他にもあるがおまけらしい)。p134の記述によると「C言語では『DS(データセグメント)もES(エクストラセグメント)もSS(スタックセグメント)も同じセグメントを指している』という思い込み」があって、だからSSをDSとESに代入するそうだ。セグメントが違うデータにはアクセスしない前提なのか。
また、もしそうならSS=DS=ESが保たれているC言語の途中で呼び出された割り込み処理内でわざわざ代入する必要はないと思うがどうなのか。まあこれに関してはタスクスイッチング中などは異なる可能性あると思う。

と思ったが、どうやらセグメントの意味を完全に誤解していた。セグメントは4GBにアクセスするための手段だと思っていたが、32bitモードになった今セグメントはその手段ではなく、プログラムのアドレスを0からにリセットして使えるようにするためのものらしい。なんじゃそりゃ。だったらDS=ES=SSも納得できるな。(もちろんCSは一致していない。)
今回の場合、CS=2<<3、DS=ES=SS=1<<3で、どちらのタスク(マルチタスクの)もこれらのセグメントを使っている。1<<3は4GB全体を示すセグメント、2<<3はbootpack.sysがあるセグメントである。ここら辺の整理をしなければ。読み飛ばした8日目の32bitモードへの道の記述の中にヒントがあると思われる。

タスクスイッチング中のJMP命令によるレジスタの保存のクロック数が知りたい。

TSSの登録の際、limitを103としていたが、これはなぜなのか。104byteあるのにどういう意味があるのか。

ちなみにJMP命令の正体はEIPへのMOV命令らしい。ここら辺も整理したり、使えるレジスタについても学びなおしたい。

sassansassan

16日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

学んだこと

タスクのスリープや優先順位を付けた。タスクのスリープやウェイクアップはタスクスイッチのリストからタスクを外したり追加したりすることで行う。タスクの優先順位は一回当たりの起動時間と、レベル付けによって行う。高レベルのタスクが実行されている間は低レベルのタスクはブロックされる。もし早く処理が終わったらタスクはスリープされるから、低レベルタスクも実行されるということだろう。

sassansassan

レベルの高いタスクが永遠に終わらない場合、それより下のタスクが全部停止してしまうなら、結構不都合な気がするけど、実際のOSもこうなっているのだろうか。

sassansassan

17日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

学んだこと

アイドルタスクを作って全てのタスクがいなくなった際にHLTするようにした。Caps Lockなども一つのコマンドとしてキーボード割り込みとして送られてくるので、OS側のメモリ上のCaps Lockのフラグを変更する。

sassansassan

18日目

sec. 2

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

学んだこと

mem、cls、dirコマンドを作った。dirコマンドはBIOSでメモリ上に読んでおいたファイルエントリ情報を表示させている。

sassansassan

ディスクはBIOSを使わないと読めないからメモリに読んでおいてやっていたがさすがに実際のOSでは毎回ディスクから読んでいるはず。じゃあその瞬間は16ビットモードに切り替えているのか?

sassansassan

20日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

sec. 8

コミットログ

学んだこと

APIの作り方はいろいろあるが、今回は割り込みルーチンを使った。割り込みルーチンに入る前にレジスタに値を入れ、その値で呼び出すAPIを分岐する。BIOSのそれと同じ。なお、今回ルーチン内からC言語の変数をC言語の関数に渡して実行することをしており、変数は特定のアドレスに保存して渡している。

sassansassan

割り込みルーチンを使わないAPIの作り方はどんなものがあるか知りたい。アセンブリからC言語の関数を呼び出す前にレジスタの保存を行っていたが、C言語でもこれを毎回やっているのか?呼び出された関数側でやらないのか?

sassansassan

21日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 7

コミットログ

学んだこと

アプリ用のセグメントを作り、そこに飛ぶことで、自動的にOS用のセグメントにアクセス不可にし、スタックなどの切り替えをする。この際、現在のタスクのTSSにESP(スタックポインタ)とSS(スタックセグメント)を手動で保存する(これはタスクスイッチで上書きされたり読み出しされたりしない別のプロパティとして保存される)。アプリ用のセグメントへの飛び方はアプリからOSを呼び出して戻るときのようにスタックを調整し無理やりRETFを使って飛ぶ(OSからアプリを呼び出すことは出来ないようになっている)。TSSへの保存を手動で行っているが、アプリの終了時も終了APIを呼び出して手動で保存していたESPを書き換えて、ここでようやく本来のstart_app()のRETを使って元の呼び出し元へ戻る。この際、TSSに保存していたスタックポインタにスタックを戻す。

sassansassan
  1. アプリ用のセグメント前にCS(コードセグメント)とEIP(命令ポインタ)をPUSHするのはRETするから理解できるとして、その前にSSとESPをPUSHしているのはなぜだろうか。アプリからOSを呼び出す(CALL)ときは必ずそれが行われているということだろうか。おそらくそうだろうが、そこの説明がなかった気がする。
  2. TSSにESPとSSを保存していて、API呼び出しの際にSSの読み出しが明示的に行われていなかったがこれは自動的に行われるということだろうか。TSSの変数の命名をちゃんとすればわかると思う。
  3. Cで作ったアプリの最初の6バイトを書き換えていて、mainをCALLしているとの説明だったが、正確にはそうではないという含みがあるのはどういう意味なのか。
  4. Cで作ったアプリのデータ用セグメントを適当に64kBにしていたが、静的データを使い始めるともっときちんと決める必要が出てくるはず。
sassansassan
  1. アプリのメタ情報がJMP命令になっていてHariMainに飛ぶJMP命令として解釈できると後に説明がある(p461)。
  2. のちにアプリの先頭(コード領域)にメタ情報を組み込み、そこでデータセグメントのサイズなどを指定できるようにしていた(p460)。
sassansassan

22日目

sec.1

コミットログ

sec.2

コミットログ

sec. 3

コミットログ

sec.4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

学んだこと

IN、OUT、CLI、HLTなどの命令はアプリ側から実行すると一般保護例外になる。指定された場所以外のOSのfar-CALLも例外になり、今のところINT 0x40(API、内部でOS側の関数のfar-CALLをする、この割り込みのみアプリ呼び出し許可をしている)でしかOSを呼び出すことは出来ない。例外は他にも除算例外、無効命令例外などがある。アプリの強制終了は他のタスクからTSSのEIPを書き換えて、次にタスクスイッチした時にアプリを終了するAPI(の中身)に飛ぶようにする。今回、C言語アプリ内で静的データを使ったので、そのためのデータ領域への転送をする処理を追加している。

sassansassan

23日目

sec. 1

コミットログ 修正コミット

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 6

コミットログ 修正コミット 修正コミット

sec. 8

コミットログ

学んだこと

malloc APIはOS側のmemman_allocを呼び出してメモリの確保を行う。この際、アプリのメタ情報(コード領域)から取得したmalloc用のデータ領域をfreeして初期化する。MEMMAN構造体はアプリ内に用意する(malloc領域の先頭に位置しAPI内で自動で参照される)。その他、点描画、線描画、ウィンドウリフレッシュ、キー入力APIを作った。キー入力APIはキー入力があるまでタスクをスリープさせる。強制終了のウィンドウの閉じ方は、SHEETとタスクを関連付けさせ、終了後にそのタスクに残っているSHEETをすべて開放することで行っている。

sassansassan
  1. 強制終了時のウィンドウの開放がそれに関連付けられたSHEETの開放なのはOOP的には違和感があるが、これは普通なんだろうか。まあ確かにメモリの開放じゃ終わらなくて、SHEETを再描画しないといけないのは分かるが。
  2. malloc領域のサイズ=データ領域のサイズ(128kB)-32kB-mallocの開始アドレスとしていたが、普通は必要に応じてアプリ用のセグメントの大きさを変更するものらしい。それはどうやってやるものなのか。
  3. なお、本ではmalloc領域が40kBとあるが、ldsファイルの中身的にそれ以上が割り当てられている気がする。
sassansassan

24日目

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

sec. 8

コミットログ

学んだこと

☓ボタンでウィンドウが閉じるようにしたり、マウスでウィンドウ移動したり切り替えをできるようにした。上のシートから順にマウスがクリックした部分が入っているかを調べることで行う。

sassansassan

26日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

sec. 8

コミットログ

sec. 9

コミットログ

sec. 10

コミットログ

学んだこと

4pxずつウィンドウを動かすことにしてMOV DWORD命令で画面を更新して画面の更新を早くした。コンソールをキーボードからいくらでも開けるようにした(基本的にはメモリを確保してtask_run()、その後はmain()からタスクのfifoにpushしてデータを送る。確保したメモリやタスクの情報はSHEETに保存することでアクセスする)。startコマンドやncstコマンドを作った。ncstコマンドはただコンソールの表示をしないでアプリを実行しているだけ。

sassansassan

27日目

sec. 1

コミットログ

sec. 2

コミットログ

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ
そこまでコンパイル時間はかかっていないため、フォルダ構成は変えず軽い整理や即時終了アプリの修正などを行った。

学んだこと

それぞれのタスクからしか参照できないローカルなセグメント(LDT(Local (Segment) Descriptor Table、GDTみたいな))をコード領域やデータ領域に用いることでその他のアプリからのアクセスをシャットアウトできる。LDTはタスクを切り替えるときにLDTRレジスタに保存する(スリープ中はTSS内)。LDTには8192個分(64kB)までテーブルを設定できる。LDTのセグメントの指定の仕方はLDTのセグメント番号×8+4とすることで指定できる。LDTRはシステム以外から書き換えることは出来ないため、これで他のタスクのローカルセグメントへのアクセスが不可能になる。LDTのセグメント番号はタスク間で被っても問題ない。LDTのセグメントはGDTにLDT用のセグメントとしてTSSのように登録しておく必要がある。この際にサイズを指定することで何個分のテーブルかが指定できる。

sassansassan

28日目

sec. 1

コミットログ

sec. 2

コミットログ
allocaは必要なさそうなので入れていない。代わりにスタック領域は広げてある(app.lds)。

sec. 3

コミットログ

sec. 4

コミットログ

sec. 5

コミットログ

sec. 6

コミットログ

sec. 7

コミットログ

学んだこと

ファイルAPIとコマンドラインAPI、日本語表示ができるようにした。Shift-JISとEUC-JPのフォントに対応できるようにしてあるが、意外と簡単な計算式でマッピングしなおしている。今回の場合言語モードはタスクごとに設定している。

sassansassan

毎度同じ質問だが、今回タスクごとに設定した言語モードは普通はどうしているのだろうか。標準出力APIに毎回文字コードを渡しているとか?ロケールの設定とか関係ありそう。

sassansassan

29日目

sec. 1

コミットログ ターミナルの高さを上げる

sec. 2

git submodule update --init --recursive
make run

コミットログ
TEK形式に変換するためにHariboteOS/z_tools_linuxを使った。WindowsならHariboteOS/z_tools_winを使えばいいと思う。

sec. 3

標準関数の追加 printf系の戻り値をintに変更

sec. 4

コミットログ

sec. 6

コミットログ

学んだこと

標準関数を作った。他はアプリづくりなど。mallocは確保した領域の先頭にサイズを保存していた。サイズ情報はどっか別に保存しているんだと思っていたが、いや確かにこれでいいな。

sassansassan

30日目

sec. 4

コミットログ

sec. 5

コミットログ
現状起動時間はそんなにかかっていないので、読み込みシリンダ数は変えていない。

学んだこと

コマンドライン計算機、テキストビューア、画像ビューアなどを作った。他にIPLのディスク読み込み速度を改善している。バグが残った状態だし、コードは読んでいないが、テキストビューアや画像ビューアは読むと何か発見があるかもしれない。でも画像ビューアは結局パレット色を使っちゃってるっぽい。

sassansassan

読み終えて

低レイヤについて学びたいと思い立ち、低レイヤーを学ぶための技術書をまとめてみるを参考にこの本に手を出した。最初やる気がわかず買ってから3日目に到達するまでに数か月かかってしまったが、その後は爆速で開発し1か月程度で終わらせることができた。何なら、後半は数日分/1日程度のスピードで進めていた。そしてこの分量で、OSは何をしているのか、APIはどう実装されているのか、OS作りは意外と難しくないんだということを知ることができた。30日でも、たった50kBでもOSは動作するということが分かった。率直にこれはすごいと思う(作者が)。

しかし、まだまだOSについて学ぶという観点では物足りないものがある。作者も最後に触れているが、ディスクアクセス、デバイスドライバ、ページング、マルチコア、GPU等々、それらについて触れられていない。また同時に、はりぼてOSではこうだが、普通のOSではどうしているんだろうという疑問があった。前者についてはコンピュータシステムの理論と実装 ―モダンなコンピュータの作り方みかん本(最近存在を知った)、後者についてははじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみがいいのではないかとみている。いずれにせよ、この本のおかげで他も読み進める速度は速いだろう。その意味ではこの本はさっさと終わらせるべきだったのだが…。

このスクラップにはいくつもの疑問を残してきている。それについてもいずれ調べ、その時にももう一度「勉強し終えて」のようなものを書きたいと思う。