Closed31

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

Ch.1 PCの仕組みからアセンブラ入門まで

参考ブログ:
https://nomad.office-aship.info/mac-os1/

  • エミュレータ(qemu)を入れる
# arch -arm64 brew install qemu
# qemu-system-x86_64 -version
QEMU emulator version 6.2.0
Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
# qemu-system-x86_64 -fda helloos.img

Hello world画面

  • アセンブラ触るためにnasm入れる
# arch -arm64 brew install nasm
# nasm -v
NASM version 2.15.05 compiled on Oct 22 2021
  • ソース書く
; hello-os
; TAB=4

DB 0xeb, 0x4e, 0x90
DB "HELLOIPL"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HELLO-OS   "
DB "FAT12   "
RESB 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
DB "hello, world"
DB 0x0a
DB 0

RESB 0x1fe-$

DB 0x55, 0xaa

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
  • コンパイル。なんかエラー出る
# nasm helloos.nas
helloos.nas:35: error: attempt to reserve non-constant quantity of BSS space
- RESB 0x1fe-$
+ RESB 0x1fe-($-$$)
  • コンパイル
    めっちゃwarningでるけど、コンパイルはできてるので一旦良しとする
# nasm helloos.nas
helloos.nas:22: warning: uninitialized space declared in .text section: zeroing [-w+zeroing]
helloos.nas:35: warning: uninitialized space declared in .text section: zeroing [-w+zeroing]
helloos.nas:40: warning: uninitialized space declared in .text section: zeroing [-w+zeroing]
helloos.nas:42: warning: uninitialized space declared in .text section: zeroing [-w+zeroing]
  • 実行
    バイナリエディタで作成したものと同じ挙動となることを確認。
# qemu-system-x86_64 -fda helloos

Ch.2 アセンブラ学習とMakefile入門

Kindle版を買ったから、本来あるべきCD-ROMのデータが手元にないことに気づく。
試行錯誤しながらやってみる。

  • ソースコード編集
; hello-os
; TAB=4

ORG 0x7c00

JMP entry
DB 0x90
DB "HELLOIPL"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0, 0, 0x29
DD 0xffffffff
DB "HELLO-OS   "
DB "FAT12   "
TIMES 18 DB 0

entry:
  MOV AX, 0
  MOV SS, AX
  MOV SP, 0x7c00
  MOV DS, AX
  MOV ES, AX

  MOV SI, msg
putloop:
  MOV AL, [SI]
  ADD SI, 1
  CMP AL, 0
  JE fin
  MOV AH, 0x0e
  MOV BX, 15
  INT 0x10
  JMP putloop
fin:
  HLT
  JMP fin
msg:
  DB 0x0a, 0x0a
  DB "hello, world"
  DB 0x0a
  DB 0

  TIMES 0x1fe-($-$$) DB 0
  DB 0x55, 0xaa

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 4600 DB 0
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 1469432 DB 0
  • ディスクイメージ作成のためにMtoolsを入れる
# arch -arm64 brew install mtools
# mtools --version
mtools (GNU mtools) 4.0.36
  • Makefile作る
ipl.bin: ipl.nas Makefile
  nasm ipl.nas -o ipl.bin -l ipl.lst

helloos.img: ipl.bin Makefile
  mformat -f 1440 -C -B ipl.bin -i helloos.img ::

img:
  make -r helloos.img

asm:
  make -r ipl.bin

run:
  make img
  qemu-system-x86_64 -fda helloos.img
  • ファイル生成
    hello worldが表示されることを確認
# make run

Ch.3 32ビットモード突入とC言語導入

1. IPL

  • ディスク読み込み追加
ORG 0x7c00

JMP entry
DB 0x90
DB "HELLOIPL"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0, 0, 0x29
DD 0xffffffff
DB "HELLO-OS   "
DB "FAT12   "
TIMES 18 DB 0

entry:
  MOV AX, 0
  MOV SS, AX
  MOV SP, 0x7c00
  MOV DS, AX

  MOV AX, 0x0820
  MOV ES, AX
  MOV CH, 0
  MOV DH, 0
  MOV CL, 2

  MOV AH, 0x02
  MOV AL, 1
  MOV BX, 0
  MOV DL, 0x00
  INT 0x13
  JC error

putloop:
  MOV AL, [SI]
  ADD SI, 1
  CMP AL, 0
  JE fin
  MOV AH, 0x0e
  MOV BX, 15
  INT 0x10
  JMP putloop

fin:
  HLT
  JMP fin

error:
  MOV SI, msg

msg:
  DB 0x0a, 0x0a
  DB "load error"
  DB 0x0a
  DB 0

  TIMES 0x1fe-($-$$) DB 0
  DB 0x55, 0xaa

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 4600 DB 0
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 1469432 DB 0
  • Makefile編集
MAKE = make -r
DEL = rm -f

ipl.bin: ipl.nas Makefile
	nasm ipl.nas -o ipl.bin -l ipl.lst

haribote.img : ipl.bin Makefile
	mformat -f 1440 -C -B ipl.bin -i haribote.img ::

run:
	$(MAKE) haribote.img
	qemu-system-x86_64 -fda helloos.img

clean:
	-$(DEL) ipl.bin
	-$(DEL) ipl.lst
	-$(DEL) haribote.img
  • 実行
    エラーが出ないのでよしとする
# make run

2. エラー対策

  • 5回まで読み込みを試行するよう変更
  MOV AX, 0x0820
  MOV ES, AX
  MOV CH, 0
  MOV DH, 0
  MOV CL, 2
-  MOV SI,
+  MOV SI, 0

+retry:
  MOV AH, 0x02
  MOV AL, 1
  MOV BX, 0
  MOV DL, 0x00
  INT 0x13
-  JNC error
+  JNC fin
+  ADD SI, 1
+  CMP SI, 5
+  JAE error
+  MOV AH, 0x00
+  MOV DL, 0x00
+  INT 0x13
+  JMP retry

3. 18セクタまで読む

4. 10シリンダ分読む

CYLS EQU 10
...
next:
  MOV AX, ES
  ADD AX, 0x0020
  MOV ES, AX
  ADD CL, 1
  CMP CL, 18 ; 18セクタ読むまでloopback
  JBE readloop
  MOV CL, 1
  ADD DH, 1
  CMP DH, 2 ; ディスクの両面読むまでloopback
  JB readloop
  MOV DH, 0
  ADD CH, 1
  CMP CH, CYLS ; 10シリンダ読むまでloopback
  JB readloop

5. OS本体を書き始めてみる

  • HLTだけする
fin:
  HLT
  JMP fin

6. ブートセクタからOS実行

7. OS本体の動作確認

  • 画面モードを切り替え
ORG 0xc200

MOV AL, 0x13
MOV AH, 0x00
INT 0x10

fin:
  HLT
  JMP fin
  • Makefile編集
MAKE = make -r
DEL = rm -f

ipl.bin: ipl.nas Makefile
	nasm ipl.nas -o ipl.bin -l ipl.lst

