30日OS自作入門12日目(Win10)

はじめに

目次

タイマ-1

タイマを使おう(harib09a)

前回は、無限ループで全力を賭してカウントを続けていましたが、あれは無限ループが繰り返されるたびに起動しているのでタイマとなれるはずもありません。

じゃどうする…?クロック数に合わせて調整をして、やる?HLTができない…どうしよ…ってなのが今回のようです。

解決法は、タイマ割り込みという、PC自体に時間を計測する装置を利用する方法があるから大丈夫みたいです。

やり方は、キーボードやマウスの時みたいに割り込みを設定してあげて、timer.cに割り込みの処理内容を書くようです。

#define PIT_CTRL  0x0043  /* ポート:コントロールレジスタを指定 */
#define PIT_CNT0  0x0040  /* ポート:カウンタ0を指定 */

/* IRQ0の割り込み処理変更 */
void init_pit(void)
{
  io_out8(PIT_CTRL, 0x34);  
  /* 2進数カウント、レートジェネレータ、16bitリードロード(下位8bit、上位8bitの順)、カウンタ0 */
  io_out8(PIT_CNT0, 0x9c);  /* 割り込み周期の下位8bit */
  io_out8(PIT_CNT0, 0x2e);  /* 割り込み周期の上位8bit */
  return;
}

これでPITの初期設定を行う、仕様に関しては以下のURLに記述されている。

(PIT)8254 - os-wiki

そして、HarimainにPITも含めた初期化の設定を追加する

init_pit(); /* PITの初期化 */
io_out8(PIC0_IMR, 0xf8); /* PITとPIC1とキーボードを許可(1111_1000) */

後は、割り込み処理の内容と登録を行うだけになります。

まず、timer.cに処理する内容を記述し

/* IDT20の処理設定 */
void inthandler20(int *esp)
{
  io_out8(PIC0_OCW2, 0x60); /* IRQ-00受付完了をPICに通知 */
  /* とりあえず何もしない */
  return;
}

次に、naskfunc.nasIDTのを追加することを記載します

_asm_inthandler20:  ;timer
    PUSH    ES
    PUSH    DS
    PUSHAD
    MOV     EAX,ESP
    PUSH    EAX
    MOV     AX,SS
    MOV     DS,AX
    MOV     ES,AX
    CALL    _inthandler20
    POP     EAX
    POPAD
    POP     DS
    POP     ES
    IRETD

あとは、dsctbl.cでIDTに割り込みハンドラの登録を行うことで設定が終わります。

set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);  /* 0x20番目にタイマー割り込みを設定 */

これで実行すると…
f:id:No000:20191226203244p:plain

何も起きていませんが、しっかりと動いています。

時間を計ってみよう(harib09b)

今回は、設定したタイマー割り込みを利用して、ウィンドウに数えているところを表示させようといった感じです。

まずは、カウントしたデータを渡すために、構造体を作るみたいです。

/* timer.c */
struct TIMERCTL {
    unsigned int count;
};

そして、timer.cに構造体のcountにカウントするプログラムを書き、Harimainの無限ループに組み込んでやれば

    for (;;) {
        sprintf(s, "%010d", timerctl.count);
        boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
        putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
        sheet_refresh(sht_win, 40, 28, 120, 44);

実行すると

タイムアウト機能(harib09c)

時間を取得できるようになったので、処理時間の計測等の処理を行うことができるようになります。
で、この時間を利用して、何秒たったらこれをしてね!(Linuxのcronみたいな)ことをタイムアウトと呼びます。
それをやってみようといった感じのようです。

まずは、タイマー用の構造体TIMERCTLにメンバを追加します。

/* timer.c */
struct TIMERCTL {
    unsigned int count;
    unsigned int timeout;
    struct FIFO8 *fifo;
    unsigned char data;
};

そして、処理内容をinthandler20に記述し

/* IDT20の処理設定 */
void inthandler20(int *esp)
{
  io_out8(PIC0_OCW2, 0x60); /* IRQ-00受付完了をPICに通知 */
  timerctl.count++;   /* 指定に従いカウントする */
  if (timerctl.timeout > 0) {
    timerctl.timeout--;
    if (timerctl.timeout == 0) {
      fifo8_put(timerctl.fifo, timerctl.data);  
      /* カウントが0になったらFIFOバッファに送る */
    }
  }
  return;
}

あとは、settimerで構造体に必要なデータを渡す。

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
  int eflags;
  eflags = io_load_eflags();  /* フラグレジスタの退避 */
  io_cli();     /* 割り込み禁止 */
  timerctl.timeout = timeout;
  timerctl.fifo = fifo;
  timerctl.data = data;
  io_store_eflags(eflags);    
  /* フラグレジスタを戻す(この際IFフラグも戻る) */
  return;
}

そして、終わりにHriMainの無限ループにくみこんでやれば…

複数のタイマを(harib09d)

今回では点滅する四角と3秒で表示するマークを作るようです。

まず構造体TIMERCTLを複数のタイマを保管できるようにするようです。

#define MAX_TIMER   500
struct TIMER {
    unsigned int timeout, flags;    /* flags:それぞれのtimerの状態を保管 */
    struct FIFO8 *fifo;
    unsigned char data;
};
struct TIMERCTL {
    unsigned int count;    /* カウンタ */
    struct TIMER timer[MAX_TIMER];  /* timeoutを500個管理可能 */
};

