30日OS自作本17日目
17日目の内容です。
ソースの動作具合
13章目以降初めて全てのソースが動作しました。16日目は全滅だったので非常に嬉しいです。ただしGUIは若干もたついており遅延があるような気がします。タイミングがシビアなアクションゲームなどはできませんが、普通に使っている分には問題ない感じです。
ウインドウを作る
- 1.枠を作る
- 2.別のタスクとして動きを作る(テキストの入力カーソルの点滅を別タスクに)
- 3.タブキーで切り替えできるようにする
- 4.文字入力できるようにする(FIFOを全てのタスクから参照できるようにする。)
- 5.記号入力できるようにする
- 6.小文字入力できるようにする。
こんな感じでwindowを作っていきます
キー配列
hariboteOSではOSがキーボードコントローラから読み取ったキーコードと文字のペアをOS内にハードコードで保持しています。
static char keytable0[0x80] = { 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0 }; static char keytable1[0x80] = { 0, 0, '!', 0x22, '#', '$', '%', '&', 0x27, '(', ')', '‾', '=', '‾', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '`', '{', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '+', '*', 0, 0, '}', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '_', 0, 0, 0, 0, 0, 0, 0, 0, 0, '|', 0, 0 };
これはおそらくwindowsのJIS配列のものなので、US配列を使っているとキーコードに対応する文字が微妙に違っています。
うーんこれは悩んだ人もいると思うのですが?ググっても同様の問題を抱えている人が見つかりません。 一応見える範囲で直して見ました。
static char keytable0[0x80] = { 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', 0x27, '`', 0, 0x5c, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0 }; static char keytable1[0x80] = { 0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', 0x22, 0x7e, 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '_', 0, 0, 0, 0, 0, 0, 0, 0, 0, '|', 0, 0 };
~
の文字コード(0x7e)は調べました。(jisの人たちはキーボードから入力できないんですね〜)
$ echo \~ |od -h 0000000 0a7e 0000002
矢印キー入力で文字が入力されるのがちょっとヤダな~
キー | キーコード | 数値 |
---|---|---|
← | 4B | 4 |
↑ | 48 | 8 |
→ | 4D | 6 |
↓ | 50 | 2 |
これ思ったんですけど、テンキーの上下左右をカーソルに割り当ててるだけですねw
687 456 123
これはMacBook Pro特有のマッピング?他のハードだと矢印キーはどんなキーコードになるんだろう?
キーコードを文字に対応させるプログラムかなんかがあって、そこが差し替え可能になっていると便利ですね。 多分ドライバとかってそのためにあるんだろうけど。
シングルクオートのフォントが変?
若干右に傾いてるような・・・シングルクォートってまっすぐだと思うのですが。。。
hankaku.txt内の0x27の位置にあるデータ
char 0x27 .....*.. ....*... ...*.... ........ ........ ........ ........ ........ ........ ........ ........ ........ ........ ........ ........ ........
と思ったけどiPhoneのキーボード見てみるとガッツリ傾いていますね。
シングルクォーと以外にこんな記号あったような気がするが、なんかの勘違いかな。うーん思い出せない。
SHIFT2つ同時押し+asdf
本章によると左右のSHIFTを押しながらa,s,d,fのキーを押すと、文字入力ができないようです。 私の環境では再現できませんでした。
面白いことを発見しました。左シフト+右シフト+「A」が入力できないのです。
ちなみにasdf以外はSHIFT両押しでも入力できるようです(zとか)
でも、左シフト+右シフト+「Z」は入力できるんです。
他lock系のキー
capslockのキーボードランプ点滅はできませんでした。(これキーボードじゃなくてOSが制御してたんですね。) 点滅できるといえばできますが、多分これホスト側のOSで点滅させているだけで、qemuを介しての動作ではないと思います。 やはり実機で試してみることが大事ですね。
また、私が使ってるMacBook Proにはnumlockとscrollrockのキーがありません。
感想
GUIのラグ
ラグで思い出しましたが、過去に特殊な事情でリモート接続を多重に繰り返すような環境で作業していたことがあります。 詳しいことは聞いていませんが多分WANかなんかで遠隔地を複数回経由したようなラグがありました。 本章の微妙なラグでそんなことを思い出しました。
30日OS自作本16日目
16日目の内容です。 タスク管理の機構を強化するような内容です。
タスク管理
15日目でハードコードされた部分を無くします。タスクも構造体の配列で管理するようにします。
#define MAX_TASKS 1000 /* 最大タスク数 */ //前回から引き続き登場task state segment 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; }; //タスク struct TASK { int sel; /* selはGDTの番号のこと */ int flags; //0:未使用 1:使用中 2:動作中 struct TSS32 tss; //task state segmentへの参照 }; //タスク制御 struct TASKCTL { int running; /* 動作しているタスクの数 */ int now; /* 現在動作しているタスクがどれだか分かるようにするための変数 */ struct TASK *tasks[MAX_TASKS];//タスクの参照の配列 struct TASK tasks0[MAX_TASKS];//タスクの配列 };
細かいですが、flagsは何で複数形なんだろう?変数の型が配列とかならわかるけど、気になる
スリープ
- 実行する必要のないタスクはマルチタスクの対象としたくない
- 例:マウス描画処理はマウス割り込み発生時以外は動かす必要がない。
- マルチタスクの対象から外す -> スリープさせる
- マルチタスク制御用の構造体(taskctl)のマルチタスク対象一覧(tasks[])から一時的に外す
- これをスリープという
- task_sleep()関数で指定したタスクのflgを1にする
動きが不安定なソース
自分の環境だとやはり13日目以降、安定しないソースがいくつかあるようです。 本章は全てのソースが何らかの動作不良を抱えていました。
ソース | 不具合内容 |
---|---|
harib13a | 動きがガタガタ |
harib13b | 動きがガタガタ |
harib13c | 表示が非常に遅い |
harib13d | 表示が非常に遅い |
harib13e | 動きがガタガタ |
追記:harib13c,dは「画面の3/4が表示されない」と表記しましたが、他のものより非常に遅いだけで表示自体はできていたようです。
マルチタスクの詳細設定
ある程度汎用化されているものの現段階でマルチタスクは 全てのタスクを0.002秒ごとに実行しているだけになります。
priority、levelという概念を導入し、マルチタスクを更に高機能化します
一つのタスクにかかる時間を調整する
一律に0.001秒ごとに切り替え、ではなく、taskAは0.1秒実行、taskBは0.01秒実行というふうな タスクごとの割り当て時間を可変的に設定できると便利です。こちらをTASKにpriorityという変数を導入し、 タスクきりかえのタイマーを常に0.002秒ではなく、任意の時間を設定します。
struct TASK { int sel, flags; /* selはGDTの番号のこと */ int level, priority; struct TSS32 tss; };
導入まえ
timer_settime(task_timer, 2);
導入後
timer_settime(task_timer, task->priority);
タスクの優先度をLEVELで制御する
スリープ機能の導入により、マウスの描画(taskA)はマウス割り込み発生時にのみ実行するようになっています。
まちタスクがこんな感じになっている時,
- task1(実行ずみ)
- task2(実行中)←runnning
- task3(未実行)
task2が実行中、task3が未実行の周期でマウス割り込みが発生すると
- task1(実行ずみ)
- task2(実行中)←runnning
- task3(未実行)
- taskA(未実行スリープから復帰)
のようになり、マウス描画(taskA)はtask2のマルチタスク終了後、task3の実行時間を更に待つことになります。 task3が一瞬で終わるようにpriorityが設定されていればいいのですが、こちらに長い実行時間が割り当てられている場合、マウス描画がもたつくことになります。
そこでLEVELという概念を導入し、task3の実行前にtaskAを先に実行させるような仕組みを作ります。*1
- task1(実行ずみ)
- task2(実行中)←runnning
- taskA(未実行スリープから復帰) !!!!ここに並ばせる
- task3(未実行)
struct TSS32 {//変更なしなので略。 }; struct TASK { int sel, flags; /* selはGDTの番号のこと */ int level, priority; struct TSS32 tss; }; struct TASKLEVEL { int running; /* 動作しているタスクの数 */ int now; /* 現在動作しているタスクがどれだか分かるようにするための変数 */ struct TASK *tasks[MAX_TASKS_LV]; }; struct TASKCTL { int now_lv; /* 現在動作中のレベル */ char lv_change; /* 次回タスクスイッチのときに、レベルも変えたほうがいいかどうか */ struct TASKLEVEL level[MAX_TASKLEVELS]; struct TASK tasks0[MAX_TASKS]; };
命名の文句ばっかりで申し訳ないですが、個人的にLEVELの構造体の名前にpriority(優先度)という単語を採用する方がしっくりきますね。。。
感想
15日目に書かれている内容ですが、マルチタスクを影分身と例えているところは言い得て妙。 影分身の移動先がマルチタスク対象の実行命令(far JMP)。 まぁ影分身ってたくさんいるって誤魔化してるだけだから全員HLT命令やっとるだけかな(待機中)。 超高速で移動して待機ってなんか勿体無い。 各移動先から手裏剣投げれると面白いかな。 でもマルチタスクの本質として各移動先で別の処理ができる、というのが重要だから、 移動先からいろいろ(手裏剣以外にも鎌とかマキビシとか)ぶん投げれれば楽しいかな。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
*1:お店の行列の割り込みとかにイメージが近いけど、割り込みという概念はすでに使っているので紛らわしい
Kotlinでラムダ式のインライン展開を逆コンパイルで検証
Java読書会で読んだKotlin in Actionの復習です。 最初はQiitaに投稿するつもりで書いたのですが、 ただ逆コンパイルしたソースを貼り付けてるだけなのでブログに書きます。
インライン関数とそのメリットについて
- ラムダは無名クラスにコンパイルされます。
- 呼び出しのたびに新しいオブジェクトが生成されます
- 実行時のオーバーヘッドとなる
ただし、inline修飾子がある場合はラムダ式がインライン展開され匿名クラスが作られない。 実行ファイルが大きくなる、などのデメリットもあるようだが、呼び出しコストが下がるというメリットがある。
- Kolinの標準ライブラリの関数はほとんどinlineになっている
コンパイル対象のKotlinのソース
fun main(args: Array<String>) { doTenTimes { println("hell world") } } inline fun doTenTimes(function:()->Unit){ for(i in 1 .. 10){ function() } }
このdoTenTimes()のinline修飾子の有無を変えて調査しました。
コンパイル結果
inline 修飾子がない場合
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: hello.kt import java.io.PrintStream; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.internal.Intrinsics; import kotlin.jvm.internal.Lambda; public final class HelloKt { public static final void main(String args[]) { Intrinsics.checkParameterIsNotNull(args, "args"); // 0 0:aload_0 // 1 1:ldc1 #9 <String "args"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> static final class main._cls1 extends Lambda implements Function0 { public volatile Object invoke() { invoke(); // 0 0:aload_0 // 1 1:invokevirtual #12 <Method void invoke()> return Unit.INSTANCE; // 2 4:getstatic #18 <Field Unit Unit.INSTANCE> // 3 7:areturn } public final void invoke() { String s = "hell world"; // 0 0:ldc1 #20 <String "hell world"> // 1 2:astore_1 System.out.println(s); // 2 3:getstatic #26 <Field PrintStream System.out> // 3 6:aload_1 // 4 7:invokevirtual #32 <Method void PrintStream.println(Object)> // 5 10:return } public static final main._cls1 INSTANCE = new main._cls1(); static { // 0 0:new #2 <Class HelloKt$main$1> // 1 3:dup // 2 4:invokespecial #60 <Method void HelloKt$main$1()> // 3 7:putstatic #62 <Field HelloKt$main$1 INSTANCE> //* 4 10:return } } doTenTimes((Function0)main._cls1.INSTANCE); // 3 6:getstatic #21 <Field HelloKt$main$1 HelloKt$main$1.INSTANCE> // 4 9:checkcast #23 <Class Function0> // 5 12:invokestatic #27 <Method void doTenTimes(Function0)> // 6 15:return } public static final void doTenTimes(Function0 function) { Intrinsics.checkParameterIsNotNull(function, "function"); // 0 0:aload_0 // 1 1:ldc1 #30 <String "function"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> int i = 1; // 3 6:iconst_1 // 4 7:istore_1 for(byte byte0 = 10; i <= byte0; i++) //* 5 8:bipush 10 //* 6 10:istore_2 //* 7 11:iload_1 //* 8 12:iload_2 //* 9 13:icmpgt 29 function.invoke(); // 10 16:aload_0 // 11 17:invokeinterface #34 <Method Object Function0.invoke()> // 12 22:pop // 13 23:iinc 1 1 //* 14 26:goto 11 // 15 29:return } }
inline 展開ありの場合
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: hello.kt import java.io.PrintStream; import kotlin.jvm.functions.Function0; import kotlin.jvm.internal.Intrinsics; public final class HelloKt { public static final void main(String args[]) { Intrinsics.checkParameterIsNotNull(args, "args"); // 0 0:aload_0 // 1 1:ldc1 #9 <String "args"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> //* 3 6:nop int i$iv = 1; // 4 7:iconst_1 // 5 8:istore_1 for(byte byte0 = 10; i$iv <= byte0; i$iv++) //* 6 9:bipush 10 //* 7 11:istore_2 //* 8 12:iload_1 //* 9 13:iload_2 //* 10 14:icmpgt 35 //* 11 17:nop { String s = "hell world"; // 12 18:ldc1 #17 <String "hell world"> // 13 20:astore_3 System.out.println(s); // 14 21:getstatic #23 <Field PrintStream System.out> // 15 24:aload_3 // 16 25:invokevirtual #29 <Method void PrintStream.println(Object)> } // 17 28:nop // 18 29:iinc 1 1 //* 19 32:goto 12 // 20 35:nop // 21 36:return } public static final void doTenTimes(Function0 function) { Intrinsics.checkParameterIsNotNull(function, "function"); // 0 0:aload_0 // 1 1:ldc1 #38 <String "function"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> int i = 1; // 3 6:iconst_1 // 4 7:istore_2 for(byte byte0 = 10; i <= byte0; i++) //* 5 8:bipush 10 //* 6 10:istore_3 //* 7 11:iload_2 //* 8 12:iload_3 //* 9 13:icmpgt 29 function.invoke(); // 10 16:aload_0 // 11 17:invokeinterface #44 <Method Object Function0.invoke()> // 12 22:pop // 13 23:iinc 2 1 //* 14 26:goto 11 // 15 29:return } }
- inline修飾子をつけることでコンパイル後のバイトコードがかなり違うことがわかる。
- 匿名クラスを引数にした関数呼び出しのコストなどはJavaのパフォーマンスに関する知識がないため、特にこの部分で説明できることがない
- インライン展開の方がパフォーマンス的には有利らしい。
- 作者: Dmitry Jemerov,Svetlana Isakova,長澤太郎,藤原聖,山本純平,yy_yank
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/10/31
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
30日OS自作本15日目
15日目はマルチタスク(タスクスイッチ)をやりました。
マルチタスクp290~310
- CPUは複数のタスクを同時に実行することができない。タスクをそれぞれ少しずつ実施すること実現できる。
- そのための仕組みがタスクスイッチ
- TR(task register)に現在のタスク番号を格納する
これを短い期間で繰り返している。
タスクスイッチ
保存するレジスタ等の内容
TSS(task status segment)という種類のセグメントを利用します。 int型のメンバが26個ありますので、保存するレジスタ等の情報は全てで26*4=104バイトの領域が必要です。
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; };
こちらをGDTに登録し、タスクスイッチのたびにロード、セーブという具合です。
- 1段目
int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
はレジスタの情報ではありません
JMP命令とEIPレジスタ
- 次の命令の実行場所はEIPレジスタに保持して管理されている
- JMP命令というのは実施
MOV EIP,<JUMP先アドレス>
と同じ意味- ただしアセンブラでEIPに直接代入できない。
- だから素直にJMP命令を使いましょう。
- ただしアセンブラでEIPに直接代入できない。
JUMP命令のnearモードとfarモード
- 命令の読み込み位置はcsとeipによって決まる
- リアルモード時はcs*16+eipで表現。
- csはセグメントのベースアドレス、eipはオフセットアドレスの意味になる。
- プロテクトモード時のcsの挙動の理解が曖昧。
実行イメージ
taskA実行中にtaskBに切り替える例で説明されています。 1.TSSをtaskA,taskBで用意
struct TSS32 tss_a, tss_b;
2.tss_bのeipにタスクとなるプログラムを設定
(関数ポインタは初見?な気がするが特に説明なし?)
void task_b_main(void) { for (;;) { io_hlt(); }//HLT命令。何もしない。 }
tss_b.eip = (int) &task_b_main;
3.GDTにTSSを登録設定
set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32); set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);
4.10秒のカウント表示時に同時にタスクスイッチ命令
} else if (i == 10) { /* 10秒タイマ */ putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); taskswitch4(); //コレ!!!!!
5.アセンブリに定義したタスクスイッチ関数を実行。 ここでfarモードでジャンプ。
_taskswitch4: ; void taskswitch4(void); JMP 4*8:0 RET
コレで3.で登録したGDT+4(task_b)の位置にJMP。この時、ジャンプ先のセグメントが実行可能セグメントではなく、 TSSなのでCPUはEIPやCSを書き換えずにタスクスイッチ命令だと解釈し、TSSで指定したタスクに変わるらしい。
※本章では以後決め打ちになっているタスク名やアドレスは関数の引数にして汎用的なタスクスイッチ処理に作り変えられる。
動画
上記のプログラムを動かした動画。10秒経過の次の瞬間にHLT命令が実行されマウス操作が受け付けられなくなっている。*1
感想
CPUがマルチコアだとどうなる?とか、Javaでマルチスレッドなプログラミングを行った時にCPUレベルではどのように動く?などまだまだイメージできないことは多いですが、
- マルチコア
- マルチタスク
- マルチスレッド
この辺の違いを意識していこう。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
*1:マウスの割り込み操作は多分受け付けているが描画処理が実行されていない。
30日OS自作本14日目
14日目の内容です。 内容としてはGUI機能をさらに強化する感じです。
- Windowが動くようになりました(不完全)
- 画面が高解像度になりました。
- キーボード入力に対応した文字が表示できるようになりました。
GUIは結構進みましたが、まだまだ改善の余地を感じさせる章でした。
動作状況
自分の環境(macOS HighSierra)では前半のコードが全く動いていません
コード | 動作 | 詳細 |
---|---|---|
harib11a | × | 非常に遅い |
harib11b | × | 非常に遅い |
harib11c | × | 非常に遅い |
harib11d | × | 表示せず(真っ暗) |
harib11e | × | 非常に遅い |
harib11f | ◯ | 正常に動作 |
harib11g | ◯ | 正常に動作 |
harib11h | ◯ | 正常に動作 |
harib11i | ◯ | 正常に動作 |
なんか総当たりでコードを入れたり抜いたりすれば分かりそうな気がしますが、多分もうちょっとスキルが着けば簡単に解決するような問題な気がするので今は首をつっこむのはやめようと思います。
一応似た環境でやってる人向けに情報共有ということで。
harib11dに付いて
harib11dですが、以下のブログの通りVRAMに書き込む番地を変えることで一応表示した。(ただし動作は非常に遅い。)
MOV DWORD [VRAM],0xe0000000
↓
MOV DWORD [VRAM],0xfd000000
これはどうやってデバッグしたんだろう???
こちらのブログは毎日更新を3年以上続けられていて(内容も濃くて)非常に凄いですね
追記:msyksphinzさんにコメントいただきました
以下を参考にしたとのことです。
https://qiita.com/tatsumack/items/491e47c1a7f0d48fc762
キー入力 to 文字列の変換
以下のようにキーと入力信号の対応があります。
A -> 1E
OSでキーボードを扱う場合に、この番号を文字列に対応づける仕組みが必要となります。
static char keytable[0x54] = { 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.' };
利用するときはこう
i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { /* キーボードデータ */ sprintf(s, "%02X", i - 256); putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2); if (i < 256 + 0x54) { if (keytable[i - 256] != 0) { s[0] = keytable[i - 256];//ココ!!! s[1] = 0; putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1); } }
ちなみにこの256を足したり引いたりしてるやつは、このブログには書いていませんが、FIFOを一本に纏めた影響。
DB命令
keytable[]がstatic char担っているのは、アセンブラに翻訳されるときにDB命令にして欲しいからです。
とのことですのでDB命令になっているアセンブラを確認してみました。
43 00000000 00 DB 0 44 00000001 00 DB 0 45 00000002 31 DB 49 46 00000003 32 DB 50 47 00000004 33 DB 51 48 00000005 34 DB 52 49 00000006 35 DB 53 50 00000007 36 DB 54 51 00000008 37 DB 55 52 00000009 38 DB 56 53 0000000A 39 DB 57 54 0000000B 30 DB 48 55 0000000C 2D DB 45 56 0000000D 5E DB 94 57 0000000E 00 DB 0 58 0000000F 00 DB 0 59 00000010 51 DB 81 60 00000011 57 DB 87 61 00000012 45 DB 69 62 00000013 52 DB 82 63 00000014 54 DB 84 64 00000015 59 DB 89 65 00000016 55 DB 85 66 00000017 49 DB 73 67 00000018 4F DB 79 68 00000019 50 DB 80 69 0000001A 40 DB 64 70 0000001B 5B DB 91 71 0000001C 00 DB 0 72 0000001D 00 DB 0 73 0000001E 41 DB 65;A 74 0000001F 53 DB 83;S 75 00000020 44 DB 68;D 76 00000021 46 DB 70 77 00000022 47 DB 71 78 00000023 48 DB 72 79 00000024 4A DB 74 80 00000025 4B DB 75 81 00000026 4C DB 76 82 00000027 3B DB 59 83 00000028 3A DB 58 84 00000029 00 DB 0 85 0000002A 00 DB 0 86 0000002B 5D DB 93 87 0000002C 5A DB 90 88 0000002D 58 DB 88 89 0000002E 43 DB 67 90 0000002F 56 DB 86 91 00000030 42 DB 66 92 00000031 4E DB 78 93 00000032 4D DB 77 94 00000033 2C DB 44 95 00000034 2E DB 46 96 00000035 2F DB 47 97 00000036 00 DB 0 98 00000037 2A DB 42 99 00000038 00 DB 0 100 00000039 20 DB 32 101 0000003A 00 DB 0 102 0000003B 00 DB 0 103 0000003C 00 DB 0 104 0000003D 00 DB 0 105 0000003E 00 DB 0 106 0000003F 00 DB 0 107 00000040 00 DB 0 108 00000041 00 DB 0 109 00000042 00 DB 0 110 00000043 00 DB 0 111 00000044 00 DB 0 112 00000045 00 DB 0 113 00000046 00 DB 0 114 00000047 37 DB 55 115 00000048 38 DB 56 116 00000049 39 DB 57 117 0000004A 2D DB 45 118 0000004B 34 DB 52 119 0000004C 35 DB 53 120 0000004D 36 DB 54 121 0000004E 2B DB 43 122 0000004F 31 DB 49 123 00000050 32 DB 50 124 00000051 33 DB 51 125 00000052 30 DB 48 126 00000053 2E DB 46
この辺ですね~
staticの有無でアセンブリはどう変わるか
ちなみにstaticを抜いてコンパイルしてみましたが、こちらもDB命令になりました。
42 00000000 00 DB 0 43 00000001 00 DB 0 (省略) 119 0000004D 36 DB 54 120 0000004E 2B DB 43 121 0000004F 31 DB 49 122 00000050 32 DB 50 123 00000051 33 DB 51 124 00000052 30 DB 48 125 00000053 2E DB 46
よく分かりませんが、staticを着けないとLCXというラベルを貼られ、他のプログラム上にハードコードされた文字列リテラルと同じようにデータセクション上でLC<数字>のラベルを貼られて管理されるようです。staticをつけるとLC<数字>ではなく配列名のラベルで別途管理されるようです。
- staticを抜いてもDB命令になる
- staticを入れると_keytableという名前でアセンブリ内で参照できるようになる
といった違いがあります。
staticを着けない場合のデータセクション
40 [SECTION .data] 41 00000000 LC0: 42 00000000 00 DB 0 (例の文字データ) 126 00000054 LC1: 127 00000054 77 69 6E 64 6F 77 00 DB "window",0x00 128 0000005B LC2: 129 0000005B 28 25 33 64 2C 20 25 33 64 29 DB "(%3d, %3d)",0x00 00000065 00 130 00000066 LC3: 131 00000066 6D 65 6D 6F 72 79 20 25 64 4D DB "memory %dMB free : %dKB",0x00 00000070 42 20 20 20 66 72 65 65 20 3A 0000007A 20 25 64 4B 42 00 132 00000080 LC7: 133 00000080 33 5B 73 65 63 5D 00 DB "3[sec]",0x00 134 00000087 LC6: 135 00000087 31 30 5B 73 65 63 5D 00 DB "10[sec]",0x00 136 0000008F LC5: 137 0000008F 5B 6C 63 72 20 25 34 64 20 25 DB "[lcr %4d %4d]",0x00 00000099 34 64 5D 00 138 0000009D LC4: 139 0000009D 25 30 32 58 00 DB "%02X",0x00
staticを着けた場合のデータセクション
41 [SECTION .data] 42 00000000 _keytable.0: 43 00000000 00 DB 0 (例の文字データ) 126 00000053 2E DB 46 127 00000054 LC0: 128 00000054 77 69 6E 64 6F 77 00 DB "window",0x00 129 0000005B LC1: 130 0000005B 28 25 33 64 2C 20 25 33 64 29 DB "(%3d, %3d)",0x00 00000065 00 131 00000066 LC2: 132 00000066 6D 65 6D 6F 72 79 20 25 64 4D DB "memory %dMB free : %dKB",0x00 00000070 42 20 20 20 66 72 65 65 20 3A 0000007A 20 25 64 4B 42 00 133 00000080 LC6: 134 00000080 33 5B 73 65 63 5D 00 DB "3[sec]",0x00 135 00000087 LC5: 136 00000087 31 30 5B 73 65 63 5D 00 DB "10[sec]",0x00 137 0000008F LC4: 138 0000008F 5B 6C 63 72 20 25 34 64 20 25 DB "[lcr %4d %4d]",0x00 00000099 34 64 5D 00 139 0000009D LC3: 140 0000009D 25 30 32 58 00 DB "%02X",0x00
宣言した直後の後処理の部分が少し違っているようです、(staticなしの方が多い)
staticなし
142 00000000 _HariMain: 143 00000000 55 PUSH EBP 144 00000001 B9 00000015 MOV ECX,21 145 00000006 89 E5 MOV EBP,ESP 146 00000008 57 PUSH EDI 147 00000009 56 PUSH ESI 148 0000000A BE [00000000] MOV ESI,LC0 149 0000000F FC CLD 150 00000010 53 PUSH EBX 151 00000011 8D BD FFFFFC34 LEA EDI,DWORD [-972+EBP] 152 00000017 81 EC 000003E4 SUB ESP,996 153 0000001D F3 REP 154 0000001E A5 MOVSD 155 0000001F 8D 75 D4 LEA ESI,DWORD [-44+EBP] 156 00000022 E8 [00000000] CALL _init_gdtidt
staticあり
143 00000000 _HariMain: 144 00000000 55 PUSH EBP 145 00000001 89 E5 MOV EBP,ESP 146 00000003 57 PUSH EDI 147 00000004 56 PUSH ESI 148 00000005 53 PUSH EBX 149 00000006 8D 75 D4 LEA ESI,DWORD [-44+EBP] 150 00000009 81 EC 00000384 SUB ESP,900 151 0000000F E8 [00000000] CALL _init_gdtidt
参照じに以下のような違いが出ます。
545 00000586 8A 84 1D FFFFFB34 MOV AL,BYTE [-1228+EBP+EBX*1] ;staticなし 540 0000054D 8A 83 [FFFFFF00] MOV AL,BYTE [_keytable.0-256+EBX];staticあり
staticありの方が元のコードに近く分かりやすいアセンブリになっています。
元のソース
/* 一文字表示してから、カーソルを1つ進める */ s[0] = keytable[i - 256];
高解像度化
VBE という規格があるようです。full HDでブートしたくて少し調べたのですが、hariboteOSが利用するでは1280x1024
が最高のようです。
; (画面モード一覧) ; 0x100 : 640 x 400 x 8bitカラー ; 0x101 : 640 x 480 x 8bitカラー ; 0x103 : 800 x 600 x 8bitカラー ; 0x105 : 1024 x 768 x 8bitカラー ; 0x107 : 1280 x 1024 x 8bitカラー
http://softwaretechnique.jp/OS_Development/Tips/VESA/vbe02.html https://en.wikipedia.org/wiki/VESA_BIOS_Extensions
さらに上の解像度が使えないか試してみましたが、1280x1024
以上でブートすることはできませんでした。
なお解像度はリアルモードで設定しています。最近のOSだとプロテクトモードで設定したりするんでしょうか。
感想
作っているGUIはwindows xp以前のGUIにそっくりなので発売当時の少年少女は 12日目あたりから、自分が打ち込んだソースが、まるでWindowsのような表示になって歓喜しているのではないでしょうか?
パソコンが魔法の箱ではなく巨大ピタゴラ装置であるとに、この辺で気づけるのかもしれません。
アセンブリ能力
ひさしぶりにアセンブリと向き合いましたが、COBOL/RPGなどの古い言語に結構似てるな〜と思います。 とにかくアセンブリを読むのに非常に疲れました。アセンブリ能力を高めるには段階的に
- HelloWorld
- if文
- for文
- 関数呼び出し
- 再帰呼び出し
- 参照渡し
- 値渡し
- 構造体
などを算数の計算ドリルのように、元のプログラムと比較したものを見比べる訓練を段階的に行うと良さそうです。 この本だけだとアセンブリ力アップは厳しそうです。エッセンスは学べると思いますが、別途トレーニングの必要があります。 アセンブリをスラスラ読めるようになってから再読するとまた発見が多そうです。
プログラマのためのSQL 読書会(19)に参加
「24 単純なSELECT文」から「25.3.3 自然外部結合と条件付き外部結合」まで読みました。
内容的にはSELECT文、結合などが中心です。
プログラマのためのSQL 第4版 すべてを知り尽くしたいあなたに
- 作者: Joe Celko
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (11件) を見る
結合って、初心者が挫折しやすいところではないでしょうか。 この本のようにcross joinから説明を始めると、わかりやすいのかもしれません。
MySQLのExplain
MySQLではExplain extendを使うことでより詳細な実行計画を表示できる。
https://dev.mysql.com/doc/refman/5.6/ja/explain-extended.html
結合前の絞り込み件数などが見えるようです。
サブクエリのネスト
「25.1相関サブクエリ」
相関サブクエリを入れ子にできる深さに理論上の制限はない。
とありますが、勿論格RDBMS製品にサブクエリのネスト制限があります。
例
字句解析・構文解析
などの話題が出た。 SQLのBNFを食わせることでパーサーを作れるらしい。
Cコンパイラを作ろう的な本で軽くみたことがあるので、この辺はしっかり掘り下げていきたい。
コードファースト、テーブルファースト
チームの考え方によってはテーブルをそこまで重視しない場合もあるらしい(アジャイル等) コード(クラス)から生成させるもの、という視点の現場ではテーブルの変更が運用中に入ることも多いらしい。
まぁ普通はテーブル作ってから開発入ると思いますが。*1
upc
「25.2 標準的な構文の内部結合」に突如出ていた謎のコードことupc。
もし読者の使っているスキーマの中にproduct_idやproduct_nbr、あるいはupcといった列名が複数のテーブルで使われているようであれば、それぞれの要素が一意な名前を持つようスキーマ全体を変更するべきだろう。
upcってなんだ?という話題になりました。
https://www.keyence.co.jp/ss/products/autoid/codereader/basic_jan.jsp
よく探せば「4.2.3 業界標準の一位識別子」に書いてありますね。
GIIN(国際取引商品番号)は小売業の商品についている商品識別コードの総称で「ジーティン」と読む。北米では、UPCというGTINの一種がよく利用されている。
外部結合はSQL99から
外部結合の構文はSQL99で追加になった。それ以前はUNIONを使った面倒のクエリが必要。 やってることは内部結合 + 左側の行で結合できず消えた列をunionで足している。
SELECT a.valu1,a.valu2,b.valu1 FROM a LEFT JOIN b on (a.id = b.a_id)
SELECT a.valu1,a.valu2,b.valu1 FROM a,b WHERE a.id = b.a_id UNION ALL SELECT a.valu1,a.valu2,CAST(NULL AS INTEGER) FROM a WHERE NOT EXISTS(SELEDT * FROM b where b.a_id = a.id);
迂回するUNION句は現実には存在しない?
OUTER JOIN導入以前は律儀に上記の迂回クエリを使っていたのかと思いきや・・・ そういうことはなく、各ベンダー早めに外部結合を入れる構文を導入していたのでそちらを利用して凌いでいた、というのが実情らしい。*2
実際に上記の外部結合と同等の結果を返すクエリの例。 https://www.shift-the-oracle.com/sql/left-right-outer-join.html https://msdn.microsoft.com/ja-jp/library/ee240720(v=sql.120).aspx
JOIN禁止?
- JOIN禁止という現場もあるらしい。
- IBM系で多いらしい。
JOIN禁止でググったらそれらしいブログが出てきた。
http://d.hatena.ne.jp/Sikushima/20110809/1312871002
RIGHT JOINは必要なのか?
RIGHT JOINだけで良いのでは無いか、標準SQLにはなぜRIGHT JOINが入っているのかという話題が出ました。 一説によると、FROM句の順番を変えられないフレームワーク等(ORM,BIツール)用にあるのでは?という話になりました。
プログラマのためのSQL 第4版 すべてを知り尽くしたいあなたに
- 作者: Joe Celko
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (11件) を見る
30日OS自作本13日目(詰む)
13日目のソースは
- harib10a:文字列表を簡単に(文字列表示のヘルパー関数を作る)
- harib10b:FIFOバッファを見直す(タイマーのFIFOを統合する)
- harib10c:性能を測定してみる(パフォーマンス測定用のカウンターの導入)
ですが、harib10cのソースが正常に動作しませんでした。
diffの結果
$ diff harib10b/bootpack.c harib10c/bootpack.c 15c15 < int mx, my, i; --- > int mx, my, i, count = 0; 78,79c78 < sprintf(s, "%010d", timerctl.count); < putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); --- > count++; 129a129,131 > > sprintf(s, "%010d", count); > putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); 131a134 > count = 0; /* 測定開始 */
上記のdiffの結果を参考に原因調査したところ、ウインドウに経過時間を書き込む関数をコメントアウトすると、動作が不安定になるようです。
for (;;) { sprintf(s, "%010d", timerctl.count); //putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); //↑79行目のこれをコメントアウトすると動作が不安定になる。 io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) { io_sti(); } else { //マウス、キーボード、タイマ割り込みが発生した時の処理(略) } }
このputfonts8_asc_shtはウインドウに文字を書き込む以外のことをしていないと思うのですが、 なぜか抜くとqemuのパフォーマンスに影響が出ました。
該当行の有無別でbootpack.lstのバイナリを見比べれば何か分かりそうな気がしますが、 14日目、30日目のソースをビルドしてみましたがこちらは正常に動作しているようですので、13日目は読むだけにしようと思います。