haribote.sys : haribote.nas Makefile
	nasm haribote.nas -o haribote.sys -l haribote.lst

haribote.img : ipl.bin haribote.sys Makefile
	mformat -f 1440 -C -B ipl.bin -i haribote.img ::
	mcopy -i haribote.img haribote.sys ::

run:
	$(MAKE) haribote.img
	qemu-system-x86_64 -fda haribote.img

clean:
	-$(DEL) ipl.bin
	-$(DEL) ipl.lst
	-$(DEL) haribote.sys
	-$(DEL) haribote.lst
	-$(DEL) haribote.img

8. 23ビットモードへの準備

  • 画面モードに関する情報をメモリにメモしておく
CYLS EQU 0x0ff0
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2
SCRNX EQU 0x0ff4
SCRNY EQU 0x0ff6
VRAM EQU 0x0ff8

ORG 0xc200

MOV AL, 0x13
MOV AH, 0x00
INT 0x10
MOV BYTE [VMODE], 8
MOV WORD [SCRNX], 320
MOV WORD [SCRNY], 200
MOV DWORD [VRAM], 0x000a0000

MOV AH, 0x02
INT 0x16
MOV [LEDS], AL

fin:
  HLT
  JMP fin

9. C言語導入

10. HLTする

本だけではソースコードの変更が追えなくなってきたので、以下のリポジトリを参照し始めた
https://github.com/zacfukuda/hariboteos

  • Cコンパイラ導入
    i386-elf-gccがApple Siliconに対応しておらず苦戦。x86_64-elf-gccに変更
# arch -arm64 brew install x86_64-elf-gcc
void io_hlt(void) {
}

void HariMain(void) {
  fin:
    io_hlt();
    goto fin;
}

  • HLTする用のアセンブリソース作成
; naskfunc
; TAB=4

[BITS 32]

GLOBAL	io_hlt

[SECTION .text]

io_hlt:
    HLT
    RET
  • Makefile作成
    x86_64部分は見よう見まねで作成しているものの、32bitモードでコンパイラできているのか怪しく、以後動かなくなる可能性大。とりあえずコンパイルは通っているので良しとする
MAKE = make -r
DEL = rm -f

ipl.bin: ipl.nas Makefile
	nasm ipl.nas -o ipl.bin -l ipl.lst

asmhead.bin : asmhead.nas Makefile
	nasm asmhead.nas -o asmhead.bin -l asmhead.lst

naskfunc.o : naskfunc.nas Makefile 
	nasm -g -f elf naskfunc.nas -o naskfunc.o -l naskfunc.lst

bootpack.hrb : bootpack.c hrb.ld naskfunc.o Makefile
	x86_64-elf-gcc -T hrb.ld bootpack.c -o bootpack.hrb -ffreestanding -nostdlib -lgcc

haribote.sys : asmhead.bin bootpack.hrb Makefile
	cat asmhead.bin bootpack.hrb > haribote.sys

haribote.img : ipl.bin haribote.sys Makefile
	mformat -f 1440 -C -B ipl.bin -i haribote.img ::
	mcopy -i haribote.img haribote.sys ::

run:
	$(MAKE) haribote.img
	qemu-system-x86_64 -fda haribote.img

clean : 
	-$(DEL) *.bin
	-$(DEL) *.lst
	-$(DEL) *.o
	-$(DEL) *.sys
	-$(DEL) *.hrb
  • 実行
    真っ黒な画面が表示。正常動作らしい
# make run

Ch.4 C言語と画面表示の練習

1. C言語からメモリに書き込み

_write_mem8:
		MOV		ECX,[ESP+4]
		MOV		AL,[ESP+8]
		MOV		[ECX],AL
		RET
  • bootpack.cの変更
void io_hlt(void);

void write_mem8(int addr, int data);

void HariMain(void) {
  int i;

  for (i = 0xa0000; i <= 0xaffff; i++) {
    write_mem8(i, 15);
  }
  
  for (;;) {
    io_hlt();
  }
}

Ch.3 ではコンパイルが通るように

void io_hlt(void) {
}

としていたが、これではアセンブラ側が呼び出されず、単に空の関数が評価されるだけなので、コンパイルは通るけれども何も起こっていなかったらしい。

  • Makefileの変更
    bootpack.cから様々処理を加えてbootpack.hrbを生成するように。先駆者たちが残してくれた資産に感謝…
default :
	$(MAKE) img

ipl10.bin : ipl10.nas Makefile
	$(NASK) ipl10.nas ipl10.bin ipl10.lst

asmhead.bin : asmhead.nas Makefile
	$(NASK) asmhead.nas asmhead.bin asmhead.lst

bootpack.gas : bootpack.c Makefile
	$(CC1) -o bootpack.gas bootpack.c

bootpack.nas : bootpack.gas Makefile
	$(GAS2NASK) bootpack.gas bootpack.nas

bootpack.obj : bootpack.nas Makefile
	$(NASK) bootpack.nas bootpack.obj bootpack.lst

naskfunc.obj : naskfunc.nas Makefile
	$(NASK) naskfunc.nas naskfunc.obj naskfunc.lst

bootpack.bim : bootpack.obj naskfunc.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \
		bootpack.obj naskfunc.obj

bootpack.hrb : bootpack.bim Makefile
	$(BIM2HRB) bootpack.bim bootpack.hrb 0

haribote.sys : asmhead.bin bootpack.hrb Makefile
	cat asmhead.bin bootpack.hrb > haribote.sys

haribote.img : ipl10.bin haribote.sys Makefile
	$(EDIMG)   imgin:../z_tools/fdimg0at.tek \
		wbinimg src:ipl10.bin len:512 from:0 to:0 \
		copy from:haribote.sys to:@: \
		imgout:haribote.img
  • 実行
    画面が真っ白になったため成功。
# make run

4-1

2. しましま

  • bootpack.c
    AND演算で0〜15の32ビットデータを生成。
-  write_mem8(i, 15);
+  write_mem8(i, i & 0x0f);
  • 実行
    画面がしましまになったため成功。
# make run

4-2

3. ポインタ

4. ポインタ応用(1)

5. ポインタ応用(2)

  • ポインタで書き換える
    ポインタではなく番地変数なのだ、という説明わかりやすかった
- write_mem8(i, i & 0x0f);
+ char *p;
+ p = (char *) i;
+ *p = i & 0x0f;
  • 以下のようにも書ける
p = (char *) i;
*p = i & 0x0f;

*(p + i) = i & 0x0f;

p[i] = i & 0x0f;

6. 色番号設定

  • bootpack.c 編集
  • naskfunc.nas 編集
    組み込みシステム作ってた学生時代を思い出す(割り込みフラグON/OFFのくだりとか)。処理の流れは
  1. 割り込みフラグ値を取得
  2. 割り込みフラグを0に設定
  3. パレットへのアクセス(色番号に情報を設定)
  4. 割り込みフラグを1.で取得した値に戻す
    という流れ。
  • 実行
    画面が以前と違う色のしましまになったため成功。
