な毎か

なんて事ない毎日がかけがえないんだよなぁ…

x86(i386)向けのOS作成日記 - タイマ1

2017.12.3

プリキュアを見た。面白い。今日はアイカツイリュージョンライブの2日目にも参加するため、横浜のコメダで作業。

だいぶ遅れたけど、EFLAGSCR0のビットフィールドをまとめる。レジスタが綺麗にまとまっていた。

EFLAGS

EFlagsレジスタ 全32bitで構成されれるレジスタで、各種演算結果、制御フラグ、システムフラグなどが格納される。

S ステータスフラグを表す。 addやsubなどの演算結果の状態を表す。 C 制御フラグを表す。 制御フラグは、bit10のDFのみに該当する。 X システムフラグを表す。 OSやアプリケーションの動作を制御する。 このフラグはアプリケーションが変更してはいけない。 予約 干渉してはいけない領域。セットするときは、必ず読み込んだ値をセットすること。

bit 種別 Flag 名前 説明 関連命令
0 S CF Carry Flag キャリーフラグ 最上位ビットでキャリーかボローが生じたときに1がセットされる。 stc, clc, cmc
1 1 予約
2 S PF Parity Flag パリティフラグ 演算結果の最下位バイトで、値が1のビットが偶数個なら1がセットされる。
3 0 予約
4 S AF Auxiliary Carry Flag 補助キャリーフラグ ビット3でキャリーかボローが生じたときに1がセットされる。BCD演算で使用する。
5 0 予約
6 S ZF Zero Flag ゼロフラグ 演算結果が0のとき1がセットされる。
7 S SF Sign Flag 符号フラグ 演算結果の最上位ビットがそのままセットされる。
8 X TF Trap Flag
9 X IF Interrupt Enable Flag 割り込み許可フラグ 1のときは割り込みを許可し、0なら割り込みを禁止する。 sti, cli
10 C DF Direction Flag 方向フラグ ストリング命令を上位に向かって処理(0)するか、下位に向かって処理(1)するかを制御する。 std, cld
11 S OF Overflow Flag オーバーフローフラグ 最上位ビットが変化した場合に1がセットされる。
12 X IOPL I/O Privilege Level I/O特権レベル
13
14 X NT Nested Task Flag
15 0 予約
16 X RF Resume Flag
17 X VM Virtual-8086 Mode 仮想8086モード
18 X AC Alignment Check アライメント・チェック このフラグとCR0のAMフラグを1にセットすると、アライメント・チェックが有効になる。有効にすると、CPLが3のときのみアライメント・チェック例外を発生させることができる。
19 X VIF Virtual Interrupt Flag
20 X VIP Virtual Interrupt Pending
21 X ID Identification Flag 識別フラグ このビットを変更することができれば、CPUID命令を実行することができる。
22-31 0 予約

CR0

bit Flag 名前 説明
0 PE Protect Enable プロテクトモードを有効にする。
1 MP Monitor Coprocesser
2 EM Emulation
3 TS Task Switch
4 ET Extension Type 拡張タイプ。最近のCPUでは、1で予約されている。
5 NE Numerical Error
6-15 予約
16 WP Write Protect
17 予約
18 AM Alignment Mask このフラグとEFLAGSのACフラグを1にセットすると、アライメント・チェックが有効になる。有効にすると、CPLが3のときのみアライメント・チェック例外を発生させることができる。
19-28 予約
29 NW Not Write through
30 CD Cash Disable
31 PG Paging 1にセットすると、ページングを有効にする。PEフラグ(CR0-0)がセットされていないと、このフラグも有効にならない。

今日はまずメモリ管理を双方向リストではなく、線形リストに置き換えられないか検討する。 その後PITに触れていく。

メモリ管理構造体の線形リスト化

双方向リストにしてしまったのでめちゃくちゃ複雑になっている。もっと簡単に出来るはず。 →線形リストにした。おまけに、ハンドルを引数に操作するAPIを追加した。(システムはstaticなメモリ管理構造体を暗黙で使用する)

2017.12.16

12.9は大洗、12.10はプリパラwinter live、12.11は有給で満喫。

