--- nono/vm/scheduler.cpp 2026/04/29 17:04:42 1.1.1.6 +++ nono/vm/scheduler.cpp 2026/04/29 17:05:33 1.1.1.18 @@ -4,504 +4,571 @@ // Licensed under nono-license.txt // +// +// スケジューラ +// + +// VM スケジューラは、仮想時間とイベントを管理する。 +// 仮想時間はすべて nsec で表し、StartTime() から単調増加する。 +// VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。 +// +// 実時間は Syncer が管理し、こちらも 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 "monitor.h" #include "rtc.h" -#include "vm.h" +#include "syncer.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 + +#define FORMAT_FULL 0 +#define FORMAT_SEC 1 + +// 経過時間 t を文字列にして返す。 +// 文字列長は、t の大きさ(とフォーマット指定)によって以下の通り。 // -// 仮想時間は MPU の起動時からのクロックサイクルの経過数で表すこととする。 -// このエミュレータはシングルプロセッサシステムのみがターゲットであり、 -// 実行中に動作クロックが変わることはないので、これで問題ない。 -// VM 界からは gMPU->total_cycle() で取得する。 +// 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秒未満の場合だけ %2u ではなく %u で1桁切り詰めているが、これは +// FORMAT_SEC との互換性のため。その必要のない10分未満と10時間未満は +// どちらも %2u で表記し桁数を維持することに努める。 +static const std::string +TimeToStrF(uint64 t, int format) +{ + char buf[32]; + char *p; + size_t len; + int n; -static void *scheduler_run(void *arg); + uint ns = t % 1000; + t /= 1000; + uint us = t % 1000; + t /= 1000; + uint ms = t % 1000; + t /= 1000; -std::unique_ptr gScheduler; + 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, "%3ud %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); +} + +const std::string +SecToStr(uint64 t) +{ + return TimeToStrF(t, FORMAT_SEC); +} + +// +// スケジューラ +// // コンストラクタ Scheduler::Scheduler() + : inherited(OBJ_SCHEDULER) { - logname = "scheduler"; - devname = "Scheduler"; - - eventlist.clear(); + // オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが + // 通りがよさそうだ。 + SetThreadName("VM"); + + slow_top = -1; + slow_top_vtime = UINT64_MAX; + + monitor = gMonitorManager->Regist(ID_MONITOR_SCHEDULER, this); + monitor->func = ToMonitorCallback(&Scheduler::MonitorUpdate); + // サイズは Init で決まる } // デストラクタ Scheduler::~Scheduler() { - Terminate(); + TerminateThread(); } // 初期化 -// ここで mpu_clock が(非ゼロに)初期化されるとこれ以降 putlog() が使える。 -// そのため、Scheduler デバイスをデバイスリストの先頭のほうに置くことで、 -// これ以降のオブジェクトの Init() は putlog() を使ってよいとできる。 -// BusErrDevice だけは Scheduler より先に作るが BusErrDevice に Init() は -// ないので (= 問題は起きないので)、よいことにする。 bool Scheduler::Init() { - const ConfigItem& item = gConfig->Find("mpu-clock"); - const std::string& val = item.AsString(); - if (val.empty()) { - item.Err(); - return false; - } - // 文字列を double に変換 - char *end; - errno = 0; - double f = strtod(val.c_str(), &end); - if (end == val.c_str() || end[0] != '\0' || errno == ERANGE) { - item.Err(); - return false; - } - // 設定の "mpu-clock" は MHz 単位だが、変数 mpu_clock は kHz 単位。 - mpu_clock = (int)(f * 1000); - // 1MHz 以下はエラー。実際 10MHz 以下でもいい気がするけど - if (mpu_clock < 1000) { - item.Err(); + if (inherited::Init() == false) { return false; } - // 起動時引数指定の高速モード - SetFullSpeed(gMainApp.fast_mode); - - // スケジューラ(VM)スレッド起動 - pthread_create(&thread, NULL, scheduler_run, NULL); - thread_created = true; + syncer = GetSyncer(); return true; } -// スケジューラを開始する -void -Scheduler::Run() -{ - std::unique_lock lock(cvmtx); - atomic_reqflag |= REQ_POWER_ON; - cv.notify_one(); -} - -// スレッドエントリ関数 -void * -scheduler_run(void *arg) +// 初期化(VM から呼ばれる) +bool +Scheduler::Init2() { - PTHREAD_SETNAME("VM"); + // イベントの個数が確定した(してなければならない) + monitor->SetSize(80, 9 + all_events.size()); - gScheduler->ThreadRun(); - return NULL; + return true; } -// スレッドエントリ関数(の実体) +// スレッドエントリ関数 void Scheduler::ThreadRun() { - uint64 cycle; // 今回実行するサイクル数 - uint32 req; + SetThreadAffinityHint(AffinityClass::Heavy); - // 最初の電源オンを待つ - 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 なら他のフラグを書き戻して抜ける。 - // REQ_{SYNC|FAST} は以降のメインループで処理するため。 - if ((req & REQ_POWER_ON)) { - req &= ~REQ_POWER_ON; - atomic_reqflag.store(req); - break; - } - } - // ここからは電源オン - - // RTC 時刻だけは最初から必要。 - rtimestart = GetRealTime(); - rtc_last_clock = rtimestart; + // 電源オンオフに関わらず、ここが rtime, vtime の基準点 + StartTime(); - cycle = 0; - mode = 0; for (;;) { - req = atomic_reqflag.exchange(0); - if (req) { - if ((req & REQ_SYNC)) { - // スケジューラを同期モードに - mode |= SCHED_SYNC; - // 同期モードでは RAM アクセスはデバイス経由 - ::direct_ram_size = 0; - } - if ((req & REQ_FAST)) { - // スケジューラを高速モードに - mode &= ~SCHED_SYNC; - // 高速モードでは RAM は直接アクセス - ::direct_ram_size = ::ram_size; - } - if ((req & REQ_RUN)) { - // MPU が通常状態になった - mode &= ~SCHED_STOP; + // 条件変数の正しい使い方としては request の参照にも mtx ロックが必要 + // だが、ここでロック取らずに参照したことによって request が立った + // ことを見落としても、ループ一回回ってもう一度ここに来るだけなので + // たぶん致命的なことはないはず。 + if (__predict_false(request)) { + uint32 req; + { + std::lock_guard lock(mtx); + req = request; + request = 0; } - if ((req & REQ_STOP)) { - // MPU が STOP 状態になった - mode |= SCHED_STOP; + if ((req & REQUEST_EXIT)) { + break; } - if ((req & REQ_MODEMASK)) { - // mode がどれかにでも変わったら - static const char *modestr[] = { - "高速", - "高速 & STOP状態", - "通常", - "通常 & STOP状態", - }; - putlog(1, "モード変更 mode=%d (%s)", mode, modestr[mode]); - - // 基準時刻をリセット - rtimebase = ::GetRealTime(); - vtimebase = GetVirtTime(); - // 該当ビットを落としておく - req &= ~REQ_MODEMASK; + for (; req; req &= req - 1) { + uint id = __builtin_ctz(req); + if (id < MessageID::MAX_REQUEST) { + InvokeMessage(id, 0); + } else { + DispatchMessage(); + } } + } - if ((req & REQ_POWER_OFF)) { - // 電源オフ要求の場合 + // 先頭のイベントを取得して停止 + 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; - // 全デバイス電源オフ - gVM->DevicePowerOff(); + // 時刻更新 + vtime = ev->vtime; - req &= ~REQ_POWER_OFF; - } + // コールバック + ev->count++; + ((ev->dev)->*(ev->func))(*ev); + } +} - if ((req & REQ_RESET)) { - // リセット要求の場合 +// 時間の始まり +void +Scheduler::StartTime() +{ + // イベントキューを空にする + for (auto ev : all_events) { + if (ev->active) { + StopEvent(*ev); + } + ev->count = 0; + } - // 全デバイスをリセット - gVM->ResetHard(); + // 時間をリセットする。 + // 呼び出しの依存関係に注意。 + vtime = 0; // required by Sync + syncer->StartTime(); // required by RTC + GetRTCDevice()->StartTime(); +} - req &= ~REQ_RESET; - } +// スレッド終了指示 +// (当然他スレッドから呼ぶことになる) +void +Scheduler::Terminate() +{ + std::unique_lock lock(mtx); + request |= REQUEST_EXIT; + cv.notify_one(); +} - if ((req & REQ_EXIT)) { - // 終了要求 - // この for ブロックをいきなり抜けるだけなので - // もうフラグの値も関係ないのだが一応。 - req &= ~REQ_EXIT; - break; - } +// イベントを登録する。 +// 登録時点では実は type だけ確定していればよい。 +// +// ここで管理してるのはポインタだけで、実体は呼び出し元クラスのメンバ +// インスタンスのはずなので、登録しっぱなしにする。 +void +Scheduler::RegistEvent(Event& ev) +{ + // イベントリストに追加 + all_events.push_back(&ev); +} - // 電源オン要求は来ないはず - assertmsg(req == 0, "req=$%x", req); - } +// 指定のイベントを開始する。 +// すでに同イベントが登録されている場合は古いイベントを削除してから +// 新しいイベントを再登録となる。 +// イベントはワンショットのみ。 +void +Scheduler::RestartEvent(Event& ev) +{ + if (ev.IsRunning()) { + StopEvent(ev); + } - // RTC は常にホスト時刻で動いており - // 32Hz = 31.25msec ごとにパルスを入れる。 - while (::GetRealTime() > rtc_last_clock + 31.25_msec) { - gRTC->ClockIn(); - rtc_last_clock += 31.25_msec; - } + StartEvent(ev); +} - // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 - // サイクル数を求めて、このサイクル数だけ MPU を駆動する。 - // MPU 実行中にデバイスアクセスなどによってスケジューラにイベントが - // 登録されると、その命令の終了とともに処理を打ち切って戻ってくる。 - // また、電源オン(リセット)時には MPU デバイスが 0 サイクル目で - // MPU リセット例外イベントを起こすようになっている。 - - // イベント - for (;;) { - Event *e; - // ロック区間 - { - std::lock_guard lock(evcs); +// 指定のイベントを開始する。 +// このイベントが登録されてないことが確定できる場合のみこちらが使える。 +// イベントはワンショットのみ。 +void +Scheduler::StartEvent(Event& ev) +{ + CT(start); +#ifdef CT_ON + if (ct_start % 100'000'000 == 0) { + printf("get*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", + (ct_get1 + ct_get2), ct_get1, ct_get2); + printf("start*=%" PRIu64 + " [%" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "]\n", + ct_start, ct_start1, ct_start2, ct_start3, ct_start4); + printf("stop*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", + (ct_stop1 + ct_stop2), ct_stop1, ct_stop2); + printf("eslow=%" PRIu64 " sslow=%" PRIu64 " pslow=%" PRIu64 "\n", + ct_eslow, ct_sslow, ct_pslow); + printf("slow_top=%d vtime=%" PRIu64 "\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); + } +} - if (eventlist.empty()) { - cycle = INT64_MAX; - break; - } - e = eventlist.front(); - if (gMPU->total_cycle() < e->cycle) { - // まだ時刻に到達していない - cycle = e->cycle - gMPU->total_cycle(); - break; - } +// 実時間間隔を指定してイベントを開始する。 +// rt_now はイベント発行者の実時間での現在時刻で、 +// rt_period は次回のイベントまでの実時間間隔。 +void +Scheduler::StartRealtimeEvent(Event& ev, uint64 rt_now, uint64 rt_period) +{ + // 実時間をもとに仮想時間軸上の推定を行う。 - // 到達したのでこのイベントをリストから削除 - e->active = false; - eventlist.pop_front(); - } + uint64 rtime = syncer->GetRealTime(); - // コールバック - e->dev->putlog(3, "イベント '%s' 時刻到達", e->GetName().c_str()); - ((e->dev)->*(e->func))(*e); + 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); +} - // CPU 駆動 - uint32 outer; - outer = gMPU->Run(cycle); - - // 同期モードなら実時間調整。 - // あるいは高速モードであってもストップ状態なら実時間駆動する。 - if (mode != 0) { - // 通常モード - uint64 vtime; - uint64 rtime; - - // 基準時からの経過時間 - vtime = GetVirtTime() - vtimebase; - rtime = GetRealTime() - rtimebase; - - if (vtime > rtime) { - // 仮想時間のほうが進んでいれば、スリープして待つ - uint64 diff = vtime - rtime; - struct timespec ts; - ts.tv_sec = diff / (1000 * 1000 * 1000); - ts.tv_nsec = diff % (1000 * 1000 * 1000); - nanosleep(&ts, NULL); - } else { - // 実時間のほうが進んでいれば、間に合ってない +// ev は slow_top には来ない前提 +void +Scheduler::EnqueueSlow(Event& ev) +{ + CT(eslow); - // XXX ここで間引き運転とか - } - } + int s = slow_top; + slow[++slow_top] = slow[s]; - // CPU の STOP 状態をこっちのモードに反映。 - // このために SCHED_STOP と CPU_REQ_STOP は同じビット位置にしてある。 - // CPU の HALT はスケジューラ的には STOP と同じ扱いでいい。 - if (((mode ^ outer) & SCHED_STOP)) { - // 状態変更要求 - if ((outer & (CPU_REQ_STOP | CPU_REQ_HALT)) != 0) { - atomic_reqflag |= REQ_STOP; - } else { - atomic_reqflag |= REQ_RUN; - } + for (; --s >= 0; ) { + if (ev.vtime <= slow[s]->vtime) { + slow[s + 1] = &ev; + return; + } else { + slow[s + 1] = slow[s]; } } + slow[0] = &ev; } -// 必要ならスレッドの終了を指示して完了まで待つ。 void -Scheduler::Terminate() +Scheduler::PushSlow(Event& ev) { - if (thread_created) { - atomic_reqflag |= REQ_EXIT; - pthread_join(thread, NULL); - thread_created = false; - } + CT(pslow); + + slow[++slow_top] = &ev; + slow_top_vtime = ev.vtime; } -// 動作モードを設定する。 -// true なら高速モード、false なら同期モード。 +// 指定のイベントを停止する。 +// 指定されたイベントが登録されていなければ何もしない。 void -Scheduler::SetFullSpeed(bool enable) +Scheduler::StopEvent(Event& ev) { - if (enable) { - atomic_reqflag |= REQ_FAST; + ev.active = false; + + if (__predict_false(&ev == fast)) { + CT(stop1); + fast = NULL; } else { - atomic_reqflag |= REQ_SYNC; + CT(stop2); + StopSlowEvent(ev); } } -// 指定のイベントを開始する。 -// すでに同イベントが登録されている場合は古いイベントを削除してから -// 新しいイベントを再登録となる。 -// イベントはワンショットのみ。 void -Scheduler::StartEvent(Event *newev) +Scheduler::StopSlowEvent(Event& ev) { - bool inserted = false; - bool updated = false; + CT(sslow); - // 相対 time から絶対 cycle を計算 - newev->cycle = gMPU->total_cycle() + Vtime2Cycle(newev->time); + int s = slow_top; - 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 (; 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; } - } - } - // ソートされているところに自身を挿入 - for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { - Event *e = *it; - if (newev->cycle <= e->cycle) { - // このイベントの前に入れる - eventlist.insert(it, newev); - inserted = true; break; } } - if (inserted == false) { - // 最後に追加 - eventlist.push_back(newev); - } - newev->active = true; - evcs.unlock(); - - gMPU->Release(); - - newev->dev->putlog(3, "イベント '%s' %s %d.%03d usec 後", - newev->GetName().c_str(), - updated ? "更新" : "開始", - (int)(newev->time / 1000), - (int)(newev->time % 1000)); } -// 指定のイベントを停止する。 -// 指定されたイベントが登録されていなければ何もしない。 +// メッセージハンドラを登録する。 void -Scheduler::StopEvent(Event *event) +Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func) { - bool found = false; + assert(msgid < MessageID::MAX); - 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; - } - } - evcs.unlock(); + auto& mh = message_handlers[msgid]; + assertmsg(mh.dev == NULL, + "%s: msgid=%d connected again", __func__, (int)msgid); + mh.dev = dev; + mh.func = func; +} - // イベントを停止した場合は MPU の実行中断はしなくてよい。 - // 1msec 後にイベントを追加した後、やっぱりそのイベントを取り消した - // 場合 (LUNA の電源オフとか) はここで MPU 処理を中断するよりも - // そのまま 1msec 走って問題ない。 +// メッセージをディスパッチして、登録されていればハンドラを呼び出す。 +void +Scheduler::DispatchMessage() +{ + uint64 msg; - if (found) { - event->dev->putlog(3, "イベント '%s' 停止", event->GetName().c_str()); + while (msgq.Dequeue(&msg)) { + MessageID msgid = (MessageID)(msg & 0xff); + uint32 arg = (msg >> 32); + + // MessageID::MPU_TRACE_ALL だけ特別対応。 + // MPU_TRACE メッセージの宛先は(最大)2つあるが、 + // スレッドを越える時は1メッセージにしておきたいので、 + // MPU_TRACE_ALL が来たらここで分岐。 + if (msgid == MessageID::MPU_TRACE_ALL) { + InvokeMessage(MessageID::MPU_TRACE_MAIN, arg); + InvokeMessage(MessageID::MPU_TRACE_XP, arg); + } else { + assert(msgid < MessageID::MAX); + InvokeMessage(msgid, arg); + } } } -// 経過時間 t を文字列にして返す。 -static const std::string -TimeToStr(uint64 t) +// メッセージハンドラを呼び出す。登録されてなければ何もしない。 +void +Scheduler::InvokeMessage(MessageID msgid, uint32 arg) { - char buf[32]; - char *p; - size_t len; - int n; + auto h = message_handlers[msgid]; + if (h.dev != NULL) { + (h.dev->*(h.func))(msgid, arg); + } +} - 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; +// メッセージを送る。 +// VM スレッド以外から呼び出しても良い。 +void +Scheduler::SendMessage(MessageID msgid, uint32 arg) +{ + std::unique_lock lock(mtx); - 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; + if (msgid < MessageID::MAX_REQUEST) { + request |= 1U << (uint)msgid; } else { - n = snprintf(p, len, "%d", s); - p += n; - len -= n; + uint64 msg = (uint64)msgid | (((uint64)arg) << 32); + if (msgq.Enqueue(msg) == false) { + putlog(0, "Message queue exhausted !! msgid=%d", (int)msgid); + } + request |= REQUEST_MESSAGE; } - n = snprintf(p, len, ".%03d'%03d'%03d", ms, us, ns); - p += n; - len -= n; - - return std::string(buf, p - buf); + cv.notify_one(); } -// モニターサイズ取得 -nnSize -Scheduler::GetMonitorSize() -{ - // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 - // そのタイミングで一度だけ呼ばれるフックが今の所ない。 - return nnSize(68, 11 + gEvents.size()); +// 指定時間が経過するか、リクエストが起きるまでスリープ +void +Scheduler::Sleep(uint64 time) +{ + std::unique_lock lock(mtx); + cv.wait_for(lock, std::chrono::nanoseconds(time), [&] { + return (request != 0); + }); + // リクエストフラグが立ってもここでは何もしない } void -Scheduler::MonitorUpdate(TextScreen& monitor) -{ - int x, y; - - monitor.Clear(); - x = 0; - y = 0; - - monitor.Print(0, y++, "Mode: %s %s", - (mode & SCHED_SYNC) ? "Sync" : "Full", - (mode & SCHED_STOP) ? "STOP" : "Run "); - monitor.Print(0, y++, "Req: %08x", (uint32)atomic_reqflag); - monitor.Print(0, y++, "MPU Speed: %d.%03dMHz", - (mpu_clock / 1000), (mpu_clock % 1000)); - uint64 vt = GetVirtTime(); - uint64 rt = GetRealTime(); - uint64 relvt = vt - vtimebase; - uint64 relrt = rt - rtimebase; - // RealTime は実時間なのでスケジューラ開始からの時間に変換 - rt -= rtimestart; - monitor.Print(0, y++, "Total Real Time: %18s", TimeToStr(rt).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(vt).c_str()); - double ratio = (double)vt / rt * 100; - monitor.Print(0, y++, " Ratio : %3d.%01d%%", - (int)ratio, ((int)(ratio * 10) % 10)); - monitor.Print(0, y++, "Moment Real Time: %18s", TimeToStr(relrt).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(relvt).c_str()); - // XXX 移動平均をとる - double relratio = (double)relvt / relrt * 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; +Scheduler::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int y; + + screen.Clear(); + + y = syncer->MonitorUpdateSub(screen, vtime); + + // 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++; - uint64 cycle = gMPU->total_cycle(); - 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->cycle > cycle) { - rem = Cycle2Vtime(e->cycle - cycle); + if (ev->vtime > vtime) { + rem = ev->vtime - vtime; } else { // XXX 実際には起きないけど、この表示をするにあたって // スケジューラスレッドと一切調停していないので、仮想時刻が - // e->cycle (イベント発生時刻) をすぎていることがある。 + // ev->vtime (イベント発生時刻) をすぎていることがある。 // すぎてるので残り時間 0 と表示しておく。 rem = 0; } @@ -509,17 +576,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++; } }