# make run

4-3

7. 四角形を描く

  • bootpack.c 編集
    boxfill8関数を用いて、100px四方の正方形を3つ表示させる

  • 実行
    三つの正方形が出たので成功。

# make run

4-4

8. 仕上げ

  • bootpack.c編集

  • 実行
    なんかそれっぽい見た目のものができた

# make run

4-5

Ch.5 構造体と文字表示とGDT/IDT初期化

1. 起動情報の受け取り

  • bootpack.c 編集
    asmhead.nasでメモした値を読み込むように変更

  • 実行
    以前と同じ画面が表示される

2. 構造体

  • bootpack.c 編集
    構造体を使ってリファクタリング

  • 実行
    以前と同じ画面が表示される

3. 矢印表記

  • bootpack.c 編集
    矢印表記を使ってリファクタリング

  • 実行
    以前と同じ画面が表示される

4. 文字を出す

  • bootpack.c 編集
    8x16サイズのバイナリデータを定義して、画面上に表示させる

  • 実行
    Aの文字が表示される
    5-1

5. フォントを増やす

6. 文字列を書く

  • bootpack.c 編集
    文字列を入力として描画できるようリファクタリング

  • 実行
    文字列が表示される。ずらして表示させると影っぽくなる
    5-3

7. 変数の値の表示

  • bootpack.c 編集
    sprintfでメモリに値を書き込み、以前作成した関数で描画

  • 実行
    スクリーン横幅が表示される
    5-4

8. マウスカーソルの表示

  • bootpack.c 編集
    マウスカーソル描画用のデータを用意し、指定の座標へ描画する

  • 実行
    マウスカーソルが表示される
    5-5

9. GDTとIDTの初期化

  • bootpack.c. 編集
    セグメンテーションを設定するGDTと、割り込みを制御するIDTの設定

  • 実行
    コンパイル成功。以前の動作と変化がないことを確認。

Ch.6 分割コンパイルと割り込み処理

1. ソースファイル分割

2. Makefile整理

3. ヘッダファイル整理

-../z_tools/haribote/harilibc.lib;
-../z_tools/haribote/golibc.lib;
+/path/to/the/file/z_tools/haribote/harilibc.lib;
+/path/to/the/file/z_tools/haribote/golibc.lib;
  • 実行
    コンパイル成功。以前の動作と変化がないことを確認。

4. 説明

5. PIC初期化

6. 割り込みハンドラ作成

このあたりの説明は正直理解があまりできていない。が、とりあえず手を動かして理解を試みる

  • PICの初期化処理を実装。IRQの割り込み信号をどのメモリで受け取るかなどを設定した上で、割り込み許可を外す
  • bootpack.c を編集し、初期化、割り込みフラグON、マウスとキーボードからの割り込みのみ受け付けるよう変更、の各処理を行う。
  • 実行
    キーボードを押すと割り込み処理が発生し、メッセージが表示される。マウスはまだ無反応。
    6-1

Ch.7 FIFOとマウス制御

1. キーコードを取得

  • io_out8(PIC0_OCW2, 0x61) により、IRQ1が発生したことをPICに通知。これがないと、次回以降の割り込み監視が行われない
  • 実行。キーボード押し込み、離しのそれぞれで割り込みが検知されている
    7-1

2. 割り込み処理の高速化

  • 割り込み処理の中で文字表示を行なっているため、このままでは遅延が発生する
  • 割り込み処理では変数に保存だけしておいて、すぐに割り込みを終了・監視開始し、その後の処理で文字表示を行うよう変更
  • 実行。以前と動作が変わらないことを確認

3. FIFOバッファを作る

  • 記憶領域を32個に増やした。FIFO型で作成。
  • 実行。以前と動作が変わらないことを確認

4. FIFOバッファの改良

  • バッファのずらしが不具合の原因になるため、配列指定を変化させることを考える
  • 実行。以前と動作が変わらないことを確認

5. FIFOバッファの整理

  • より汎用的なFIFOバッファを作成。unsigned char型でbufを定義し、overflowした場合はフラグをtrueに
  • 実行。以前と動作が変わらないことを確認

6. マウス接続

  • 過去のOSはマウスに対応していなかったために、有効化命令を発行しないといけない
  • マウス制御回路はキーボード制御回路に入っているので、キーボード制御回路をうまく初期化すれば良い
  1. キーボード制御回路がデータ送信可能になるまで待つ(CPUほどキーボード制御回路が速くないため、命令についていけなくなる可能性があるため)
  2. キーボード制御回路がデータ送信可能かを確認しながら、モード設定を行う(書き込みモードへ変更->マウス利用のためのモードへ変更)
  3. キーボード制御回路がデータ送信可能かを確認しながら、マウスに有効化命令を送る(マウスへデータ転送を行うモードに変更->有効化命令を送信)
  • 実行。マウスから割り込みがくるように
    7-2

7. マウスからのデータ受信

  • キーボードと同じ要領で、マウスからの割り込み受付ロジックを記述
  • 実行。マウスからのデータが画面上に表示されるようになった(左:キーボード、右:マウス)
    7-3

Ch.8 マウス制御と32ビットモード切り替え

1. マウスの解読(1)

  • マウスのbuf[1]が移動とクリック、buf[2]が左右の動き、buf[3]が上下の動きに対応している

2. 整理

  • 構造体を使ってリファクタリング

3. マウスの解読(2)

  • マウスの左/右クリック、左右、上下の状態を表示できるように
  • 実行。マウスの状態に合わせて文字列が変化する
    8-1

4. マウスを動くように

  • 割り込みが入るごとに、マウス・座標を消し、新たな値でマウス・座標を描画する
    8-2

  • 現在はタスクバー部分を保護していないので、タスクバー部分に触れると消してしまう
    8-3

5. 32ビットモード

  • asmhead.nasの内容について深掘り
  • アセンブラの細かい動作についてはよくわかっていないが、memcpyの動作についてはわかってきた
    (事前準備: ESI, EDIにそれぞれ転送元と転送先の開始番地をコピー、ECXに転送回数を設定)
  1. EAXにESI番地のメモリの値をコピー(AX: accumulator, SI: source index)
  2. ESIの座標を4ずらす(32ビット分)
  3. EDI番地のメモリの値にEAXをコピー(DI: destination index)
  4. EDIの座標を4ずらす
  5. ECXから1引く(CX: counter)
  6. ECXが0になるまで1-5を繰り返す
  • その他の決まりごとについては、必要になったら仕様書見て理解すれば良いと思う

Ch.9 メモリ管理

1. ソースの整理

  • マウスとキーボードに関する記述を分離
  • 実行して動作が変わらないことを確認

