Yabu.log

ITなどの雑記

30日OS自作本17日目

17日目の内容です。

www.youtube.com

ソースの動作具合

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が表示されない」と表記しましたが、他のものより非常に遅いだけで表示自体はできていたようです。

www.youtube.com

マルチタスクの詳細設定

ある程度汎用化されているものの現段階でマルチタスクは 全てのタスクを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命令やっとるだけかな(待機中)。 超高速で移動して待機ってなんか勿体無い。 各移動先から手裏剣投げれると面白いかな。 でもマルチタスクの本質として各移動先で別の処理ができる、というのが重要だから、 移動先からいろいろ(手裏剣以外にも鎌とかマキビシとか)ぶん投げれれば楽しいかな。

30日でできる! OS自作入門

30日でできる! OS自作入門

*1:お店の行列の割り込みとかにイメージが近いけど、割り込みという概念はすでに使っているので紛らわしい

Kotlinでラムダ式のインライン展開を逆コンパイルで検証

Java読書会で読んだKotlin in Actionの復習です。 最初はQiitaに投稿するつもりで書いたのですが、 ただ逆コンパイルしたソースを貼り付けてるだけなのでブログに書きます。

インライン関数とそのメリットについて

Kotlinではラムダ式は無名クラスにコンパイルされる

  • ラムダは無名クラスにコンパイルされます。
  • 呼び出しのたびに新しいオブジェクトが生成されます
  • 実行時のオーバーヘッドとなる

ただし、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のパフォーマンスに関する知識がないため、特にこの部分で説明できることがない
    • インライン展開の方がパフォーマンス的には有利らしい。

Kotlinイン・アクション

Kotlinイン・アクション

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命令を使いましょう。
      

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

www.youtube.com

感想

CPUがマルチコアだとどうなる?とか、Javaでマルチスレッドなプログラミングを行った時にCPUレベルではどのように動く?などまだまだイメージできないことは多いですが、

この辺の違いを意識していこう。

30日でできる! OS自作入門

30日でできる! OS自作入門

*1:マウスの割り込み操作は多分受け付けているが描画処理が実行されていない。

30日OS自作本14日目

14日目の内容です。 内容としてはGUI機能をさらに強化する感じです。

  • Windowが動くようになりました(不完全)
  • 画面が高解像度になりました。
  • キーボード入力に対応した文字が表示できるようになりました。

GUIは結構進みましたが、まだまだ改善の余地を感じさせる章でした。

www.youtube.com

動作状況

自分の環境(macOS HighSierra)では前半のコードが全く動いていません

コード 動作 詳細
harib11a × 非常に遅い
harib11b × 非常に遅い
harib11c × 非常に遅い
harib11d × 表示せず(真っ暗)
harib11e × 非常に遅い
harib11f 正常に動作
harib11g 正常に動作
harib11h 正常に動作
harib11i 正常に動作

なんか総当たりでコードを入れたり抜いたりすれば分かりそうな気がしますが、多分もうちょっとスキルが着けば簡単に解決するような問題な気がするので今は首をつっこむのはやめようと思います。

一応似た環境でやってる人向けに情報共有ということで。

harib11dに付いて

harib11dですが、以下のブログの通りVRAMに書き込む番地を変えることで一応表示した。(ただし動作は非常に遅い。)

msyksphinz.hatenablog.com

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以上でブートすることはできませんでした。

www.youtube.com

なお解像度はリアルモードで設定しています。最近のOSだとプロテクトモードで設定したりするんでしょうか。

感想

作っているGUIwindows xp以前のGUIにそっくりなので発売当時の少年少女は 12日目あたりから、自分が打ち込んだソースが、まるでWindowsのような表示になって歓喜しているのではないでしょうか?

パソコンが魔法の箱ではなく巨大ピタゴラ装置であるとに、この辺で気づけるのかもしれません。

アセンブリ能力

ひさしぶりにアセンブリと向き合いましたが、COBOL/RPGなどの古い言語に結構似てるな〜と思います。 とにかくアセンブリを読むのに非常に疲れました。アセンブリ能力を高めるには段階的に

  • HelloWorld
  • if文
  • for文
  • 関数呼び出し
  • 再帰呼び出し
  • 参照渡し
  • 値渡し
  • 構造体

などを算数の計算ドリルのように、元のプログラムと比較したものを見比べる訓練を段階的に行うと良さそうです。 この本だけだとアセンブリ力アップは厳しそうです。エッセンスは学べると思いますが、別途トレーニングの必要があります。 アセンブリをスラスラ読めるようになってから再読するとまた発見が多そうです。

プログラマのためのSQL 読書会(19)に参加

「24 単純なSELECT文」から「25.3.3 自然外部結合と条件付き外部結合」まで読みました。

内容的にはSELECT文、結合などが中心です。

結合って、初心者が挫折しやすいところではないでしょうか。 この本のようにcross joinから説明を始めると、わかりやすいのかもしれません。

MySQLのExplain

MySQLではExplain extendを使うことでより詳細な実行計画を表示できる。

https://dev.mysql.com/doc/refman/5.6/ja/explain-extended.html

結合前の絞り込み件数などが見えるようです。

サブクエリのネスト

「25.1相関サブクエリ」

相関サブクエリを入れ子にできる深さに理論上の制限はない。

とありますが、勿論格RDBMS製品にサブクエリのネスト制限があります。

字句解析・構文解析

flex,yacc,バイソン

などの話題が出た。 SQLBNFを食わせることでパーサーを作れるらしい。

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ツール)用にあるのでは?という話になりました。

https://www.quora.com/Why-do-we-have-Left-Join-and-Right-Join-in-SQL-if-we-can-use-Left-Join-to-get-same-result-as-of-Right-Join-by-just-changing-the-position-of-tables

*1:時代遅れ?

*2:標準SQLを使うべきという立場のこの本からすれば標準外のベンダ拡張を使うのは好ましく無い

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の結果を参考に原因調査したところ、ウインドウに経過時間を書き込む関数をコメントアウトすると、動作が不安定になるようです。

www.youtube.com

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のパフォーマンスに影響が出ました。

f:id:yuyubu:20180528210833p:plain
qemuのcpu使用率が上がっています

該当行の有無別でbootpack.lstのバイナリを見比べれば何か分かりそうな気がしますが、 14日目、30日目のソースをビルドしてみましたがこちらは正常に動作しているようですので、13日目は読むだけにしようと思います。

github.com