Open15

30日OS

jun_networksjun_networks

1日目

必要なやつを入れる

  • ghex: バイナリエディタ
  • QEMU: OS用の?エミュレータ
  • nasm: アセンブラ
sudo apt install qemu ghex nasm

helloos.imgを以下のコマンドでバイナリエディタで開き本の通りに打ち込む

touch helloos.img
ghex helloos.img

以下のコマンドでQEMUでhelloos.imgを起動する

qemu-system-i386 helloos.img

ここで上手く動かない場合は打ち間違いの可能性があるのでとりあえずそのまま先に進む. そうすると今度はバイナリを手打ちするのではなくアセンブリを使って書くというのがあるので, それをアセンブル(後述)して生成されたイメージを使うと上手くいくかも.

気になる場合はアセンブリをアセンブルしたイメージとdiffを取ってみると良い.

diff <(xxd helloos.img) <(xxd helloos_from_asm.img)

How do I compare binary files in Linux?

アセンブリ

helloos.asm を作る

helloos.asm
; hello-os
; TAB=4

; 以下は標準的なFAT12フォーマットフロッピーディスクのための記述

    DB      0xeb, 0x4e, 0x90
    DB      "HELLOIPL"      ; ブートセクタの名前を自由に書いて良い(8バイト)   ; IPL(Initial Program Loader)   ; DB命令で文字列を書くとそれに対応する文字コードを調べて1バイトずつ並べてくれる
    DW      512             ; 1セクタの大きさ                       (512にしなければいけない)  ; DW (DataWord) 命令は2バイト(16bit)
    DB      1               ; クラスタの大きさ                      (1セクタにしなければいけない)
    DW      1               ; FATがどこから始まるか                   (普通は1セクタ目からにする)
    DB      2               ; FATの個数                             (2にしなければいけない)
    DW      224             ; ルートディレクトリ領域の大きさ            (普通は224エントリにする)
    DW      2880            ; このドライブの大きさ                      (2880セクタにしなければならない)
    DB      0xf0            ; メディアタイプ                            (0xf0にしなければならない)
    DW      9               ; FAT領域の長さ                             (9セクタにしなければならない)
    DW      18              ; 1トラックにいくつのセクタがあるか         (18にしなければならない)
    DW      2               ; ヘッドの数                                (2にしなければならない)
    DD      0               ; パーティションを使っていないのでここは必ず0   ; DD(Data DoubleWord) は4バイト(32bit)
    DD      2880            ; このドライブの大きさをもう一度書く
    DB      0, 0, 0x29      ; よくわからないけどこの値にしておくといいらしい
    DD      0xffffffff      ; たぶんボリュームシリアル番号
    DB      "HELLO-OS   "   ; ディスクの名前                            (11Byte)
    DB      "FAT12   "      ; フォーマットの名前                        (8Byte)
    RESB    18              ; とりあえず18バイト開けておく

; プログラム本体

    DB  0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    DB  0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    DB  0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    DB  0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    DB  0xee, 0xf4, 0xeb, 0xfd

; メッセージ部分

    DB      0x0a, 0x0a      ; 改行を2つ
    DB      "hello, world"
    DB      0x0a            ; 改行
    DB      0

    RESB    0x1fe-($-$$)    ; 0x001feまでを0x00で埋める命令 ;0x1fe = 510  ;($-$$) にはここまで並べた命令の大きさ(バイト)が入っている

    DB      0x55, 0xaa      ; 最初のセクタ(ブートセクタ)の最後の2バイトが 55 AA なら実行してくれる ; 1セクタ=512バイト

; 以下はブートセクタ以外の部分の記述

    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    4600
    DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    1469432


; nasm hello.asm -o helloos_from_asm.img  でバイナリーを出力できる

以下のコマンドでアセンブルする

nasm hello.asm -o helloos_from_asm.img

-lオプションで対応する機械語を出力出来る

nasm hello.asm -o helloos_from_asm.img -l helloos.lst
jun_networksjun_networks