2. メモリ容量チェック(2)

  • メモリ容量チェックをしたいが、間にキャッシュが入るとキャッシュ側が反応してしまうので、一時的にキャッシュをOFFにする
  • メモリに値を書き込み、反転を繰り返し、想定通りの結果が得られたか否かで判定を行う
  • 想定される結果は32MBだが、実際にやってみると3072MBと表示されてしまう
    9-1

3. メモリ容量チェック(3)

  • Cコンパイラの最適化により、XORロジック等が省略されてしまう
  • メモリテスト部分をアセンブラで書き直す
  • 実行。書籍では32MBとなっているが、現在のQEMUのメモリはデフォルト128MBらしく、その値が出力されている
    9-2

4. メモリ管理

  • 4KBごとにセグメントを分けて、セグメントに対応する1バイト/1ビットの領域を確保して、フラグ管理するやり方。一番いい方法ではないが、Windowsのフロッピーディスクはこれに近い
  • x番地からyバイトが空いている、という情報を表にして並べる方法。メモリの節約になる。読み書きも最小限で済むため速い。ただしメモリ管理が複雑になる
  • 2番目の方法を選択。メモリの容量の取得、メモリの確保・解放ロジックを記述
  • 未使用のメモリ領域を登録した後、算出。0x00001000-0x0009efffまでの632KBと、0x00400000から最大領域までの124MBで、計127,608KBであれば良い
  • 実行。正しい値が出力されている
    9-3

Ch.10 重ね合わせ処理

1. メモリ管理の続き

  • リファクタリング。ついでに1KB単位で解放・確保ができる関数を作る

2. 重ね合わせ処理

  • ウインドウやマウスカーソルなど、保護したい要素の下に「下敷き」があると考え、要素を動かすときは下敷きを移動する。これにより、ウインドウやマウスカーソルを動かしても他の要素が消えない
  • sheet_allocはsheetオブジェクトの確保、sheet_setbufはsheetオブジェクトの値設定、sheet_updownは、各sheetのzIndexを設定するイメージ、sheet_refreshは、zIndexの小さいものから順に描画する、sheet_slideはzIndexの変更なくsheetをスライドさせる、sheet_freeはsheetオブジェクトを解放する
  • 各関数を用いて背景とマウスを描画
  • 実行。タスクバーが消えなくなった
    10-1

3. 高速化(1)

  • マウスを動かすとちらつきが発生する。これを直すことを考える
  • 現在はマウスを少し動かすと全画面(320x240)をrefreshしている。移動に関する部分だけ書き換えるとしたら、移動元と移動先だけなので16x16x2の512画素だけで済む。これに従って改良
  • 高速化に成功。でもまだ少しチラつく

4. 高速化(2)

  • refreshsubが遅い。refresh範囲のみfor文で回して、不要なif分を削る
  • 高速化に成功。以前と同じ描画だが、快適に動作するように

Ch.11 ついにウィンドウ

1. もっとマウス

  • Windowsのように、マウスカーソルが画面外に隠れるまで右や下にいけるようにしたい
  • 現状だと、単に設定変えると誤動作する
    11-1

2. 画面外サポート

  • 画面外にはみ出たときに、画面外を書き換えないよう変更
  • 実行。想定通りに動作する
    11-2

3. shtctlの指定省略

  • 構造体を編集して、shtctlを引数で渡さなくて良いようにする
  • 実行。以前と動作が変わらないことを確認

4. ウィンドウを出す

  • 背景やマウスと同様、ウィンドウを記述
  • 実行。新たにウィンドウが表示される
    11-3

5. 少し遊んでみる

  • マウスとウィンドウのzIndexを逆転させる
  • 実行。マウスがウィンドウの裏にもぐるようになる
    11-4

6. 高速カウンタ

  • for文でカウンタをぶん回し、画面に描画する
  • 実行すると、チラつきながら画面描画される
    11-5

7. チラチラ解消(1)

  • refresh対象とそれより上のレイヤーのみ描き直すよう変更する
  • 実行すると、だいぶチラつきは抑えられたが、マウスをカウンタの上に乗せるとチラつく
    11-6

8. チラチラ対策(2)

  • zIndexの値を使って角画素ごとにzIndexの値を当てはめたmapを作り、それに応じてrefreshsubを実行するように変更
  • 実行すると、マウスをカウンタ上に置いてもちらつきがなくなった
    11-7

Ch.12 タイマ-1

1. タイマを使う

  • PIT(Programmable Interval Timer)はIRQ0番につながっているので、PITを設定するとIRQ0の割り込み間隔を設定できる
  • 指定のOUT命令を実行して1秒間に100回割り込みが発生するよう設定
  • キーボード割り込みと同様に、割り込みハンドラを設定。IDTに登録
  • 実行。以前と動作が変わらないことを確認

2. 時間を測る

  • 1回の割り込みごとにcounterがincrementされるように変更
  • 実行。以前と異なり1秒ごとに100だけカウントされることを確認

3. タイムアウト機能

  • タイムアウトした時にFIFOバッファにデータを送信。キーボードやマウスと同様の方法で処理実行できるように
  • 実行。10秒経ったら10[sec]が表示されるように
    12-1

4. 複数のタイマ

  • 複数のタイマを設定できるよう、構造体を編集。alloc, free, init等の処理を実装
  • 実行。3秒, 10秒経ったら表示される文字列と、点滅するカーソルが実装される
    12-2

5. 割り込み処理は短く(1)

  • 1回の割り込みごとにtimerのcountをdecrementするのではなく、countとtimeoutの比較のみでtimeoutを判定するように変更
  • 結果的に定期的にcountをリセットする必要が出るが、1年以上は必要ないので今回は無視する

6. 割り込み処理は短く(2)

  • 全てのタイマに同じ処理をさせるのではなく、timeoutまでの時間が最も短いタイマのみ処理を行うよう変更する

7. 割り込み処理は短く(3)

  • タイムアウト時に、for文のネストが発生しないようリファクタリング。sheetsのときのように、タイマのポインタを並べたものを作成し、全件検索しなくて良いようにした
  • タイマの登録時に、一時的に割り込み禁止にしているが、これは仕方ないとする
  • 実行。以前と動作が変わらないことを確認

Ch.13 タイマ-2

1. 文字列表示を簡単に

  • 共通関数化してリファクタリング

2. FIFOバッファを見直す(1)

  • タイマ用のFIFOバッファを1つにまとめる
  • このあたりから動作がおかしくなっている。ネット上にあるソースを様々試してみたがダメなので、不具合っぽい

3. 性能を測定してみる

  • counterを回して性能を測定

4. FIFOバッファを見直す(2)

  • マウス、キーボード、タイマのFIFOバッファを共通化

5. 割り込み処理は短く(4)

  • タイマ処理にnext変数を持たせ、nextのチェーンでタイマの前後関係を定義する(ブロックチェーンと同じ原理)

6. 番兵を使ってプログラムを短くする

  • タイマチェーンの一番後ろに、呼ばれない仮のタイマ(番兵)を用意して、プログラムを単純化する

Ch.14 高解像度・キー入力

