--- nono/vm/scheduler.cpp 2026/04/29 17:04:50 1.1.1.8 +++ nono/vm/scheduler.cpp 2026/04/29 17:05:14 1.1.1.15 @@ -4,299 +4,274 @@ // Licensed under nono-license.txt // +// +// スケジューラ +// + +// VM スケジューラは、仮想時間とイベントを管理する。 +// 仮想時間はすべて nsec で表し、StartTime() から単調増加する。 +// VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。 +// +// 実時間は Sync が管理し、こちらも nsec で表す。 +// このストップウォッチも StartTime() から単調増加する。 +// 実時間、仮想時間とも電源オフ中でも単調増加することに注意。 +// +// 「実経過時間」は gRealtime.Elapsed() で取得でき +// o (たぶん) Scheduler スレッド開始時から、常に増加。 +// o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。 +// o デバッガプロンプトで停止中は進行が停止する。 +// という性質を持つ。 +// +// 同期走行モードにおいては rtime_epoch, vtime_epoch をそれぞれの基準点とし +// これらと現在の実経過時間、仮想経過時間の差が同期するよう調整する。 +// 高速走行モードにおいてはこれらは必要ないので特に参照しない。 +// +// 一定間隔で行うパフォーマンス測定は、前回計測時の実および仮想経過時間を +// last_perf_rtime, last_perf_vtime として持っているので、これと現在の +// 経過時間との比で求める。同期走行/高速走行の影響は受けない。 + #include "scheduler.h" -#include "config.h" -#include "mainapp.h" -#include "mfp.h" -#include "mpu680x0.h" -#include "mpu88xx0.h" -#include "mythread.h" +#include "mpu.h" #include "rtc.h" -#include "vm.h" +#include "sync.h" -// VM スケジューラは、仮想時間とイベントを管理する。 +//#define CT_ON + +#ifdef CT_ON +static uint64 ct_get1; +static uint64 ct_get2; +static uint64 ct_start; +static uint64 ct_start1; +static uint64 ct_start2; +static uint64 ct_start3; +static uint64 ct_start4; +static uint64 ct_stop1; +static uint64 ct_stop2; +static uint64 ct_eslow; +static uint64 ct_sslow; +static uint64 ct_pslow; +#define CT(name) ct_##name += 1 +#else +#define CT(name) +#endif + +// グローバル参照用 +Scheduler *gScheduler; + +#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桁。これが最大幅。 // -// 仮想時間は起動時からの nsec で表し、MPU デバイス (実際はその向こうの -// CPU コア) が管理している。VM 界からは gMPU->GetVirtTime() で取得する。 +// 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 void *scheduler_run(void *arg); +const std::string +SecToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_SEC); +} -std::unique_ptr gScheduler; +// +// スケジューラ +// // コンストラクタ Scheduler::Scheduler() + : inherited("Scheduler") { - logname = "scheduler"; - devname = "Scheduler"; - - eventlist.clear(); + // オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが + // 通りがよさそうだ。 + SetThreadName("VM"); + + slow_top = -1; + slow_top_vtime = UINT64_MAX; + + monitor.func = ToMonitorCallback(&Scheduler::MonitorUpdate); + // サイズは Init で決まる + monitor.Regist(ID_MONITOR_SCHEDULER); } // デストラクタ Scheduler::~Scheduler() { - Terminate(); + TerminateThread(); + gScheduler = NULL; } // 初期化 bool Scheduler::Init() { - // 起動時引数指定の高速モード - SetFullSpeed(gMainApp.fast_mode); - - // スケジューラ(VM)スレッド起動 - pthread_create(&thread, NULL, scheduler_run, NULL); - thread_created = true; + // この時点でイベントの個数は確定しているはず(してなければならない) + monitor.SetSize(80, 9 + all_events.size()); return true; } -// スケジューラを開始する -void -Scheduler::Run() -{ - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_POWER_ON; - cv.notify_one(); -} - // スレッドエントリ関数 -void * -scheduler_run(void *arg) -{ - PTHREAD_SETNAME("VM"); - - gScheduler->ThreadRun(); - return NULL; -} - -// スレッドエントリ関数(の実体) void Scheduler::ThreadRun() { - uint32 req; + // 電源オンオフに関わらず、ここが rtime, vtime の基準点 + StartTime(); - // 最初の電源オンを待つ for (;;) { - std::unique_lock lock(cvmtx); - cv.wait(lock, [&] { return (atomic_reqflag != 0); }); - req = atomic_reqflag.load(); - // 終了 - if ((req & REQ_EXIT)) { - return; - } - // REQ_POWER_ON ならこれだけ下げて抜ける。 - // 他のフラグはメインループで処理するため維持。 - if ((req & REQ_POWER_ON)) { - req &= ~REQ_POWER_ON; - atomic_reqflag.store(req); - break; - } - } - // ここからは電源オン - - // RTC 時刻だけは最初から必要。 - rtimestart = GetRealTime(); - rtc_last_clock = rtimestart; - - mode = 0; - for (;;) { - req = atomic_reqflag.exchange(0); - if (__predict_false(req != 0)) { - uint32 newmode = mode; - - // 動作モード変更要因を一つずつ調べる - if ((req & REQ_CPU_MODE)) { - // CPU 状態が new_cpumode に変更になった - newmode &= ~SCHED_CPU_MASK; - newmode |= new_cpumode; - req &= ~REQ_CPU_MODE; - } - if ((req & REQ_USER_MODE)) { - // ユーザからの高速/同期モード指示が変更になった - newmode &= ~SCHED_SYNC; - newmode |= new_usermode; - req &= ~REQ_USER_MODE; - } - // 動作モード変更要因を全部調べた結果、変更が必要か。 - // mode、newmode どちらもゼロ(高速モード)か非ゼロ(通常モード)か - // なので、等値比較ではなく二値にしてから比較。 - if ((bool)newmode != (bool)mode) { - // 動作モード変更 (ここは全ビットを維持して代入) - mode = newmode; - - if (mode == 0) { - // 高速モードに - putlog(1, "高速モード"); - - // 高速モードでは RAM は直接アクセス - ::direct_ram_size = gRAM->GetSize(); - } else { - // 同期モードに - putlog(1, "同期モード"); - - // 同期モードでは RAM アクセスはデバイス経由 - ::direct_ram_size = 0; - } - - // 基準時刻をリセット - rtimebase = ::GetRealTime(); - vtimebase = gMPU->GetVirtTime(); - } - - if ((req & REQ_POWER_OFF)) { - // 電源オフ要求の場合 - - // 全デバイス電源オフ - gVM->DevicePowerOff(); - - req &= ~REQ_POWER_OFF; - } - - if ((req & REQ_RESET)) { - // リセット要求の場合 - - // 全デバイスをリセット - gVM->ResetHard(); - - req &= ~REQ_RESET; + // 条件変数の正しい使い方としては request の参照にも mtx ロックが必要 + // だが、ここでロック取らずに参照したことによって request が立った + // ことを見落としても、ループ一回回ってもう一度ここに来るだけなので + // たぶん致命的なことはないはず。 + if (__predict_false(request)) { + uint32 req; + { + std::lock_guard lock(mtx); + req = request; + request = 0; } - - if ((req & REQ_EXIT)) { - // 終了要求 - // この for ブロックをいきなり抜けるだけなので - // もうフラグの値も関係ないのだが一応。 - req &= ~REQ_EXIT; + if ((req & REQUEST_EXIT)) { break; } - - // 電源オン要求は来ないはず - 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; - } - - // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 - // 時間を求めて、この時間分だけ MPU を駆動する。 - // MPU 実行中にデバイスアクセスなどによってスケジューラにイベントが - // 登録されると、その命令の終了とともに処理を打ち切って戻ってくる。 - // また、電源オン(リセット)時には MPU デバイスが 0nsec で MPU の - // リセット例外イベントを起こすようになっている。 - - // delta_vtime が今回進める仮想時間。 - // とりあえず初期値を仮置きしとくけど、イベントは通常 20msec 以内には - // 必ず起きるので (例えば LUNA の sysclk は 10 or 16 msec 周期)、 - // この初期値は実質上限値。20msec 程度より大きければいくらでもよくて - // 100msec とかでいいんだけど、二進数で置数しやすいよう - // 0x200'0000 (約33.5msec) にしておく。 - uint64 delta_vtime = 0x200'0000; - - // イベント - uint64 vtime = gMPU->GetVirtTime(); - for (;;) { - Event *e; - // ロック区間 - { - std::lock_guard lock(evcs); - - if (eventlist.empty()) { - break; - } - e = eventlist.front(); - if (e->vtime > vtime) { - // まだ時刻に到達していない - delta_vtime = e->vtime - vtime; - break; - } - - // 到達したのでこのイベントをリストから削除 - e->active = false; - eventlist.pop_front(); + if ((req & REQUEST_MESSAGE)) { + DispatchMessage(); } - - // コールバック - e->dev->putlogf(3, lstr("イベント '%s' 時刻到達", - e->GetName().c_str())); - ((e->dev)->*(e->func))(*e); - - // 同時刻のイベントがあるかも知れないのでなくなるまで調べる } - // CPU を駆動 - new_cpumode = gMPU->Run(delta_vtime); - - // CPU 駆動によって進んだ仮想時刻を再取得 - vtime = gMPU->GetVirtTime(); - - // 同期モードなら実時間調整。 - // あるいは高速モードであってもストップ状態なら実時間駆動する。 - 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 { - // 実時間のほうが進んでいれば、間に合ってない - - // XXX ここで間引き運転とか - } + // 先頭のイベントを取得して停止 + Event *ev; + if (__predict_true(fast != NULL)) { + CT(get1); + ev = fast; + fast = NULL; + } else { + CT(get2); + ev = slow[slow_top--]; + slow_top_vtime = slow[slow_top]->vtime; } + ev->active = false; - // CPU 側の走行状態が変わったら、このループに通知 - if (__predict_false(((mode ^ new_cpumode) & SCHED_CPU_MASK))) { - atomic_reqflag |= REQ_CPU_MODE; - } + // 時刻更新 + vtime = ev->vtime; + + // コールバック + ev->count++; + ((ev->dev)->*(ev->func))(*ev); } } -// 必要ならスレッドの終了を指示して完了まで待つ。 +// 時間の始まり void -Scheduler::Terminate() +Scheduler::StartTime() { - if (thread_created) { - RequestExit(); - pthread_join(thread, NULL); - thread_created = false; + // イベントキューを空にする + for (auto ev : all_events) { + if (ev->active) { + StopEvent(*ev); + } + ev->count = 0; } + + // 時間をリセットする。 + // 呼び出しの依存関係に注意。 + vtime = 0; // required by Sync + gSync->StartTime(); // required by RTC + gRTC->StartTime(); } -// スレッド終了を指示する。 +// スレッド終了指示 +// (当然他スレッドから呼ぶことになる) void -Scheduler::RequestExit() +Scheduler::Terminate() { - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_EXIT; + std::unique_lock lock(mtx); + request |= REQUEST_EXIT; cv.notify_one(); } -// 動作モードを設定する。 -// true なら高速モード、false なら同期モード。 +// イベントを登録する。 +// 登録時点では実は type だけ確定していればよい。 +// +// ここで管理してるのはポインタだけで、実体は呼び出し元クラスのメンバ +// インスタンスのはずなので、登録しっぱなしにする。 void -Scheduler::SetFullSpeed(bool enable) +Scheduler::RegistEvent(Event& ev) { - if (enable) { - new_usermode = 0; - } else { - new_usermode = SCHED_SYNC; - } - atomic_reqflag |= REQ_USER_MODE; + // イベントリストに追加 + all_events.push_back(&ev); } // 指定のイベントを開始する。 @@ -304,208 +279,260 @@ Scheduler::SetFullSpeed(bool enable) // 新しいイベントを再登録となる。 // イベントはワンショットのみ。 void -Scheduler::StartEvent(Event *newev) +Scheduler::RestartEvent(Event& ev) { - bool inserted = false; - bool updated = false; + if (ev.IsRunning()) { + StopEvent(ev); + } - // time は仮想相対時間 [nsec]、vtime は仮想絶対時刻 [nsec] - newev->vtime = gMPU->GetVirtTime() + newev->time; + StartEvent(ev); +} - 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; +// 指定のイベントを開始する。 +// このイベントが登録されてないことが確定できる場合のみこちらが使える。 +// イベントはワンショットのみ。 +void +Scheduler::StartEvent(Event& ev) +{ + CT(start); +#ifdef CT_ON + if (ct_start % 100'000'000 == 0) { + printf("get*=%lu [%lu, %lu]\n", + (ct_get1 + ct_get2), ct_get1, ct_get2); + printf("start*=%lu [%lu, %lu, %lu, %lu]\n", + ct_start, ct_start1, ct_start2, ct_start3, ct_start4); + printf("stop*=%lu [%lu, %lu]\n", + (ct_stop1 + ct_stop2), ct_stop1, ct_stop2); + printf("eslow=%lu sslow=%lu pslow=%lu\n", + ct_eslow, ct_sslow, ct_pslow); + printf("slow_top=%d vtime=%lu\n", slow_top, slow_top_vtime); + } +#endif + + // この assert を有効にすると 10 ポイント性能が低下する(502%->492%) + //assertmsg(ev.IsRunning() == false, "%s", ev.GetName().c_str()); + + // vtime は仮想絶対時刻 [nsec]、time は仮想相対時間 [nsec] + ev.vtime = vtime + ev.time; + + ev.active = true; + + if (__predict_true(ev.vtime <= slow_top_vtime)) { + if (__predict_true(fast == NULL)) { + CT(start1); + fast = &ev; + } else { + if (__predict_false(ev.vtime <= fast->vtime)) { + CT(start2); + PushSlow(*fast); + fast = &ev; + } else { + CT(start3); + PushSlow(ev); } } + } else { + CT(start4); + EnqueueSlow(ev); } - // ソートされているところに自身を挿入 - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (newev->vtime <= e->vtime) { - // このイベントの前に入れる - eventlist.insert(it, newev); - inserted = true; - break; +} + +// 実時間間隔を指定してイベントを開始する。 +// rt_now はイベント発行者の実時間での現在時刻で、 +// rt_period は次回のイベントまでの実時間間隔。 +void +Scheduler::StartRealtimeEvent(Event& ev, uint64 rt_now, uint64 rt_period) +{ + // 実時間をもとに仮想時間軸上の推定を行う。 + + uint64 rtime = gSync->GetRealTime(); + + if (rtime > rt_now + rt_period) { + // 実時間がさらに 1 period 以上進んでいる。 + // 実時間に追いつかないといけないので、イベントの + // 周期を短くして回復運転させる。 + // 周期を短くしすぎるとゲストが処理できなくなるので、 + // 10% カットに留める。 + ev.time = rt_period - rt_period / 10; + } else if (rtime < rt_now - rt_period) { + // イベントがさらに 1 period 以上進んでいる。 + // 実時間を追い越してしまっているので、イベントの + // 周期を長くして待つ。長くする方はいくらでも長くしていいので、 + // 連続した場合はどんどん長くしていく。 + ev.time += rt_period; + } else { + // おおむね同期している + ev.time = rt_period; + } + + RestartEvent(ev); +} + +// ev は slow_top には来ない前提 +void +Scheduler::EnqueueSlow(Event& ev) +{ + CT(eslow); + + int s = slow_top; + slow[++slow_top] = slow[s]; + + for (; --s >= 0; ) { + if (ev.vtime <= slow[s]->vtime) { + slow[s + 1] = &ev; + return; + } else { + slow[s + 1] = slow[s]; } } - if (inserted == false) { - // 最後に追加 - eventlist.push_back(newev); - } - newev->active = true; - evcs.unlock(); - - gMPU->Release(); - - newev->dev->putlogf(3, lstr("イベント '%s' %s %d.%03d usec 後", - newev->GetName().c_str(), - updated ? "更新" : "開始", - (int)(newev->time / 1000), - (int)(newev->time % 1000))); + slow[0] = &ev; +} + +void +Scheduler::PushSlow(Event& ev) +{ + CT(pslow); + + slow[++slow_top] = &ev; + slow_top_vtime = ev.vtime; } // 指定のイベントを停止する。 // 指定されたイベントが登録されていなければ何もしない。 void -Scheduler::StopEvent(Event *event) +Scheduler::StopEvent(Event& ev) { - bool found = false; + ev.active = 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; + if (__predict_false(&ev == fast)) { + CT(stop1); + fast = NULL; + } else { + CT(stop2); + StopSlowEvent(ev); + } +} + +void +Scheduler::StopSlowEvent(Event& ev) +{ + CT(sslow); + + int s = slow_top; + + for (; s >= 0; s--) { + if (&ev == slow[s]) { + for (; s < slow_top; s++) { + slow[s] = slow[s + 1]; + } + slow_top--; + if (slow_top < 0) { + slow_top_vtime = UINT64_MAX; + } else { + slow_top_vtime = slow[slow_top]->vtime; + } break; } } - evcs.unlock(); +} - // イベントを停止した場合は MPU の実行中断はしなくてよい。 - // 1msec 後にイベントを追加した後、やっぱりそのイベントを取り消した - // 場合 (LUNA の電源オフとか) はここで MPU 処理を中断するよりも - // そのまま 1msec 走って問題ない。 - - if (found) { - event->dev->putlogf(3, lstr("イベント '%s' 停止", - event->GetName().c_str())); - } +// メッセージハンドラを登録する。 +void +Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func) +{ + auto& mh = message_handlers[msgid]; + assert(mh.dev == NULL); + mh.dev = dev; + mh.func = func; } -// 経過時間 t を文字列にして返す。 -static const std::string -TimeToStr(uint64 t) +// メッセージをディスパッチして、登録されていればハンドラを呼び出す。 +void +Scheduler::DispatchMessage() { - char buf[32]; - char *p; - size_t len; - int n; + uint64 msg; - 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; + while (msgq.Dequeue(&msg)) { + MessageID msgid = (MessageID)(msg & 0xff); + uint32 arg = (msg >> 32); - 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; + assert(0 <= msgid && msgid < MessageID::MAX); + + auto& mh = message_handlers[msgid]; + if (mh.dev != NULL) { + ((mh.dev)->*(mh.func))(msgid, arg); + } } - n = snprintf(p, len, ".%03d'%03d'%03d", ms, us, ns); - p += n; - len -= n; +} - return std::string(buf, p - buf); +// メッセージを送る。 +// VM スレッド以外から呼び出しても良い。 +void +Scheduler::SendMessage(MessageID msgid, uint32 arg) +{ + std::unique_lock lock(mtx); + + uint64 msg = (uint64)msgid | (((uint64)arg) << 32); + if (msgq.Enqueue(msg) == false) { + putlog(0, "Message queue exhausted !!"); + } + request |= REQUEST_MESSAGE; + cv.notify_one(); } -// モニターサイズ取得 -nnSize -Scheduler::GetMonitorSize() +// 指定時間が経過するか、リクエストが起きるまでスリープ +void +Scheduler::Sleep(uint64 time) { - // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 - // そのタイミングで一度だけ呼ばれるフックが今の所ない。 - return nnSize(68, 11 + gEvents.size()); + std::unique_lock lock(mtx); + cv.wait_for(lock, std::chrono::nanoseconds(time), [&] { + return (request != 0); + }); + // リクエストフラグが立ってもここでは何もしない } void -Scheduler::MonitorUpdate(TextScreen& monitor) +Scheduler::MonitorUpdate(Monitor *, TextScreen& screen) { - int x, y; + int y; - monitor.Clear(); - x = 0; - y = 0; + screen.Clear(); - int clock_khz = gMPU->GetClockSpeed(); + y = gSync->MonitorUpdateSub(screen, vtime); - monitor.Print(0, y, "Mode: 0x%04x (", mode); - if ((mode & SCHED_SYNC)) { - monitor.Puts(14, y, TA::On, "Sync"); - } else { - monitor.Puts(14, y, "Full"); - } - switch (mode & SCHED_CPU_MASK) { - case SCHED_CPU_NORMAL: - monitor.Puts(19, y, "Run"); - break; - case SCHED_CPU_STOP: - monitor.Puts(19, y, TA::On, "STOP"); - break; - case SCHED_CPU_HALT: - monitor.Puts(19, y, TA::On, "HALT"); - break; - } - monitor.Puts(23, y, ")"); - y++; - monitor.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); - monitor.Print(0, y++, "MPU Speed: %d.%03dMHz", + int clock_khz = gMPU->GetClockSpeed(); + 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)); - - // 0 1 2 3 4 5 6 - // 0123456789012345678901234567890123456789012345678901234567890123456 - // Set Time Remain Time Description Code - // 3.123'456'789 3.123'456'789 0123456789012345678901234 $01234567 - x = 0; + + // 0 1 2 3 + // 012345678901234567890123456789 + // Event Time Remain Time + // 3.123'456'789 3.123'456'789 + // + // 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789 + // Description Count + // 012345678901234567890123456789 999,999,999,999,999 + // 18,446,744,073,709,551,615 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"); + screen.Puts(0, y, "Event Time"); + screen.Puts(15, y, "Remain Time"); + screen.Puts(30, y, "Description"); + screen.Puts(75, y, "Count"); y++; - for (const auto& e : gEvents) { + for (const auto ev : all_events) { uint64 rem; TA attr; - if (e->active) { + + if (ev->IsRunning()) { attr = TA::Normal; - if (e->vtime > vtime) { - rem = e->vtime - vtime; + if (ev->vtime > vtime) { + rem = ev->vtime - vtime; } else { // XXX 実際には起きないけど、この表示をするにあたって // スケジューラスレッドと一切調停していないので、仮想時刻が - // e->cycle (イベント発生時刻) をすぎていることがある。 + // ev->vtime (イベント発生時刻) をすぎていることがある。 // すぎてるので残り時間 0 と表示しておく。 rem = 0; } @@ -513,17 +540,21 @@ 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); + + if (ev->count != ev->last_count) { + attr = TA::Normal; + ev->last_count = ev->count; + } + + // 先にイベント回数を表示。 + // 21文字以上になったら上位桁が長い Description で消されても + // 大勢に影響はないだろう。 + std::string countstr = format_number(ev->count); + screen.Print(54, y, attr, "%26s", countstr.c_str()); + screen.Print(0, y, attr, "%14s %14s %s ", + SecToStr(ev->time).c_str(), + SecToStr(rem).c_str(), + ev->GetName().c_str()); + y++; } }