アセンブリ

DB命令: (data byte). ファイルの内容を1バイトだけ直接書く命令
RESB命令: (reserve byte): 指定したバイト数分予約(あけておく)する

レジスタ

AX: アキュムレータ(accumulator:累積演算機という意味)
CX: カウンタ(counter:数を数える機械という意味)
DX: データ(data:データという意味)
BX: ベース(base:土台とか基点という意味)
SP: スタックポインタ(stack pointer:スタック用ポインタ)
BP: ベースポインタ(base pointer:ベース用ポインタ)
SI: ソースインデックス(source index:読み込みインデックス)
DI: デスティネーションインデックス(destination index:書き込みインデックス)

jun_networksjun_networks

Day03

フロッピーディスクの構造

フロッピーディスクは80シリンダあって, ヘッドが合計2つあって, さらにそれぞれのシリンダに18セクタがあり, 1セクタは512バイトなので

80 * 2 * 18 * 512 = 1474560 = 1440 [KB]

jun_networksjun_networks

Day03

BIOSが設計された当時は32bitレジスタがCPUに載せれなかったらしく, そのままではレジスタが持てる値がメモリアドレスの範囲より小さいので, しょうがないので補助的な役割をするセグメントレジスタを作った.

今は32bitレジスタ(EBXレジスタとか)は普通にあるけど, 上記の理由でBIOSでは読み込み先アドレスを指定するのにEBXレジスタなどの32bitレジスタではなく, セグメントレジスタと他の16bitレジスタを使ってメモリアドレスを計算し, プログラムを読み込む位置を指定する

メモリ番地の指定には常にセグメントレジスタを一緒に指定しなければならないという決まりがある. 省略した場合, [DS: を指定したものとみなされる.

例: MOV CX, [1234] は正確に書くと MOV CX, [DS:1234] となる.

そして[DS:1234]DS * 16 + 1234 を意味する. なので, ブートセクタが読み込まれた時にDSレジスタを0で初期化しないといけないのだ.

jun_networksjun_networks

Day03

IPL(Initial Program Loader)の実装が一段落してきたところでOS本体の開発に取りかかるわけだが, Ubuntuでやる場合はツールとかが本とはだいぶ違うので他の人の記事見たほうが良いな

C言語の導入はLinux環境だとかなりツールが違うので他の人の記事を見たほうが良い

jun_networksjun_networks

Day04

Ubuntu20.04LTS の環境でやろうとしたらなぜか harib01f でコンパイル出来ずに詰まった.

どうやらリンカが上手くリンク出来てないのが原因だった.

解決策としてはgcc-7を使うとコンパイルすることが出来た. gccの別バージョンのインストール方法はこれ見ると良い

gcc-8以降を使うと .note.gnu.property というセクションが自動的に作成され, これが原因でリンク出来ないようだ. gcc-7 ではこのセクションは生成されないので正しくリンクすることが出来た.

jun_networksjun_networks

day13 タイマーの性能測定のところでウィンドウの文字列更新処理を削除するとマウスやキーボードが反応しなくなる

while (1)
	{
                // ここから
		sprintf(s, "%d", timerctl.count);
		putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10);
                // ここまで消すと動かない

		io_cli();  // 割り込み禁止!

マウスやキーボード, タイマーなどが全て効かなくなってるから, whileループが動いていない or 割り込みが発生していない?

jun_networksjun_networks

day14 set490(&fifo, 1); を追加するとマウスが動かなくなる.

これはtimer_settime() 内のタイマを挿入する処理が悪かった.

day13 タイマーの性能測定のところでウィンドウの文字列更新処理を削除するとマウスやキーボードが反応しなくなる

		io_cli();  // 割り込み禁止!
		if  (fifo32_getsize(&fifo) == 0) {
			io_sti();
			// io_stihlt();  // こっちにすると動く

io_cli()io_sti() が高速に切り替わり過ぎてCLI実行した後に来た割り込みが破棄されてるかなんかなってる?