1. 性能測定

  • タイマを大量に追加して性能測定
  • Cコンパイラの影響でJMP命令のアドレスがずれることにより、若干値に影響が出る

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

  • asmhead.nas の画面モード指定を変更し、解像度を上げる
  • QEMUで実行すると黒画面になったが、これまで同様一旦放置

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

  • VBI(VESA BIOS extension)の情報を取得して、対応バージョンであれば高解像度にするよう変更
  • QEMUで実行すると以前の画面が出力されたが、相変わらず動きが遅い。一旦放置

4. キー入力(1)

  • キーマップを参照しつつ、Aのキー入力を行ったときにAを表示できるように変更
  • QEMUで実行すると操作が軽くなり、正常動作する。画面書き込みのどこかで不具合?
    14-1

5. キー入力(2)

  • キーマップに対応するテーブルを用意し、英数字を入力できるように変更
    14-2

6. おまけ(1)

  • 文字を連続して入力したときに、文字データとカーソルをずらして表示するようにし、文字列を表現できるように
  • Backspaceを入力した時に、文字データの背景を塗りつぶしつつ、カーソルの位置を前にずらすことで文字の削除を表現できるように
    14-3

7. おまけ(2)

  • 左ボタンを押したらウインドウ位置をマウス位置に合わせて移動させられるように
    14-4

Ch.15 マルチタスク-1

1. タスクスイッチ

  • タスクスイッチにかかる時間は、後に実行再開できるようにするために、メモリへI/Oする時間
  • task status segment(TSS)はintが26個集まったもの
struct TSS32 {
	int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
	int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
	int es, cs, ss, ds, fs, gs;
	int ldtr, iomap;
};
  • 1, 4行目はタスク設定、2行目は32ビットレジスタ、3行目は16ビットレジスタ。EIPは次に実行する命令がメモリのどの番地にあるかをCPUが記憶しておくためのレジスタ。JMP命令に使われる
  • タスクスイッチさせるにはJMP命令を使う。以下のような場合、CSに16を、EIPに0x1bが入る。この時、指定したセグメントがTSSだった場合は、タスクスイッチ命令だと判断する
JMP DWORD 2*8:0x0000001b
  • 10秒後にタスクスイッチした後、HLTしかしなくなる(フリーズする)プログラムを記述
  • 実行。10[sec]の文字が表示された瞬間にすべての割り込みが動作しなくなる
    15-1

2. もっとタスクスイッチ

  • 切り替えてからさらに5秒後に、元のタスクに戻すよう記述
  • 実行。10[sec]の文字が表示されたさらに5秒後、割り込みが動作するように

3. マルチタスク(1)

  • JMP FARと記述すると、far-JMPさせることができる。以下のように記述すると、[ESP+4]にはEIPの値が、[ESP+8]にはCSの値が入る
JMP FAR [ESP+4]
  • timer_settime関数で、0.02秒ごとにタスクスイッチするよう設定する
  • 実行。綺麗に動作しているように見える

4. マルチタスク(2)

  • タスクBでカウンタをひたすら表示させる処理を入れ、タスクA, Bが同時に動いていることを確認する
  • 実行。実際にタスクBが動作していることを確認
    15-2

5. スピードアップ

  • 0.01秒に1回カウンタ表示させるよう変更
  • sht_backをメモリ上で[ESP+4]に当たる場所にC言語上で設定するようリファクタリング
  • 実行。Ch.13と同様に動作が遅くなる不具合発生。やはり文字表示周りになんらか問題があるのか

6. スピード測定

  • 以前と同様に、カウンタを回して1秒間の処理回数を確認(動作が遅いため検証できず)

7. もっとマルチタスク

  • 割り込み処理のところでタスクスイッチするよう(それぞれのタスクでタスクスイッチ処理を記述しないよう)変更
  • 各プログラムに不具合があった場合でも、タスクが必ず切り替わるという点で利点がある。タスクスイッチの際にFIFOバッファを使わないため、高速化にもなる

Ch.16 マルチタスク-2

1. タスク管理の自動化

  • task管理領域を設定し、allocしておく。タスクが2つ以上設定されたら、0.2秒間隔で各タスクにスイッチする
  • 実行。問題なく動作する
    16-1

2. スリープ

  • HLTさせる代わりに、タスクをスリープさせることを考える。taskcrl->tasks[]から除去する
  • FIFOにデータが入ったら起こす(スリープ解除する)
  • 実行。動きが遅いが既知の問題のためスルーする

3. ウインドウを増やす

  • カウントするだけのタスクを増やす形で、ウインドウを増やした
  • 実行。ウインドウが描画されない不具合があるがスルー

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

  • priority * 0.1秒ごとにタスクスイッチするようにする。priorityはタスク実行時に指定する

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

  • タスクA(キーボード、マウスの割り込み)が遅いので、タスクAの優先順位を最大の10にする。タスクAはスリープするので、優先順位を上げても問題ない。しかしこの場合、複数のタスクの優先順位が高いと、待ちが大きくなる
  • レベル0-2を考え、優先度の高いものを0、中くらいのものを1、低いものを2に設定することにする。レベルが0がすべてスリープしたり、下のレベルに降りた場合はレベル1にスイッチ、という感じにする
  • 実行。相変わらず動作が遅いが表示はされた
    16-2

Ch.17 コンソール

1. アイドルタスク

  • タスクA, B0-B2があり、AがスリープしたあとはB0-B2のタスクを探しにいくというつくりだった
  • B0-B2を使わないとしたら、スリープ後に不具合が起きる。そのためHLTをしたいが、汎用的な方法を探りたい
  • 番兵的な考え方で、HLTし続けるタスクを一番下のレベルに置いておく
  • 実行。以前と同じ画面が表示される。動作はそこそこ安定している

2. コンソールを作る

  • コンソールっぽい見た目のウィンドウを表示する
  • 実行。表示された
    17-1

3. 入力切り替え

  • Tabキーを押したらウィンドウのタイトル部の色が変わるようにする
  • 実行。Tabキーを押すと確かにコンソールのヘッダ部がハイライトされる
    17-2

4. 文字入力

  • task構造体の中にfifoを入れるようリファクタリング
  • フラグの値に応じて、コンソールと別に表示されているウィンドウのどちらかにFIFOにデータを送る
  • 実行。今回は文字入力も切り替わる
    17-3

5. 記号入力

  • shiftキーが押下されているか否かによって、keytableを使い分けることで記号を入力できるようにする
  • 実行。エクスクラメーションマーク(!)などが表示できるように
    17-4

6. 大文字と小文字

  • CapsLockとshiftキーの状態を対応させることで、大文字と小文字を使い分けられるように
  • CapsLockの値はasmhead.nasで取得済み
  • 実行。大文字と小文字が分けられるようになった
  • コラムに左シフト+右シフト+Aが入力できないという話があったが、今のMacBookではできた
    17-5