大洗は例のごとくあんこう鍋目当てで行ったけど、最初(2016年冬)に行った旅館が一番うまかったので合意。観光としては水族館に行ったり(水しぶきが凄い)、海を見たり(水平線が盛り上がって見えるのが大変綺麗)、海鮮市場でいいだけうまいものを食べたり(あんこう汁が一番うまかったかも。次いで磯前神社の道の向かいにある食道での生しらす丼か)。

プリパラwinter liveは、今年トップクラスに面白かった。メイキングドラマをほとんど省いて高速で曲が打ち込まれ、2時間半程度で32曲も出してきたのだ。。。WITHの盛り上がりは素晴らしいし、夢川ゆいちゃんはかわいいし、ノンシュガーは凄いし。。。いよいよプリパラに傾くときが来たのか。

PITによるタイマモジュール実装

タイマは最初から線形リストでの実装を行う。つまり、最も近いタイムアウト順に並べたキューを作成する。

#include "defines.h"
#include "asmfunc.h"
#include "interrupt.h"
#include "fifo.h"

/* PIT(Programmable Interval Timer)の設定ポート */
#define PIT_CONTROL                     0x0043
#define PIT_COUNT0                      0x0040

/* 8254の動作周波数=1193180Hz */
#define I8254_CLOCK_HZ                  1193180

/* 同時に管理可能なタイマの最大数 */
#define MAX_NUM_TIMER                   500                  /* ユーザが使用可能なタイマ数 */
#define MAX_NUM_INTERNAL_TIMER          (MAX_NUM_TIMER + 1)  /* システムが使用するタイマ数; 番兵の分を追加 */

/* タイマ管理モジュールの状態ビット */
#define TIMERCONTROL_FLAGS_INITIALIZED  (1 << 0) /* 初期化済みか? */

/* タイマの状態フラグビット */
#define TIMER_FLAGS_ALLOCED             (1 << 0) /* 領域が確保されているか? */
#define TIMER_FLAGS_WAITFORTIMEOUT      (1 << 1) /* タイムアウト待ち状態か? */

/* タイムアウトカウントの無効値 */
#define TIMER_INVALID_TIMEOUT_VALUE     0xFFFFFFFFFFFFFFFF

/* タイマの線形リスト */
struct Timer {
  struct Timer*   next;
  uint64_t        timeout;  /* タイムアウトまでのカウント */
  uint32_t        flags;    /* 状態フラグ */
  struct FIFO32*  fifo;     /* FIFO */
  uint32_t        data;     /* タイマを識別するデータ */
};

/* タイマ管理構造体 */
struct TimerControl {
  uint8_t         flags;                              /* 状態フラグ   */
  uint64_t        timer_count;                        /* カウント */
  uint32_t        timer_count_period_ms;              /* カウントの上昇周期[ms] */
  struct Timer*   timer_top;                          /* タイマリスト先頭 */
  struct Timer    timer_list[MAX_NUM_INTERNAL_TIMER]; /* タイマの領域 */
};

/* システムが管理するタイマ */
static struct TimerControl st_system_timer_control;

/* PITの割り込みハンドラ */
void pit_inthandler(int *esp)
{
  struct Timer*        p_timer;
  struct TimerControl* timerctl = &st_system_timer_control;

  /* 割込み完了を通知 */
  pic_pit_eoi();

  /* タイマのカウンタを更新 */
  timerctl->timer_count++;

  /* 次のタイムアウトまでは何もしない */
  if (timerctl->timer_count < timerctl->timer_top->timeout) {
    return;
  }

  /* タイムアウト時間を過ぎたタイマをまとめてタイムアウト */
  for (p_timer = timerctl->timer_top;
       p_timer->timeout <= timerctl->timer_count;
       p_timer = p_timer->next) {
    p_timer->flags &= ~TIMER_FLAGS_WAITFORTIMEOUT;
    FIFO32_PutData(p_timer->fifo, p_timer->data);
  }

  /* 先頭を更新 */
  timerctl->timer_top = p_timer;

}

