--- nono/vm/scheduler.cpp 2026/04/29 17:04:50 1.1.1.8 +++ nono/vm/scheduler.cpp 2026/04/29 17:05:03 1.1.1.12 @@ -12,24 +12,161 @@ #include "mpu88xx0.h" #include "mythread.h" #include "rtc.h" +#include "uimessage.h" #include "vm.h" // VM スケジューラは、仮想時間とイベントを管理する。 // -// 仮想時間は起動時からの nsec で表し、MPU デバイス (実際はその向こうの +// 仮想時間は電源オン時からの nsec で表し、MPU デバイス (実際はその向こうの // CPU コア) が管理している。VM 界からは gMPU->GetVirtTime() で取得する。 +// +// 実時間はストップウォッチ gRealtime を基準とし、こちらも nsec で表す。 +// このストップウォッチ自体は Scheduler 初期化時から常に単調増加する。 +// 電源オフ中でも RTC へのパルス供給のために単調増加し続けることに注意。 +// +// 「実経過時間」は、GetRealTime() (中身は gRealtime.Elapsed()) で取得でき +// o (たぶん)アプリケーション起動時から、常に増加。 +// o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。 +// o VM 電源オフ時でも進行する。 +// o デバッガプロンプトで停止中は進行が停止する。 +// という性質を持つ。 +// 「仮想経過時間」は、gMPU->GetVirtTime() で取得でき +// o 電源オン時のみ有効。 +// o (今のところ) MPU の命令境界で増加する +// という性質を持つ。 +// +// 同期走行モードにおいては rtime_epoch, vtime_epoch をそれぞれの基準点とし +// これらと現在の実経過時間、仮想経過時間の差が同期するよう調整する。 +// 高速走行モードにおいてはこれらは必要ないので特に参照しない。 +// +// 一定間隔で行うパフォーマンス測定は、前回計測時の実および仮想経過時間を +// last_perf_rtime, last_perf_vtime として持っているので、これと現在の +// 経過時間との比で求める。同期走行/高速走行の影響は受けない。 static void *scheduler_run(void *arg); std::unique_ptr gScheduler; +// パフォーマンス測定を行う実時間間隔 +static constexpr uint64 PERF_INTERVAL = 1000_msec; + +// 実時間の基準となるストップウォッチ。 +Stopwatch gRealtime; + +// 実経過時間 [nsec] を返す +static inline uint64 GetRealTime() +{ + return gRealtime.Elapsed(); +} + +#define FORMAT_FULL 0 +#define FORMAT_SEC 1 + +// 経過時間 t を文字列にして返す。 +// 文字列長は、t の大きさ(とフォーマット指定)によって以下の通り。 +// +// 0 1 2 +// 01234567890123456789012345 +// "0.mmm'uuu'nnn" 10秒未満、または FORMAT_SEC なら13桁 +// "59.mmm'uuu'nnn" 1分未満なら14桁 +// " 9:59.mmm'uuu'nnn" 1時間未満なら17桁 +// " 9:59:59.mmm'uuu'nnn" 24時間未満なら20桁 +// "999d 23:59:59.mmm'uuu'nnn" 1日以上なら25桁。これが最大幅。 +// +// FORMAT_SEC は1桁秒以下の場合 (実際には1秒未満の場合) に用いる。 +// FORMAT_FULL は 1000日経過すると桁がずれるけど、それはもういいだろう。 +// +// 10秒未満の場合だけ %2d ではなく %d で1桁切り詰めているが、これは +// FORMAT_SEC との互換性のため。その必要のない10分未満と10時間未満は +// どちらも %2d で表記し桁数を維持することに努める。 +static const std::string +TimeToStrF(uint64 t, int format) +{ + char buf[32]; + char *p; + size_t len; + int n; + + uint ns = t % 1000; + t /= 1000; + uint us = t % 1000; + t /= 1000; + uint ms = t % 1000; + t /= 1000; + + uint s, m, h, d; + if (format == FORMAT_FULL) { + s = t % 60; + t /= 60; + m = t % 60; + t /= 60; + h = t % 24; + t /= 24; + d = t; + } else { + s = t; + m = 0; + h = 0; + d = 0; + } + + p = buf; + len = sizeof(buf); + if (d) { + n = snprintf(p, len, "%3dd %02u:%02u:%02u", d, h, m, s); + p += n; + len -= n; + } else if (h) { + n = snprintf(p, len, "%2u:%02u:%02u", h, m, s); + p += n; + len -= n; + } else if (m) { + n = snprintf(p, len, "%2u:%02u", m, s); + p += n; + len -= n; + } else { + n = snprintf(p, len, "%u", s); + p += n; + len -= n; + } + n = snprintf(p, len, ".%03u'%03u'%03u", ms, us, ns); + p += n; + len -= n; + + return std::string(buf, p - buf); +} + +const std::string +TimeToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_FULL); +} + +static const std::string +SecToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_SEC); +} + +// +// スケジューラ +// + // コンストラクタ Scheduler::Scheduler() + : inherited("Scheduler") { - logname = "scheduler"; - devname = "Scheduler"; + for (int i = 0; i < perfq.Capacity(); i++) { + perfq.Enqueue(1000); + } + + sync_event.func = (DeviceCallback_t)&Scheduler::SyncCallback; + sync_event.time = 50_msec; + sync_event.SetName("Scheduler Sync"); - eventlist.clear(); + monitor.func = (MonitorCallback_t)&Scheduler::MonitorUpdate; + // サイズは Init で決まる + monitor.Regist(ID_MONITOR_SCHEDULER); } // デストラクタ @@ -42,6 +179,9 @@ Scheduler::~Scheduler() bool Scheduler::Init() { + // この時点でイベントの個数は確定しているはず(してなければならない) + monitor.SetSize(72, 10 + gEvents.size()); + // 起動時引数指定の高速モード SetFullSpeed(gMainApp.fast_mode); @@ -52,15 +192,6 @@ Scheduler::Init() return true; } -// スケジューラを開始する -void -Scheduler::Run() -{ - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_POWER_ON; - cv.notify_one(); -} - // スレッドエントリ関数 void * scheduler_run(void *arg) @@ -75,35 +206,132 @@ scheduler_run(void *arg) void Scheduler::ThreadRun() { - uint32 req; + // ここが rtime の基準点。 + // Stopwatch.Restart() は .Net 由来なので + // Restart() は経過時間をリセットしてからタイマーを開始の意。 + gRealtime.Restart(); + rtime = 0; + next_rtc_rtime = 31.25_msec; - // 最初の電源オンを待つ for (;;) { - std::unique_lock lock(cvmtx); - cv.wait(lock, [&] { return (atomic_reqflag != 0); }); - req = atomic_reqflag.load(); - // 終了 - if ((req & REQ_EXIT)) { + if (RunOff() == false) + return; + if (RunOn() == false) return; + } +} + +// 電源オフ時のスケジューラメインループ。 +// false ならこのスレッドの終了指示。 +bool +Scheduler::RunOff() +{ + uint32 oldreq; + + ispower = false; + + oldreq = -1; + for (;;) { + // RunOn() と違ってこっちは処理したフラグだけを下ろす。 + + // デバッグ表示も前回から変化した時だけ。 + uint32 req = atomic_reqflag.load(); + if (__predict_false(req != oldreq)) { + if (loglevel >= 2) { + PutlogReqFlag(req); + } + oldreq = req; } - // REQ_POWER_ON ならこれだけ下げて抜ける。 - // 他のフラグはメインループで処理するため維持。 - if ((req & REQ_POWER_ON)) { - req &= ~REQ_POWER_ON; - atomic_reqflag.store(req); - break; + + // 終了 + if ((atomic_reqflag & REQ_EXIT)) { + return false; } + + if ((atomic_reqflag & REQ_RESTART)) { + // 電源オフ中に RESTART は通常来るはずはないが、来てしまったら + // メニューから RESTART 操作中に VM が電源オフしてしまったのかも + // 知れないので、単に電源を入れる。 + atomic_reqflag &= ~REQ_RESTART; + atomic_reqflag |= REQ_POWER_ON; + } + + // 電源オン指示 + if ((atomic_reqflag & REQ_POWER_ON)) { + // 電源オン要求の場合 + + // 全デバイス電源オン + for (const auto& d : gDevices) { + if (!d->PowerOn()) { + return false; + } + } + + // 続いて全デバイスをリセット + for (const auto& d : gDevices) { + d->ResetHard(); + } + + // POWER_ON は処理したので下ろす。他のフラグは触らない。 + atomic_reqflag &= ~REQ_POWER_ON; + // 電源オンは RESET も兼ねる (RESET は RunOn() 内で行う) + atomic_reqflag |= REQ_RESET; + + return true; + } + + // RTC はホスト時間で 32Hz (31.25msec) ごとにカウントアップするが、 + // usleep() はそこまでの精度がないことと、RunRTC() は前回カウント + // アップ時刻からの差で処理しており入力が多少ぶれても構わないので、 + // 適当にその周期の半分強で sleep してみる。 + usleep(16_msec / 1_usec); + + // RTC をホスト時間で進める。 + // (電源オンするまでは gRealTime は進み続けている) + rtime = GetRealTime(); + RunRTC(); } - // ここからは電源オン + __unreachable(); +} - // RTC 時刻だけは最初から必要。 - rtimestart = GetRealTime(); - rtc_last_clock = rtimestart; +// 電源オン時のスケジューラメインループ。 +// false ならこのスレッドの終了指示。 +bool +Scheduler::RunOn() +{ + uint64 last_event_vtime; // 直近のイベント発生時刻 (統計用) + Statistics event_stat; + uint64 last_sync_rtime; + + ispower = true; + + // パフォーマンス計測は電源オン時のみなので、ここで初期化 + last_perf_rtime = rtime; + last_perf_vtime = gMPU->GetVirtTime(); + next_perf_rtime = rtime + PERF_INTERVAL; + // 最初の集計が行われる起動後1秒までは、この値が表示されるので + // インチキして 100% にしておく。 + perf_counter = 100; + + last_event_vtime = last_perf_vtime; + + // 実時間系の初期化 + rtime = GetRealTime(); + last_sync_rtime = 0; + overslept = 0; + sync_event.Start(); - mode = 0; for (;;) { - req = atomic_reqflag.exchange(0); + vtime = gMPU->GetVirtTime(); + + uint32 req = atomic_reqflag.exchange(0); if (__predict_false(req != 0)) { + + // デバッグ表示 + if (loglevel >= 2) { + PutlogReqFlag(req); + } + uint32 newmode = mode; // 動作モード変更要因を一つずつ調べる @@ -119,75 +347,154 @@ Scheduler::ThreadRun() newmode |= new_usermode; req &= ~REQ_USER_MODE; } - // 動作モード変更要因を全部調べた結果、変更が必要か。 - // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か - // なので、等値比較ではなく二値にしてから比較。 - if ((bool)newmode != (bool)mode) { - // 動作モード変更 (ここは全ビットを維持して代入) + if ((req & REQ_BOOT_MODE)) { + // ブートページが変更になった + newmode &= ~SCHED_BOOT; + newmode |= new_bootmode; + req &= ~REQ_BOOT_MODE; + } + if ((req & REQ_KEY_MODE)) { + // キー入力中かどうか + newmode &= ~SCHED_KEY; + newmode |= new_keymode; + req &= ~REQ_KEY_MODE; + } + + // いずれかでも変化していれば.. + if (mode != newmode) { + // 動作モード変更要因を全部調べた結果、変更が必要か。 + // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か + // なので、等値比較ではなく二値にしてから比較。 + if ((bool)mode != (bool)newmode) { + // モードが変わるのでここで一旦同期をとる + auto over = Sync(); + if (overslept < over) { + overslept = over; + } + + if (newmode == 0) { + // 高速モードに + putlog(1, "高速モード"); + + // 同期モードを抜けた時刻 + last_sync_rtime = rtime; + + // 高速モードでは RAM は直接アクセス + ::direct_ram_size = gRAM->GetSize(); + + } else { + // 同期モードに + putlog(1, "同期モード"); + + // 同期モードでは RAM アクセスはデバイス経由 + ::direct_ram_size = 0; + + // 同期用の基準点を設置。 + // ゲストが STOP 命令による定常状態にいる時は + // 割り込みなど短期間だけ高速モードになるのを繰り返す。 + // スリープ粒度の粗い NetBSD ホストでは同期モードに + // 切り替えるたびに基準点を移動していると、スリープ + // しすぎてしまうので、短期間だけ高速モードを出入り + // した分は基準点を移動せず無視する。 + if (rtime - last_sync_rtime > overslept) { + rtime_epoch = rtime; + vtime_epoch = vtime; + } + } + } + + // ブール比較の後で、全ビットを維持したまま代入。 mode = newmode; + } - if (mode == 0) { - // 高速モードに - putlog(1, "高速モード"); - - // 高速モードでは RAM は直接アクセス - ::direct_ram_size = gRAM->GetSize(); - } else { - // 同期モードに - putlog(1, "同期モード"); + if ((req & REQ_RESTART)) { + // 電源オン中に RESTART が来たら電源オンを予約して電源オフ。 + // UIMessage::POWEROFF は GUI にアプリケーションを終了させる + // ためのものなのでここでは呼ばない。 + atomic_reqflag |= REQ_POWER_ON; - // 同期モードでは RAM アクセスはデバイス経由 - ::direct_ram_size = 0; + // 全デバイス電源オフ + for (const auto& d : gDevices) { + if (!d->PowerOff()) { + return false; + } } - // 基準時刻をリセット - rtimebase = ::GetRealTime(); - vtimebase = gMPU->GetVirtTime(); + return true; } if ((req & REQ_POWER_OFF)) { // 電源オフ要求の場合 // 全デバイス電源オフ - gVM->DevicePowerOff(); + for (const auto& d : gDevices) { + if (!d->PowerOff()) { + return false; + } + } + + // GUI に通知 + UIMessage::Post(UIMessage::POWEROFF); - req &= ~REQ_POWER_OFF; + return true; } if ((req & REQ_RESET)) { // リセット要求の場合 // 全デバイスをリセット - gVM->ResetHard(); + for (const auto& d : gDevices) { + d->ResetHard(); + } req &= ~REQ_RESET; } if ((req & REQ_EXIT)) { // 終了要求 - // この for ブロックをいきなり抜けるだけなので - // もうフラグの値も関係ないのだが一応。 - req &= ~REQ_EXIT; - break; + return false; } - // 電源オン要求は来ないはず + // これいるかな? assertmsg(req == 0, "req=$%x", req); } - // RTC は常にホスト時刻で動いており - // 32Hz = 31.25msec ごとにパルスを入れる。 - while (::GetRealTime() > rtc_last_clock + 31.25_msec) { - gRTC->ClockIn(); - rtc_last_clock += 31.25_msec; + // パフォーマンス表示 + if (__predict_false(rtime >= next_perf_rtime)) { + uint64 rperf = rtime - last_perf_rtime; + uint64 vperf = vtime - last_perf_vtime; + + // 百分率で小数以下1桁分までとなるよう整数で計算する + perfq.EnqueueForce((uint32)(vperf * 1000 / rperf)); + // 移動平均を求める (perfq は常に要素数分埋めてある) + int ma = 0; + int qlen = perfq.Length(); + for (int i = 0; i < qlen; i++) { + auto v = perfq.Peek(i); + ma += v; + } + ma /= qlen; + // 最下位を四捨五入して捨てる (整数%になる) + ma = (ma + 5) / 10; + perf_counter = ma; + + // 直近1秒の Sync 回数 + last_sync_count = sync_count; + sync_count = 0; + + // 直近1秒のイベント統計 + last_event_stat = event_stat; + event_stat.Clear(); + + last_perf_rtime = rtime; + last_perf_vtime = vtime; + next_perf_rtime = rtime + PERF_INTERVAL; } // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 // 時間を求めて、この時間分だけ MPU を駆動する。 // MPU 実行中にデバイスアクセスなどによってスケジューラにイベントが // 登録されると、その命令の終了とともに処理を打ち切って戻ってくる。 - // また、電源オン(リセット)時には MPU デバイスが 0nsec で MPU の - // リセット例外イベントを起こすようになっている。 // delta_vtime が今回進める仮想時間。 // とりあえず初期値を仮置きしとくけど、イベントは通常 20msec 以内には @@ -198,17 +505,16 @@ Scheduler::ThreadRun() uint64 delta_vtime = 0x200'0000; // イベント - uint64 vtime = gMPU->GetVirtTime(); for (;;) { Event *e; // ロック区間 { std::lock_guard lock(evcs); - if (eventlist.empty()) { + if (eventq.empty()) { break; } - e = eventlist.front(); + e = eventq.top(); if (e->vtime > vtime) { // まだ時刻に到達していない delta_vtime = e->vtime - vtime; @@ -217,9 +523,15 @@ Scheduler::ThreadRun() // 到達したのでこのイベントをリストから削除 e->active = false; - eventlist.pop_front(); + eventq.pop(); } + // イベント発生間隔 (統計情報) + // t は前回と今回のイベント予定時刻の間隔。 + uint64 t = e->vtime - last_event_vtime; + event_stat.Add(t); + last_event_vtime = e->vtime; + // コールバック e->dev->putlogf(3, lstr("イベント '%s' 時刻到達", e->GetName().c_str())); @@ -228,42 +540,84 @@ Scheduler::ThreadRun() // 同時刻のイベントがあるかも知れないのでなくなるまで調べる } + // RTC をホスト時間で進める + // (同期イベントによって sleep したかもしれないので、その後がよかろう) + RunRTC(); + // CPU を駆動 new_cpumode = gMPU->Run(delta_vtime); - // CPU 駆動によって進んだ仮想時刻を再取得 - vtime = gMPU->GetVirtTime(); + // CPU 側の走行状態が変わったら、このループに通知 + if (__predict_false(((mode ^ new_cpumode) & SCHED_CPU_MASK))) { + atomic_reqflag |= REQ_CPU_MODE; + } + } + __unreachable(); +} - // 同期モードなら実時間調整。 - // あるいは高速モードであってもストップ状態なら実時間駆動する。 - if (mode != 0) { - // 通常モード - uint64 vspan; - uint64 rspan; - - // 基準時からの経過時間 - vspan = vtime - vtimebase; - rspan = GetRealTime() - rtimebase; - - if (vspan > rspan) { - // 仮想時間のほうが進んでいれば、スリープして待つ - uint64 diff = vspan - rspan; - struct timespec ts; - ts.tv_sec = diff / (1000 * 1000 * 1000); - ts.tv_nsec = diff % (1000 * 1000 * 1000); - nanosleep(&ts, NULL); - } else { - // 実時間のほうが進んでいれば、間に合ってない +// 同期イベント +void +Scheduler::SyncCallback(Event& ev) +{ + Sync(); + + // 次回のイベントを再登録 + ev.Start(); +} - // XXX ここで間引き運転とか +// 同期処理 +// ここで仮想時間を実時間と同期させる。 +// スリープしすぎた実時間を返す。 +uint64 +Scheduler::Sync() +{ + uint64 over = 0; + + // 実時間をここで更新 + rtime = GetRealTime(); + + // 統計情報 + sync_count++; + + // 同期モードなら.. + if (mode != 0) { + uint64 rspan = rtime - rtime_epoch; + uint64 vspan = vtime - vtime_epoch; + if (vspan > rspan) { + // 仮想時間のほうが進んでいれば、スリープして待つ + uint64 diff = vspan - rspan; + struct timespec ts; + ts.tv_sec = diff / (1000 * 1000 * 1000); + ts.tv_nsec = diff % (1000 * 1000 * 1000); + nanosleep(&ts, NULL); + + // スリープしすぎた時間を計測。 + // この時間にはスリープ時間を計算するための時間も含む。 + uint64 prev = rtime; + rtime = GetRealTime(); + uint64 realslept = rtime - prev; + if (realslept > diff) { + over = realslept - diff; } - } + } else { + // 実時間のほうが進んでいれば、間に合ってない - // CPU 側の走行状態が変わったら、このループに通知 - if (__predict_false(((mode ^ new_cpumode) & SCHED_CPU_MASK))) { - atomic_reqflag |= REQ_CPU_MODE; + // XXX ここで間引き運転とか } } + return over; +} + +// RTC は常にホスト時刻で動いており +// 32Hz = 31.25msec ごとにパルスを入れる。 +// VM の電源がオフでも実行する。 +void +Scheduler::RunRTC() +{ + while (rtime >= next_rtc_rtime) { + gRTC->ClockIn(); + next_rtc_rtime += 31.25_msec; + } } // 必要ならスレッドの終了を指示して完了まで待つ。 @@ -272,6 +626,15 @@ Scheduler::Terminate() { if (thread_created) { RequestExit(); + Wait(); + } +} + +// スレッドの完了まで待つ。 +void +Scheduler::Wait() +{ + if (thread_created) { pthread_join(thread, NULL); thread_created = false; } @@ -281,9 +644,7 @@ Scheduler::Terminate() void Scheduler::RequestExit() { - std::unique_lock lock(cvmtx); atomic_reqflag |= REQ_EXIT; - cv.notify_one(); } // 動作モードを設定する。 @@ -299,6 +660,80 @@ Scheduler::SetFullSpeed(bool enable) atomic_reqflag |= REQ_USER_MODE; } +// ブートページモードを設定する。 +// 戻り値は現在の動作モードがブートモード(ROM)なら true。 +// (VM::SwitchBootPage から呼ぶので個別には呼ばないこと) +void +Scheduler::RequestBootPageMode(bool isrom) +{ + if (isrom) { + new_bootmode = SCHED_BOOT; + } else { + new_bootmode = 0; + } + atomic_reqflag |= REQ_BOOT_MODE; +} + +// キー入力中かどうかを設定する。 +// true ならキー入力中、false なら入力中でない。 +void +Scheduler::RequestKeyPressed(bool pressed) +{ + if (pressed) { + new_keymode = SCHED_KEY; + } else { + new_keymode = 0; + } + atomic_reqflag |= REQ_KEY_MODE; +} + +// REQ_* フラグのデバッグ表示 +void +Scheduler::PutlogReqFlag(uint32 req) const +{ + static const char * const cpumode_str[] = { + "Normal", + "STOP", + "HALT", + "HALTXXX", + }; + char buf[128]; + int len = 0; + + buf[0] = '\0'; + +#define SET(FL, MSG) \ + if ((req & (FL))) len += snprintf(buf + len, sizeof(buf) - len, ",%s", MSG) + + SET(REQ_EXIT, "EXIT"); + SET(REQ_RESTART, "RESTART"); + SET(REQ_RESET, "RESET"); + SET(REQ_POWER_OFF, "POWER_OFF"); + SET(REQ_POWER_ON, "POWER_ON"); + if ((req & REQ_USER_MODE)) { + len += snprintf(buf + len, sizeof(buf) - len, ",USER:%s", + (new_usermode == 0) ? "Fast" : "Sync"); + } + if ((req & REQ_CPU_MODE)) { + len += snprintf(buf + len, sizeof(buf) - len, ",CPU:%s", + cpumode_str[new_cpumode & 3]); + } + if ((req & REQ_BOOT_MODE)) { + len += snprintf(buf + len, sizeof(buf) - len, ",BOOT:%s", + (new_bootmode == 0) ? "Normal" : "Boot"); + } + if ((req & REQ_KEY_MODE)) { + len += snprintf(buf + len, sizeof(buf) - len, ",KEY:%s", + (new_keymode == 0) ? "-" : "Pressed"); + } + + if (buf[0] != '\0') { + buf[0] = '<'; + strlcat(buf, ">", sizeof(buf)); + } + putlogn("%sreq=$%04x%s", (ispower ? "" : "Off "), req, buf); +} + // 指定のイベントを開始する。 // すでに同イベントが登録されている場合は古いイベントを削除してから // 新しいイベントを再登録となる。 @@ -306,42 +741,25 @@ Scheduler::SetFullSpeed(bool enable) void Scheduler::StartEvent(Event *newev) { - bool inserted = false; bool updated = false; // time は仮想相対時間 [nsec]、vtime は仮想絶対時刻 [nsec] newev->vtime = gMPU->GetVirtTime() + newev->time; evcs.lock(); - // すでにあれば削除 + // すでにあれば削除して... if (newev->active) { - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (e == newev) { - eventlist.erase(it); - updated = true; - break; - } - } - } - // ソートされているところに自身を挿入 - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (newev->vtime <= e->vtime) { - // このイベントの前に入れる - eventlist.insert(it, newev); - inserted = true; - break; - } - } - if (inserted == false) { - // 最後に追加 - eventlist.push_back(newev); + updated = eventq.Erase(newev); } + // 自身を追加 + eventq.push(newev); newev->active = true; - evcs.unlock(); - gMPU->Release(); + // 先頭なら再スケジュールのため CPU 実行をここで打ち切り + if (eventq.top() == newev) { + gMPU->Release(); + } + evcs.unlock(); newev->dev->putlogf(3, lstr("イベント '%s' %s %d.%03d usec 後", newev->GetName().c_str(), @@ -358,16 +776,9 @@ Scheduler::StopEvent(Event *event) bool found = false; evcs.lock(); - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (e == event) { - e->active = false; - eventlist.erase(it); - found = true; - break; - } - } + found = eventq.Erase(event); evcs.unlock(); + event->active = false; // イベントを停止した場合は MPU の実行中断はしなくてよい。 // 1msec 後にイベントを追加した後、やっぱりそのイベントを取り消した @@ -380,119 +791,86 @@ Scheduler::StopEvent(Event *event) } } -// 経過時間 t を文字列にして返す。 -static const std::string -TimeToStr(uint64 t) -{ - char buf[32]; - char *p; - size_t len; - int n; - - uint ns = t % 1000; - t /= 1000; - uint us = t % 1000; - t /= 1000; - uint ms = t % 1000; - t /= 1000; - uint s = t % 60; - t /= 60; - uint m = t % 60; - t /= 60; - uint h = t; - - p = buf; - len = sizeof(buf); - if (h) { - n = snprintf(p, len, "%d:%02d:%02d", h, m, s); - p += n; - len -= n; - } else if (m) { - n = snprintf(p, len, "%d:%02d", m, s); - p += n; - len -= n; - } else { - n = snprintf(p, len, "%d", s); - p += n; - len -= n; - } - n = snprintf(p, len, ".%03d'%03d'%03d", ms, us, ns); - p += n; - len -= n; - - return std::string(buf, p - buf); -} - -// モニターサイズ取得 -nnSize -Scheduler::GetMonitorSize() -{ - // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 - // そのタイミングで一度だけ呼ばれるフックが今の所ない。 - return nnSize(68, 11 + gEvents.size()); -} - void -Scheduler::MonitorUpdate(TextScreen& monitor) +Scheduler::MonitorUpdate(Monitor *, TextScreen& screen) { int x, y; - monitor.Clear(); + screen.Clear(); x = 0; y = 0; int clock_khz = gMPU->GetClockSpeed(); - monitor.Print(0, y, "Mode: 0x%04x (", mode); + screen.Print(0, y, "Mode: 0x%04x (", mode); if ((mode & SCHED_SYNC)) { - monitor.Puts(14, y, TA::On, "Sync"); + screen.Puts(14, y, TA::On, "Sync"); } else { - monitor.Puts(14, y, "Full"); + screen.Puts(14, y, TA::Off, "Full"); } switch (mode & SCHED_CPU_MASK) { case SCHED_CPU_NORMAL: - monitor.Puts(19, y, "Run"); + screen.Puts(19, y, TA::Off, "Run"); break; case SCHED_CPU_STOP: - monitor.Puts(19, y, TA::On, "STOP"); + screen.Puts(19, y, TA::On, "STOP"); break; case SCHED_CPU_HALT: - monitor.Puts(19, y, TA::On, "HALT"); + screen.Puts(19, y, TA::On, "HALT"); break; } - monitor.Puts(23, y, ")"); + screen.Puts(24, y, TA::OnOff((mode & SCHED_BOOT)), "Boot"); + screen.Puts(29, y, TA::OnOff((mode & SCHED_KEY)), "Key"); + screen.Puts(33, y, ")"); y++; - monitor.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); - monitor.Print(0, y++, "MPU Speed: %d.%03dMHz", + screen.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); + screen.Print(0, y++, "MPU Speed: %d.%03dMHz", (clock_khz / 1000), (clock_khz % 1000)); - uint64 vtime = gMPU->GetVirtTime(); - uint64 rtime = GetRealTime(); - uint64 vspan = vtime - vtimebase; - uint64 rspan = rtime - rtimebase; - // RealTime は実時間なのでスケジューラ開始からの時間に変換 - rtime -= rtimestart; - monitor.Print(0, y++, "Total Real Time: %18s", TimeToStr(rtime).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(vtime).c_str()); - double ratio = (double)vtime / rtime * 100; - monitor.Print(0, y++, " Ratio : %3d.%01d%%", - (int)ratio, ((int)(ratio * 10) % 10)); - monitor.Print(0, y++, "Moment Real Time: %18s", TimeToStr(rspan).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(vspan).c_str()); - // XXX 移動平均をとる - double relratio = (double)vspan / rspan * 100; - monitor.Print(0, y++, " Ratio : %3d.%01d%%", - (int)relratio, ((int)(relratio * 10) % 10)); + uint64 rt = rtime; + uint64 vt = vtime; + + auto s_rt = TimeToStr(rt); + auto s_vt = TimeToStr(vt); + std::string s_sync_rt {}; + std::string s_sync_vt {}; + if (mode != 0) { + uint64 sync_rt = rtime - rtime_epoch; + uint64 sync_vt = vtime - vtime_epoch; + s_sync_rt = TimeToStr(sync_rt); + s_sync_vt = TimeToStr(sync_vt); + } + // 日 まで表示しようとするときは桁位置を動かす + int width = std::max(rt, vt) < 86400_sec ? 20 : 25; + + screen.Print(0, y++, "Total Real Time: %*s", width, s_rt.c_str()); + screen.Print(4, y++, "Virtual Time: %*s", width, s_vt.c_str()); + screen.Print(0, y++, "Sync Real Time: %*s", width, s_sync_rt.c_str()); + screen.Print(4, y++, "Virtual Time: %*s", width, s_sync_vt.c_str()); + + screen.Print(0, y++, "Performance : %4d%%", perf_counter); + + x = 44; + y = 0; + screen.Print(x, y++, "Max OverSlept: %13s", SecToStr(overslept).c_str()); + screen.Print(x, y++, "Sync per sec : %4u", last_sync_count); + screen.Print(x, y++, "Event per sec: %8u", + (uint)last_event_stat.Count()); + screen.Print(x, y++, "Evt Intvl Min: %13s", + SecToStr(last_event_stat.Min()).c_str()); + screen.Print(x, y++, "Evt Intvl Avg: %13s", + SecToStr(last_event_stat.Average()).c_str()); + screen.Print(x, y++, "Evt Intvl Max: %13s", + SecToStr(last_event_stat.Max()).c_str()); // 0 1 2 3 4 5 6 - // 0123456789012345678901234567890123456789012345678901234567890123456 - // Set Time Remain Time Description Code - // 3.123'456'789 3.123'456'789 0123456789012345678901234 $01234567 + // 01234567890123456789012345678901234567890123456789012345678901234568 + // Set Time Remain Time Description + // 3.123'456'789 3.123'456'789 012345678901234567890123456789 x = 0; - y++; - monitor.Puts(x, y, "Set Time"); - monitor.Puts(x + 16, y, "Remain Time"); - monitor.Puts(x + 32, y, "Description"); - monitor.Puts(x + 58, y, "Code"); + y = 9; + screen.Puts(x + 0, y, "Event Time"); + screen.Puts(x + 15, y, "Remain Time"); + screen.Puts(x + 30, y, "Description"); y++; for (const auto& e : gEvents) { @@ -513,17 +891,10 @@ Scheduler::MonitorUpdate(TextScreen& mon attr = TA::Disable; rem = 0; } - monitor.Print(x, y++, attr, - "%3u.%03u'%03u'%03u %3u.%03u'%03u'%03u %-25s $%08x", - (uint)(e->time / (1000 * 1000 * 1000)), - (uint)((e->time / 1000 / 1000) % 1000), - (uint)((e->time / 1000) % 1000), - (uint)(e->time % 1000), - (uint)(rem / (1000 * 1000 * 1000)), - (uint)((rem / 1000 / 1000) % 1000), - (uint)((rem / 1000) % 1000), - (uint)(rem % 1000), - e->GetName().c_str(), - e->code); + screen.Print(x, y++, attr, + "%13s %13s %-30s", + SecToStr(e->time).c_str(), + SecToStr(rem).c_str(), + e->GetName().c_str()); } }