7. Lockキー対応

  • Lockキーが押下されるごとに、binfo->ledsを書き換えることで、キーボードランプを表示させるようにする
  • ただし今回はエミュレータのみでの実行なので、確認はせず

Ch.18 dirコマンド

1. カーソル点滅制御(1)

2. カーソル点滅制御(2)

  • Tabキーを押下するごとに変数を書き換え、カーソルの点滅を制御する
  • 実行。片方のウィンドウだけカーソルが点滅するようになった
    18-1

3. Enterキーに対応

  • コンソールでEnterキーを押したときに、改行できるようにする
  • Enterキーを押したときにカーソルのy座標を一行分ずらすことで実現する
  • 実行。確かに改行される
    18-2

4. スクロールに対応

  • 一番下の行まで行ったときにスクロールされるよう修正する
  • 画素をy軸方向に1行分ずらすことで実現する
  • 実行。スクロールされることを確認
    18-3

5. memコマンド

  • コマンドライン上に"mem"コマンドが入力されたらメモリ情報を表示するように変更(実装上、空白などが入るとうまく動作しない)
  • 実行結果の表示についても、大筋はこれまで通り(書き換え、ずらし、スクロール等)
  • 実行。memコマンドが正しく実行される
    18-4

6. clsコマンド

  • mac/linuxで言うところのclearコマンド。コンソールの全画面を黒く塗りつぶす
  • strcmpを入れて文字列比較部分のリファクタリングも行う
  • 実行。確かに全消去される
    18-5

7. dirコマンド

  • ファイル名を一覧表示するコマンド。ファイルの情報は32バイトで保存され、以下の形式
struct FILEINFO {
  unsigned char name[8], ext[3], type;
  char reserve[10];
  unsigned short time, date, clustno;
  unsigned int size;
};
  • typeは読み込み専用、隠しファイルなどを表す。clustnoはセクタと同じ意味
  • 以上の情報からファイル名を一括表示するよう編集
  • 実行。ファイル名が表示された
    18-6

Ch.19 アプリケーション

1. typeコマンド

  • mac/linuxで言うところのcat
  • clustnoが1違うとファイルの位置が1セクタずれる。1セクタ512バイトなので、逆算してファイルの番地を算出する
  • アルゴリズムは ファイルを見つける->ファイル情報から番地を算出する->1文字ずつ描画する
  • 実行。ファイルの内容が表示された
    19-1

2. typeコマンド改良

  • 改行やタブの処理を実装する
  • 0x0a(carriage return)だけで改行するようにし、タブは4文字区切りとする
  • 実行。改行とタブが表示された
    19-2

3. FATに対応

  • 512バイトよりも大きいファイルの場合、次のセクタではないところへ続きを書き込むことがある
  • シリンダ0、ヘッド0、セクタ2-9にあるFAT(file allocation table)を利用して、正しくファイルを読み取れるようにする
  • FATは圧縮されており、復元すると次のセクタ情報のチェーンが現れる。clustno=2が読み終わった後、FATの2番を見て、次のセクタを確認する
  • FATは2つあり、2つ目はバックアップ(FATが壊れると致命的なため)

4. ソースの整理

  • リファクタリング

5. アプリケーション

  • 以下の流れで実装
    (1) アプリケーションファイル(アセンブラ言語)を作る
    (2) アセンブルしてapp.hrb(実行可能データ)を作る
    (3) メモリ領域確保する
    (4) typeコマンドと同様にしてファイルの内容を読み込む
    (5) セグメント登録する
    (6) セグメントの最初の番地(ORG 0)にfarjmpする(ファイル実行)
    (7) メモリ領域を解放する
  • フリーズするだけのアプリを作成し、実行。確かにフリーズする
    19-3

Ch.20 API

1. プログラムの整理

  • リファクタリング

2. 一文字表示API(1)

  • アプリからOSの機能を呼び出して利用する。今回はcons_purcharを呼び出して一文字表示させる
  • 以下の流れで実装
    (1) アプリケーションからレジスタに文字コードを渡す
    (2) アプリケーションからasm_cons_purcharを呼び出す
    (3) asm_cons_purcharでconsオブジェクト、レジスタの値などをスタックに積み込む(これでC言語関数が文字コードにアクセスできる)
    (4) asm_cons_purcharからcons_putcharを呼び出す
  • 上記(3)にてconsオブジェクトの番地が必要だが、これはconsオブジェクト生成の際に明示的に設定する
  • 実行。書籍にある通り異常終了する

3. 一文字API(2)

  • 異なるセグメント上にいるため、far-CALL, far-RETにしないといけない
  • 実行。hltコマンドを打つも異常終了する。一旦先に進める

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

  • アプリでhltする代わりに(far-)RETするようにする
  • OSの方も、アプリを(far-)JMPではなく(far-)CALLするようにする
  • OSのアセンブリに書き足したので、asm_cons_purcharの番地が変更される
  • 実行。hlt関数を打つとhelloが表示されるように
    20-1

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

  • OSを書き換えた時に、関数の番地が変わらないようにする
  • IDT(Interrupt Descriptor Table)にasm_cons_purcharを登録することにする
  • IDTの0x40番に設定すると、INT 0x40でasm_cons_putcharを呼び出すことができる
  • ただし、INT命令で呼び出された場合は割り込み扱いになるため、RETFではなくIRETDとする
  • INTで呼び出すと、自動でCLIが実行されて割り込みが禁止されるが、今回はマウスやキー入力が効かなくなることは避けたいため、asm_cons_putcharの最初でCLIを行う
  • 実行。問題なく動作する
    20-2