/* タイマモジュール初期化 */
void TimerControl_Initialize(struct TimerControl* timerctl)
{
  uint32_t i_timer;
  struct Timer* timer_sentinel_p;
  
  /* 初期化済みの場合は何もせず即時リターン */
  if (timerctl == NULL
      || (timerctl->flags & TIMERCONTROL_FLAGS_INITIALIZED)) {
    return;
  }
  
  /* タイマ管理構造体を初期化 */
  timerctl->timer_count           = 0;
  timerctl->flags                 = 0;
  timerctl->timer_count_period_ms = 0; /* (仮) */

  /* 全タイマを初期化 */
  for (i_timer = 0; i_timer < MAX_NUM_INTERNAL_TIMER; i_timer++) {
    struct Timer* p_timer = &(timerctl->timer_list[i_timer]);
    p_timer->timeout = 0;
    p_timer->flags   = 0;
    p_timer->fifo    = NULL;
    p_timer->data    = 0;
  }

  /* 番兵を立てる */
  timer_sentinel_p = &(timerctl->timer_list[0]);
  timer_sentinel_p->timeout            = TIMER_INVALID_TIMEOUT_VALUE; /* タイムアウトは無効値 */
  timer_sentinel_p->flags             |= (TIMER_FLAGS_ALLOCED | TIMER_FLAGS_WAITFORTIMEOUT);
  timer_sentinel_p->next               = NULL;                        /* 必ず末尾 */

  /* 番兵を先頭にセット */
  timerctl->timer_top          = timer_sentinel_p;
  
    /* 初期化フラグを立てる */
  timerctl->flags |= TIMERCONTROL_FLAGS_INITIALIZED;
  
}

/* タイマモジュール終了 */
void TimerControl_Finalize(struct TimerControl* timerctl)
{
  uint32_t i_timer;

  /* 終了済みならば即時リターン */
  if (timerctl == NULL
      || !(timerctl->flags & TIMERCONTROL_FLAGS_INITIALIZED)) {
    return;
  }

  /* 全タイマを初期化 */
  for (i_timer = 0; i_timer < MAX_NUM_INTERNAL_TIMER; i_timer++) {
    struct Timer* p_timer = &(timerctl->timer_list[i_timer]);
    p_timer->timeout = 0;
    p_timer->flags   = 0;
    p_timer->fifo    = NULL;
    p_timer->data    = 0;
  }

  /* 初期化フラグを落とす */
  timerctl->flags &= ~TIMERCONTROL_FLAGS_INITIALIZED;
}

/* システムタイマモジュール終了 */
void SystemTimerControl_Finalize(void)
{
  TimerControl_Finalize(&st_system_timer_control);
}

/* システムタイマモジュール初期化 */
void SystemTimerControl_Initialize(uint32_t timer_interval_ms)
{
  /* 
   * 設定カウント値countの計算
   * 設定カウント値countと実際の割込み頻度f[Hz]は次の式を満たす:
   * f = I8254_CLOCK_HZ / count
   * よって count = I8254_CLOCK_HZ / f
   */
  uint16_t count = (I8254_CLOCK_HZ * timer_interval_ms) / 1000;

  /* 割込み周期設定 */
  io_out8(PIT_CONTROL, 0x34);
  io_out8(PIT_COUNT0,  count & 0xFF);         /* カウント値の下位8bit */
  io_out8(PIT_COUNT0,  (count >> 8) & 0xFF);  /* カウント値の上位8bit */

  /* システムのタイマ管理構造体を初期化 */
  TimerControl_Initialize(&st_system_timer_control);

  /* カウント周期を保存 */
  st_system_timer_control.timer_count_period_ms = timer_interval_ms;

  /* PITの割込み許可 */
  pic_pit_enableint();
}

/* 新規にタイマを割り当て */
struct Timer* TimerControl_AllocTimer(struct TimerControl* timerctl)
{
  uint32_t i_timer;

  /* モジュール初期化前は割り当て禁止 */
  if (timerctl == NULL
      || !(timerctl->flags & TIMERCONTROL_FLAGS_INITIALIZED)) {
    return NULL;
  }

  /* 割り当て済みでないタイマを探す */
  for (i_timer = 0; i_timer < MAX_NUM_INTERNAL_TIMER; i_timer++) {
    struct Timer* p_timer = &(timerctl->timer_list[i_timer]);
    
    if (!(p_timer->flags & TIMER_FLAGS_ALLOCED)) {
      p_timer->flags |= TIMER_FLAGS_ALLOCED;
      return p_timer;
    }
  }