あとは、Timer.cとHarimainを点滅と3秒用に増やせば完成

実行すると

f:id:No000:20191229122023p:plain

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

割り込み処理を短くするための道その1です。
一回目は長くなってしまう割り込み処理を少しでも短くしようとするみたいです。減算しながら数えていたカウントを予定時刻に設定し、減算をしなくてもよいようにしています。要は、割り込み内での減算をなくし、ある一定の秒数に達したら割り込みをするといった感じみたいです。

割り込み処理の修正

void inthandler20(int *esp)
{
  int i;
  io_out8(PIC0_OCW2, 0x60); /* IRQ-00受付完了をPICに通知 */
  timerctl.count++;   /* 指定に従いカウントする */
  for (i = 0; i < MAX_TIMER; i++) {
    if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
      if (timerctl.timer[i].timeout <= timerctl.count) {    
      /* これで減算しつつタイマーにしていたのを、一定の時間に来たら通知するという仕組みにすることができた */
        timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
        fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
      }
    }
  }
  return;
}

割り込み予定時刻の測定

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
  timer->timeout = timeout + timerctl.count;
  /* 割り込み予定時刻を計算している */
  timer->flags = TIMER_FLAGS_USING;
  return;
}

これで実行してみると
上と同じ感じですね…あまり違いがない

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

割り込み処理がまだ長いので、次はif文を減らすことを行うみたいです。
減らし方は、500個ものタイマを全部ifで一々確認するのは非効率的なので、次にどのタイマが来るかを構造体で覚えておけるように修正を行います。

struct TIMERCTL {
    unsigned int count, next;    
    /* count:カウンタ,next:次に注目するタイマー */
    struct TIMER timer[MAX_TIMER];
};

これに対応し、割り込みハンドラも修正するようです。

/* IDT20の処理設定 */
void inthandler20(int *esp)
{
  int i;
  io_out8(PIC0_OCW2, 0x60); /* IRQ-00受付完了をPICに通知 */
  timerctl.count++;   /* 指定に従いカウントする */
  if (timerctl.next > timerctl.count) {
    return; /* まだ次の時刻になっていないので、もうおしまい */
  }
  timerctl.next = 0xffffffff;
  for (i = 0; i < MAX_TIMER; i++) {
    if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
      if (timerctl.timer[i].timeout <= timerctl.count) {    
      /* これで減算しつつタイマーにしていたのを、一定の時間に来たら通知するという仕組みにすることができた */
        timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
        fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
      } else {
        /* まだタイムアウトではない */
        if (timerctl.next > timerctl.timer[i].timeout) {
          timerctl.next = timerctl.timer[i].timeout;
        }
      }
    }
  }
  return;
}

あとは、依存しているtimer.cの関数の修正を行って実行すると

同じ画面ですが、動くので成功したみたい

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

もう少し、それぞれのタイマの管理をしたいのでsheet.cでのシートごとの管理のように、タイマもそれぞれ管理できるように修正するようです。

まず、構造体に各種タイマを番号と使っているかに関しててで管理できるように変更を行うみたいで

#define MAX_TIMER   500
struct TIMER {
    unsigned int timeout, flags;
    struct FIFO8 *fifo;
    unsigned char data;
};
struct TIMERCTL {
    unsigned int count, next, using;    
    /* count:カウンタ,next:次に注目するタイマー,using:使用中タイマの個数 */
    struct TIMER *timers[MAX_TIMER];
    /* タイマーを順番に管理 */
    struct TIMER timers0[MAX_TIMER];
};

/ IDT20の処理設定 / void inthandler20(int esp) { int i, j; / / io_out8(PIC0_OCW2, 0x60); / IRQ-00受付完了をPICに通知 / timerctl.count++; / 指定に従いカウントする / if (timerctl.next > timerctl.count) { return; / まだ次の時刻になっていないので、もうおしまい / } for (i = 0;i < timerctl.using; i++) { / timers のタイマは全て動作中のものなので、flagsを確認しない / if (timerctl.timers[i]->timeout > timerctl.count) { break; } / タイムアウト / timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC; fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data); } / ちょうど一個のタイマがタイムアウトした。残りをずらす。 */ timerctl.using -= i; for (j = 0; j < timerctl.using; j++) { timerctl.timers[j] = timerctl.timers[i + j]; } if (timerctl.using > 0) { timerctl.next = timerctl.timers[0]->timeout; } else { timerctl.next = 0xffffffff; } return; }

そして、依存している関数init_pitとtimer_allocを修正し、timersへの登録を行うプログラムを書きます。  

/ timersへの登録を追加 / void timer_settime(struct TIMER timer, unsigned int timeout) { int e, i, j; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); / どこに入れればいいかを探す / for (i = 0; i < timerctl.using; i++) { if (timerctl.timers[i]->timeout >= timer->timeout) { break; } } / うしろにずらす / for (j = timerctl.using; j > i; j--) { timerctl.timers[j] = timerctl.timers[j - 1]; } timerctl.using++; / 空いた隙間に入れる */ timerctl.timers[i] = timer; timerctl.next = timerctl.timers[0]->timeout; io_store_eflags(e); return; }

これで実行すると  

[https://twitter.com/Wagahaiha_toto/status/1211146197672480768:embed]


変わってはないですが、動くということは成功みたいです。  

#おわりに  
プラサミナウ