6. アプリケーション名を自由に

  • mem, clsなどのコマンド以外の場合は、ファイル名を探して実行することにする(拡張子はあってもなくても良いことにする
  • 実行。問題なく動作する
    20-3

7. レジスタに気を付ける

  • hello.nasでループを使って、表示文字列が多くなっても対応できるようにしたいが、ループがうまく動作しない
  • cons_putcharがECXのレジスタをおかしくしてしまっている(原因は?)と考え、PUSHADとPOPADを加える
  • 実行。問題なく動作する

8. 文字列表示API

  • 文字コード0がくるまで表示する場合と、文字数を指定して表示する場合の2つの関数を作る
  • ついでにmem, dir, typeをこれでリファクタリング
  • 作った関数は1つのAPIとしてまとめ、レジスタEDXに機能番号を入れることで、どの関数を呼び出すかを決定する
  • 文字列表示を行うhello2(EDX: 2)を実行するもうまくいかず、hello(EDX: 1)はうまくいった
    20-4

Ch.21 OSを守ろう

1. 文字列表示API

  • セグメントの指定がうまくいってないために、文字列表示ができなかった
  • 一文字表示の時と同じように、メモリ番地を指定してセグメントを指定する
  • 実行。確かに文字列が表示される
    21-1

2. アプリケーションをC言語で作る

  • 以下の流れで一文字表示アプリを作る
    (1) C言語でエントリファイルを作る
    (2) レジスタに値を入れて割り込みさせるアセンブラを作る(
    (3) far-RETできるように一部バイナリを変更

3. OSを守ろう(1)

4. OSを守ろう(2)

  • OS管理用メモリに勝手にアクセスできないようにする
  • アプリ用のデータセグメントを作って、アプリが動いている間はその領域を使うようにする
  • アセンブラは流し読み程度。後の章でCPUがセグメント切り替えを自由にやってくれるよう書き換えるらしい
  • 実行。crackコマンドを打っても問題なく動作するようになった
    21-2

5. 例外のサポ~ト

  • 異常終了は割り込み番号0x0dに関数を登録すれば良い
  • 例外時にはconsoleで表示するようにする
  • 実行するも文字列表示がない。QEMUのエミュレーションの都合なので無視

6. OSを守ろう(3)

  • アプリからアセンブラを使ってOS用のセグメントを代入してしまう
  • OSのコマンドが実行できなくなる

7. OSを守ろう(4)

  • アプリからOS用のセグメントを代入できないようにする
  • セグメント定義のところで、アクセス権に0x60を足すことで、アプリ用のセグメントとして設定することができる
  • x86ではOSがアプリをCALLしてはいけないことになっているので、Stackにアプリの番地を登録しておいて、RETFすることで実行する
  • 実行。crack2を実行すると確かに例外が表示される
    21-3

Ch.22 C言語でアプリケーションを作ろう

1. OSを守ろう(5)

  • タイマの設定を変更してみる→IN/OUT命令が一般保護例外になるので問題なし
  • CLI, HLTする→CLI, STI, HLTはすべて例外になるので問題なし
  • OSのCLI関数をfar-CALLしてみる→設定された番地以外のCALLを禁止しているので問題なし
  • APIを改造してみる→OSが動かなくなる(OSが改造される、あるいはOSが自滅するようなAPIが元からない限りは問題ない)

2. バグ発見を手伝おう

  • スタック例外の割り込み番号に0x0cに対応する関数を用意し、IDTに登録
  • 例外が起きた時、esp[11] = EIPとなるので、EIPを参照することで例外が発生したアドレスがわかる
  • lstファイルから追っていくと、確かに例外が発生した地点を指していることがわかる

3. アプリの強制終了

  • 無限ループを実行したら永遠に動けないので、強制終了機能をつける
  • キーボード割り込みの中で、強制終了をするとコンソールタスクのレジスタを書き換えてアプリ終了させる
  • アプリが動いていない時に押すと誤動作するので、アプリが起動していることを確認してから処理する
  • 実行。確かに強制終了することを確認
    22-1

4. C言語で文字列表示(1)

5. C言語で文字列表示(2)

  • C言語から文字列表示APIを呼び出す関数を作るが、動かない
  • hrbがコード部とデータ部に分かれているため。データ部を読み込めるようにしないといけない
  • hrbファイルに書かれているサイズ分、データをデータセグメントにコピーしてから起動するようにする
  • 実行。文字列が表示される
    22-2

6. ウインドウを出す

  • レジスタに値を入れるのは、引き渡されたレジスタの番地を逆算して再代入することでできる
  • 実行。ウィンドウが表示される
    22-3

7. ウィンドウに文字や四角を描く

  • これまでと同様の手順で、文字や四角を表示してみる
    22-4

Ch.23 グラフィックいろいろ

1. mallocを作る

  • mallocでアプリにメモリを割り当てる。アプリを作るときに、確保するメモリ量を設定する

2. 点を描く

  • ウィンドウに点を描くAPIを作る。表示位置の(x, y)座標、ウィンドウの番号、色番号を指定してCALL
  • 30個ランダムに表示させてみる
    23-1

3. ウィンドウのリフレッシュ

  • 点を1つ描画するたびにウィンドウがリフレッシュされるのを改善
  • 新たにリフレッシュだけするAPIを作成し、最後にCALLする

4. 線を引く

  • 線の太さ分(ここでは1px)を考慮して、dx, dyを算出する
  • 16本線を引いてみる
    23-2

5. ウィンドウのクローズ

6. キー入力のAPI

  • ウィンドウの番号を指定してsheetの割り当てを解除する(クローズする)APIを作成
  • Enterを押したら終了するように作る

7. キー入力で遊ぶ

  • これまでの応用で、点を上下左右に動かせるようなアプリを作ってみる
    23-3

8. 強制終了でウィンドウを閉じる

  • 強制終了した際にウィンドウを閉じる処理を入れていないので、メモリがないのにウィンドウが画面に残ってしまう
  • アプリを終了するときに、sheetをすべて確認して、終了しようとしているアプリに紐づくsheetを解放する

Ch.24 ウィンドウ操作

1. ウィンドウの切り替え(1)

  • F11を押したら、一番下のウィンドウが一番上に来るようにする
  • F11の割り込みで、sheetの高さをマウスの下に設定する

2. ウィンドウの切り替え(2)

  • マウスのクリックでウィンドウを切り替えられるようにする
  • マウスの位置座標とsheetの範囲を見比べて、さらに色が表示されているかどうかで該当するウィンドウがどれかを判断する
    24-1

3. ウィンドウの移動

  • ウィンドウのタイトルの部分をD&Dしたら、sheetを動かすようにする
    24-2

4. ウィンドウをマウスで閉じる

  • ×ボタン領域をクリックしたら、強制終了させる

5. アプリケーションウィンドウも入力切り替え

  • Tabキーを押すと、選択中のウィンドウの一つ下に切り替えるようにする
  • ウィンドウを閉じると、一番上のウィンドウに切り替わるようにする
    24-3

6. 入力ウィンドウをマウスで切り替える

  • マウスでウィンドウをクリックすると、そのウィンドウが一番上に上がってくるようにする

7. タイマAPI

  • タイマ(ストップウォッチ)を作る。alloc->init->set->freeの流れ
  • 1秒ごとに描画し直す。1秒ごとに128のデータが割り込まれるので、それに応じて再描画
  • 128以外のデータが来た場合は、他のキーを押したと判断してアプリを終了する
    24-4

8. タイマのキャンセル

  • 7.時点では1秒タイマがキャンセルされていないので、コンソールに128のデータが送られてしまう
  • 指定したタイマを削除し、かつnextチェーンをつなぎ直す
  • アプリが終了したときに、使っていたタイマをすべてキャンセルするようにする

Ch.25 コンソールを増やそう

1. BEEPサウンド

  • BEEPサウンドはPITで制御可能。ON/OFFは0x61で制御
  • QEMUなのでエミュレートは断念

2. 色を増やそう(1)

  • RGBそれぞれ6階調を設定し、6^3色を使えるようにする
    25-1

3. 色を増やそう(2)

  • 濃点と淡点のバランスを変えて、中間色っぽく見せられるようにする
    25-2

4. ウィンドウの初期位置

  • 画面の中央にウィンドウが出るようにする
    25-3

5. コンソールを増やそう(1)

  • まずは単純にコンソールタスクを2つ作ってみることにする
  • コンソールAの実行結果がコンソールBに表示されてしまう
    25-4

6. コンソールを増やそう(2)

  • データセグメントをAとBで共有していたことが問題。そのためそれぞれ定義する
    25-5
  • しかし、アプリを閉じると異常終了する
    25-6

7. コンソールを増やそう(3)

  • アプリ用コード/データセグメントが被っているため、両方起動するとデータが上書きされてしまう
  • 各アプリでセグメント番号が被らないように設定し直す
  • 異常終了しなくなった
    25-7

8. コンソールを増やそう(4)

  • 強制終了や×ボタンで指定のウィンドウが消せるようにする
    25-8

9. もっとOSらしく(1)

10. もっとOSらしく(2)

  • コンソール以外に表示させていた画面を消したいが、普通に消すとうまくいかない
  • コンソールタスクの優先度が低いにもかかわらず、FIFOバッファの初期化がコンソールタスク内で行われているため、FIFOバッファが初期化されない
  • 実行。無事うまく起動できるように
    25-9

Ch.26 ウィンドウ移動の高速化

1. ウィンドウ移動を速く(1)

  • sheet_refreshmapをリファクタリングする。マウスは不透明部分があるが、ウィンドウは四角形で不透明部分がないため、不透明部分に関するif文をなくす

2. ウィンドウ移動を速く(2)

  • 32bitレジスタを利用して、同時に4バイト書き込ませることを考える
  • ウィンドウの大きさ、座標も4の倍数になるよう調整する

3. ウィンドウ移動を速く(3)

  • sheet_refreshsubも同様に、4バイト書き込ませるようにリファクタリング

4. ウィンドウ移動を速く(4)

  • マウスから移動のデータを受け取ってもすぐには描画せず、FIFOが空になったら描画するように変更

5. 最初のコンソールを1つに

  • 最初に起動するコンソールを1つにし、Shift+F2を押すとコンソールが出るようにする
  • コンソールを開く関数を外出しし、コンソール開閉をフラグで持たせる
    26-1

6. コンソールをもっとたくさん

  • コンソール数の制限をとりあえず無くしてみる
    26-2

7. コンソールを閉じる(1)

  • exitコマンドでコンソールを閉じるようにしてみる
  • メモリ、描画、構造体をfreeする。コンソール用のStackのアドレスを記録するように修正
  • コンソールがすべて無くなった時は、文字入力やウィンドウ削除に対して何もしないようにする
    26-3

8. コンソールを閉じる(2)

  • マウスで閉じられるようにする。×が押されたら特定のデータを送ってexitコマンドを実行する

9. startコマンド

  • "start hoge"で、新しいコンソールで"hoge"コマンドを実行できるようにする
    26-4

10. ncstコマンド

  • 新しいコンソールを出さずにアプリを動かす(古いコンソールは引き続きコマンド実行できる)ようにする
  • コンソールへの文字列表示などはすべて無視
  • 実行すると、アプリが閉じられない。対応は次回
    26-5

Ch.27 LDTとライブラリ

1. まずはバグを直そう

  • F1やxボタン押下でタスクを起こすようにする
  • 今までこの命令がなくてもうまく動いていたのは、カーソル点滅のための割り込みがあったため

2. アプリ実行中でもコンソールを閉じたい

  • ×ボタンを押したらひとまずコンソールを非表示にし、FIFOにデータが届いた時点でメモリを解放する
  • コンソールだけ閉じられるようになった
    27-1

3. アプリケーションを守ろう(1)

  • 動作中のアプリを壊すことを試みる
  • consoleのセグメントからアプリ用データセグメント長を取得して、該当するデータセグメントを全部適当なデータで埋め尽くす
    27-2

4. アプリケーションを守ろう(2)

  • アプリ用のセグメントをLDT(Local (segment) Descriptor Table)で設定することで、他のタスクから攻撃されることを防ぐ
    27-3

5. アプリケーションのサイズ改善

  • 関数を分割することでサイズを小さくする。使わないobjファイルを書いても、リンカが使うものだけリンクしてくれるので問題なし

6. ライブラリ

  • たくさんのobjファイルをまとめてlibファイルにする

7. make環境の整理

  • フォルダを分けてそれぞれにMakefileを持たせる
  • これにより、アプリを追加しても大元のMakefileは変更されないため、makeが高速になる

Ch.28 ファイルと日本語表示

1. alloca(1)

  • 10,000までの素数を表示するアプリを作る
  • スタックに4KB以上の変数を確保しようとすると、__allocaという関数を呼び出そうとし、失敗する
  • 今回はapi_mallocでスタック領域を確保してごまかす
    28-1

2. alloca(2)

  • allocaでスタックにEAXバイトの領域を確保する(ESPを書き換えるのでJMP先に注意する)

3. ファイルAPI

  • open, seek, read, write, closeの5つのAPIを作成する
  • ipl10.nasをcatしてみる
    28-2

4. コマンドラインAPI

  • cat(type)を実装する
    28-3

5. 日本語表示(1)

  • OSASKで日本語ファイルを用意
  • langmodeを設定することで、日本語と英語を切り替えられるようにする
    28-4

6. 日本語表示(2)

  • (Shift-JIS)2バイトから区点コードを取得し、フォントの番地を計算
  • 全角文字の1バイト目を表示したところで自動改行しないよう、空白を描画する
  • まだ漢字が表示されない
    28-5

7. 日本語表示(3)

  • 読み込み範囲を広げると、漢字が表示される
    28-6
  • EUC-JPに対応。langmode=2でEUC-JPに対応するようにする
    28-7
  • langmodeを取得できるAPIを作成
  • 書籍ではバグが出ることになっているが、問題なく実行できた
    28-8

Ch.29 圧縮と簡単なアプリケーション

1. バグ修正

  • refresh関数にバグあり(Ch.28時点で問題なく実行できたのはなぜ?)

2. ファイル圧縮

  • 日本語フォントファイルを小さくすることが目的
  • 圧縮アルゴリズムについては筆者作のものを流用。142KB->56.6KBに

3. 標準関数

  • putchar, exit, printfなどのC言語の標準関数を作ってみる。内容はこれまでとほぼ同じ

4. 非矩形ウィンドウ

  • 透明色を使用して、四角でないウィンドウを作ってみる
    29-1

5. bball

  • たくさん線を引いて、幾何学模様を作ってみる
    29-2

6. インベーダーゲーム

  • インベーダーゲームを作ってみる
    29-3

Ch.30 高度なアプリケーション

以下をC言語で作成

  • コマンドライン計算機
  • テキストビューア
  • MML(Music Macro Language)プレイヤ
  • 画像ビューア
    30-1
    30-2
    30-3
    30-4

IPLを改良して、起動時のシリンダ読み込みを高速化。読み込みシリンダ数をギリギリまで減らし、複数のセクタをまとめて読み込む

このスクラップは2022/02/16にクローズされました
作成者以外のコメントは許可されていません