  /* 見つからなかった */
  return NULL;
}

struct Timer* SystemTimerControl_AllocTimer(void)
{
  return TimerControl_AllocTimer(&st_system_timer_control);
}

void TimerControl_SetTimeout(struct TimerControl* timerctl, struct Timer* timer, uint64_t timeout)
{
  uint32_t eflags;
  struct Timer* p_timer;

  /* 引数チェック. 0カウント後のタイムアウトは許容しない */
  if (timer == NULL || timeout == 0) {
    return;
  }

  /* タイマ管理構造体からの相対時間でタイムアウトを設定 */
  timer->timeout = timeout + timerctl->timer_count;
  
  /* EFLAGSを退避 */
  eflags = io_load_eflags();
  /* 割り込み禁止 */
  io_cli();

  /* リストの先頭に入れる */
  if (timer->timeout <= timerctl->timer_top->timeout) {
    timer->next                  = timerctl->timer_top;   
    timerctl->timer_top          = timer;
    goto EXIT;
  }

  /* リストの挿入位置を探索 */
  for (p_timer = timerctl->timer_top;
       p_timer->next != NULL;
       p_timer = p_timer->next) {
    if (timer->timeout > p_timer->timeout
        && timer->timeout <= p_timer->next->timeout) {
      timer->next = p_timer->next;
      p_timer->next = timer;
      goto EXIT;
    }
  }
  
EXIT:
  /* 待ち状態にセット */
  timer->flags  |= TIMER_FLAGS_WAITFORTIMEOUT;  
  /* EFLAGSを復帰 */
  io_store_eflags(eflags);
}

void SystemTimerControl_SetTimeout(struct Timer* timer, uint64_t timeout)
{
  TimerControl_SetTimeout(&st_system_timer_control, timer, timeout);
}

/* タイマを未割り当て状態に */
void Timer_Free(struct Timer* timer)
{
  if (timer == NULL) {
    return;
  }

  /* 一旦破棄したタイマが再割り当て後に即時に動き始めるのを防ぐため、
   * タイムアウト待ち状態フラグも落とす */
  timer->flags &= ~(TIMER_FLAGS_ALLOCED | TIMER_FLAGS_WAITFORTIMEOUT);
}

void Timer_Initialize(struct Timer* timer, struct FIFO32* fifo, uint32_t data)
{
  if (timer == NULL) {
    return;
  }

  timer->fifo = fifo;
  timer->data = data;
}

メモリモジュールの時と同じく、システム用のタイマ構造体をstaticに確保している:

/* システムが管理するタイマ */
static struct TimerControl st_system_timer_control;

システムとしては、このstaticなタイマ管理構造体を介して処理を行う。 他は自作OS本と比べて目新しい所は無いかも…番兵を立てた線形リストが分かりやすいし、簡潔だし、それなりに早いので…。

今日は案外早くキリが付いた。残件としては、

  • システムが使用するFIFOの共通化
  • →少し重め。新しくヘッダを切って(もしくはdefines.hか?)、どの整数番号がどのメッセージに該当するのか特定できるようにすべし。
  • もしくは、メッセージキューを作る?←最終的にこっちに持っていきたい。メッセージタイプとその実体(union)からなる構造体。
  • マウスとキーボードデータのデコード
  • →こっちをやる。
  • やってて気付いたけど、デコードしたデータをFIFOに載せる事を考えると、やっぱりFIFOでメッセージを飛ばしたくなる。上の対応がなるべく早く求められる。
  • マウスのデコーダはやっつけ実装。
  • キーボードは、コードから文字の変換テーブルだけ作成。まだled(CapsLock, NumLock, ScrollLock)とかに対応できていない。344p周辺を読まねば。
  • テストでポート入出力命令を使えるようにする
  • コンソールのテストを行うのに必須。擬似的にやれないか探る。
  • io_in系は内部的なFIFOを使うのが良さそう。予めinされる予定のデータをFIFOに詰めておいて、io_inを呼び出すと順番に出てくるイメージ。
  • io_out系は関数ポインタが良さそう。データ毎に場合分けをした処理を行う。関数ポインタを書き換えることで対応を変える。

メッセージキューの置き換えまでできたら、次はマルチタスクか。実際着手できるのはいつになるのか…