--- nono/vm/scheduler.cpp 2026/04/29 17:05:11 1.1.1.14 +++ nono/vm/scheduler.cpp 2026/04/29 17:06:01 1.1.1.22 @@ -9,14 +9,14 @@ // // VM スケジューラは、仮想時間とイベントを管理する。 -// 仮想時間はすべて nsec で表し、StartTime() から単調増加する。 +// 仮想時間はすべて tsec で表し、StartTime() から単調増加する。 // VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。 // -// 実時間は Sync が管理し、こちらも nsec で表す。 +// 実時間は Syncer が管理し、こちらも tsec で表す。 // このストップウォッチも StartTime() から単調増加する。 // 実時間、仮想時間とも電源オフ中でも単調増加することに注意。 // -// 「実経過時間」は gRealtime.Elapsed() で取得でき +// 「実経過時間」は gRealtime.Elapsed_nsec() で取得できるが tsec で保持する。 // o (たぶん) Scheduler スレッド開始時から、常に増加。 // o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。 // o デバッガプロンプトで停止中は進行が停止する。 @@ -31,9 +31,10 @@ // 経過時間との比で求める。同期走行/高速走行の影響は受けない。 #include "scheduler.h" -#include "mpu.h" +#include "event.h" +#include "monitor.h" #include "rtc.h" -#include "sync.h" +#include "syncer.h" //#define CT_ON @@ -55,105 +56,13 @@ static uint64 ct_pslow; #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桁。これが最大幅。 -// -// 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); -} - -const std::string -SecToStr(uint64 t) -{ - return TimeToStrF(t, FORMAT_SEC); -} - // // スケジューラ // // コンストラクタ Scheduler::Scheduler() - : inherited("Scheduler") + : inherited(OBJ_SCHEDULER) { // オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが // 通りがよさそうだ。 @@ -162,24 +71,34 @@ Scheduler::Scheduler() slow_top = -1; slow_top_vtime = UINT64_MAX; - monitor.func = ToMonitorCallback(&Scheduler::MonitorUpdate); - // サイズは Init で決まる - monitor.Regist(ID_MONITOR_SCHEDULER); + monitor = gMonitorManager->Regist(ID_MONITOR_SCHEDULER, this); + monitor->SetCallback(&Scheduler::MonitorScreen); + // サイズは最終的に Init2() で決まる。 + monitor->SetSize(80, 9); } // デストラクタ Scheduler::~Scheduler() { TerminateThread(); - gScheduler = NULL; } // 初期化 bool Scheduler::Init() { - // この時点でイベントの個数は確定しているはず(してなければならない) - monitor.SetSize(80, 9 + all_events.size()); + evman = GetEventManager(); + syncer = GetSyncer(); + + return true; +} + +// 初期化(VM から呼ばれる) +bool +Scheduler::Init2() +{ + // イベントの個数が確定した(してなければならない) + monitor->AddHeight(evman->all.size()); return true; } @@ -188,6 +107,8 @@ Scheduler::Init() void Scheduler::ThreadRun() { + SetThreadAffinityHint(AffinityClass::Heavy); + // 電源オンオフに関わらず、ここが rtime, vtime の基準点 StartTime(); @@ -206,8 +127,14 @@ Scheduler::ThreadRun() if ((req & REQUEST_EXIT)) { break; } - if ((req & REQUEST_MESSAGE)) { - DispatchMessage(); + + for (; req; req &= req - 1) { + uint id = __builtin_ctz(req); + if (id < MessageID::MAX_REQUEST) { + InvokeMessage(id, 0); + } else { + DispatchMessage(); + } } } @@ -229,7 +156,9 @@ Scheduler::ThreadRun() // コールバック ev->count++; - ((ev->dev)->*(ev->func))(*ev); + auto dev = ev->GetOwner(); + auto func = ev->GetCallback(); + (dev->*(func))(ev); } } @@ -238,9 +167,9 @@ void Scheduler::StartTime() { // イベントキューを空にする - for (auto ev : all_events) { + for (auto *ev : evman->all) { if (ev->active) { - StopEvent(*ev); + StopEvent(ev); } ev->count = 0; } @@ -248,8 +177,8 @@ Scheduler::StartTime() // 時間をリセットする。 // 呼び出しの依存関係に注意。 vtime = 0; // required by Sync - gSync->StartTime(); // required by RTC - gRTC->StartTime(); + syncer->StartTime(); // required by RTC + GetRTCDevice()->StartTime(); } // スレッド終了指示 @@ -257,31 +186,19 @@ Scheduler::StartTime() void Scheduler::Terminate() { - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); request |= REQUEST_EXIT; cv.notify_one(); } -// イベントを登録する。 -// 登録時点では実は type だけ確定していればよい。 -// -// ここで管理してるのはポインタだけで、実体は呼び出し元クラスのメンバ -// インスタンスのはずなので、登録しっぱなしにする。 -void -Scheduler::RegistEvent(Event& ev) -{ - // イベントリストに追加 - all_events.push_back(&ev); -} - // 指定のイベントを開始する。 // すでに同イベントが登録されている場合は古いイベントを削除してから // 新しいイベントを再登録となる。 // イベントはワンショットのみ。 void -Scheduler::RestartEvent(Event& ev) +Scheduler::RestartEvent(Event *ev) { - if (ev.IsRunning()) { + if (ev->IsRunning()) { StopEvent(ev); } @@ -292,40 +209,41 @@ Scheduler::RestartEvent(Event& ev) // このイベントが登録されてないことが確定できる場合のみこちらが使える。 // イベントはワンショットのみ。 void -Scheduler::StartEvent(Event& ev) +Scheduler::StartEvent(Event *ev) { CT(start); #ifdef CT_ON if (ct_start % 100'000'000 == 0) { - printf("get*=%lu [%lu, %lu]\n", + printf("get*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", (ct_get1 + ct_get2), ct_get1, ct_get2); - printf("start*=%lu [%lu, %lu, %lu, %lu]\n", + printf("start*=%" PRIu64 + " [%" PRIu64 ", %" PRIu64 ", %" PRIu64 ", %" PRIu64 "]\n", ct_start, ct_start1, ct_start2, ct_start3, ct_start4); - printf("stop*=%lu [%lu, %lu]\n", + printf("stop*=%" PRIu64 " [%" PRIu64 ", %" PRIu64 "]\n", (ct_stop1 + ct_stop2), ct_stop1, ct_stop2); - printf("eslow=%lu sslow=%lu pslow=%lu\n", + printf("eslow=%" PRIu64 " sslow=%" PRIu64 " pslow=%" PRIu64 "\n", ct_eslow, ct_sslow, ct_pslow); - printf("slow_top=%d vtime=%lu\n", slow_top, slow_top_vtime); + 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()); + //assertmsg(ev->IsRunning() == false, "%s", ev->GetName().c_str()); - // vtime は仮想絶対時刻 [nsec]、time は仮想相対時間 [nsec] - ev.vtime = vtime + ev.time; + // vtime は仮想絶対時刻 [tsec]、time は仮想相対時間 [tsec] + ev->vtime = vtime + ev->time; - ev.active = true; + ev->active = true; - if (__predict_true(ev.vtime <= slow_top_vtime)) { + if (__predict_true(ev->vtime <= slow_top_vtime)) { if (__predict_true(fast == NULL)) { CT(start1); - fast = &ev; + fast = ev; } else { - if (__predict_false(ev.vtime <= fast->vtime)) { + if (__predict_false(ev->vtime <= fast->vtime)) { CT(start2); - PushSlow(*fast); - fast = &ev; + PushSlow(fast); + fast = ev; } else { CT(start3); PushSlow(ev); @@ -341,11 +259,11 @@ Scheduler::StartEvent(Event& ev) // rt_now はイベント発行者の実時間での現在時刻で、 // rt_period は次回のイベントまでの実時間間隔。 void -Scheduler::StartRealtimeEvent(Event& ev, uint64 rt_now, uint64 rt_period) +Scheduler::StartRealtimeEvent(Event *ev, uint64 rt_now, uint64 rt_period) { // 実時間をもとに仮想時間軸上の推定を行う。 - uint64 rtime = gSync->GetRealTime(); + uint64 rtime = syncer->GetRealTime(); if (rtime > rt_now + rt_period) { // 実時間がさらに 1 period 以上進んでいる。 @@ -353,16 +271,16 @@ Scheduler::StartRealtimeEvent(Event& ev, // 周期を短くして回復運転させる。 // 周期を短くしすぎるとゲストが処理できなくなるので、 // 10% カットに留める。 - ev.time = rt_period - rt_period / 10; + ev->time = rt_period - rt_period / 10; } else if (rtime < rt_now - rt_period) { // イベントがさらに 1 period 以上進んでいる。 // 実時間を追い越してしまっているので、イベントの // 周期を長くして待つ。長くする方はいくらでも長くしていいので、 // 連続した場合はどんどん長くしていく。 - ev.time += rt_period; + ev->time += rt_period; } else { // おおむね同期している - ev.time = rt_period; + ev->time = rt_period; } RestartEvent(ev); @@ -370,7 +288,7 @@ Scheduler::StartRealtimeEvent(Event& ev, // ev は slow_top には来ない前提 void -Scheduler::EnqueueSlow(Event& ev) +Scheduler::EnqueueSlow(Event *ev) { CT(eslow); @@ -378,33 +296,33 @@ Scheduler::EnqueueSlow(Event& ev) slow[++slow_top] = slow[s]; for (; --s >= 0; ) { - if (ev.vtime <= slow[s]->vtime) { - slow[s + 1] = &ev; + if (ev->vtime <= slow[s]->vtime) { + slow[s + 1] = ev; return; } else { slow[s + 1] = slow[s]; } } - slow[0] = &ev; + slow[0] = ev; } void -Scheduler::PushSlow(Event& ev) +Scheduler::PushSlow(Event *ev) { CT(pslow); - slow[++slow_top] = &ev; - slow_top_vtime = ev.vtime; + slow[++slow_top] = ev; + slow_top_vtime = ev->vtime; } // 指定のイベントを停止する。 // 指定されたイベントが登録されていなければ何もしない。 void -Scheduler::StopEvent(Event& ev) +Scheduler::StopEvent(Event *ev) { - ev.active = false; + ev->active = false; - if (__predict_false(&ev == fast)) { + if (__predict_false(ev == fast)) { CT(stop1); fast = NULL; } else { @@ -414,14 +332,14 @@ Scheduler::StopEvent(Event& ev) } void -Scheduler::StopSlowEvent(Event& ev) +Scheduler::StopSlowEvent(Event *ev) { CT(sslow); int s = slow_top; for (; s >= 0; s--) { - if (&ev == slow[s]) { + if (ev == slow[s]) { for (; s < slow_top; s++) { slow[s] = slow[s + 1]; } @@ -440,8 +358,11 @@ Scheduler::StopSlowEvent(Event& ev) void Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func) { + assert(msgid < MessageID::MAX); + auto& mh = message_handlers[msgid]; - assert(mh.dev == NULL); + assertmsg(mh.dev == NULL, + "%s: msgid=%d connected again", __func__, (int)msgid); mh.dev = dev; mh.func = func; } @@ -456,27 +377,46 @@ Scheduler::DispatchMessage() MessageID msgid = (MessageID)(msg & 0xff); uint32 arg = (msg >> 32); - assert(0 <= msgid && msgid < MessageID::MAX); - - auto& mh = message_handlers[msgid]; - if (mh.dev != NULL) { - ((mh.dev)->*(mh.func))(msgid, arg); + // 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); } } } +// メッセージハンドラを呼び出す。登録されてなければ何もしない。 +void +Scheduler::InvokeMessage(MessageID msgid, uint32 arg) +{ + auto h = message_handlers[msgid]; + if (h.dev != NULL) { + (h.dev->*(h.func))(msgid, arg); + } +} + // メッセージを送る。 // VM スレッド以外から呼び出しても良い。 void Scheduler::SendMessage(MessageID msgid, uint32 arg) { - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); - uint64 msg = (uint64)msgid | (((uint64)arg) << 32); - if (msgq.Enqueue(msg) == false) { - putlog(0, "Message queue exhausted !!"); + if (msgid < MessageID::MAX_REQUEST) { + request |= 1U << (uint)msgid; + } else { + uint64 msg = (uint64)msgid | (((uint64)arg) << 32); + if (msgq.Enqueue(msg) == false) { + putlog(0, "Message queue exhausted !! msgid=%d", (int)msgid); + } + request |= REQUEST_MESSAGE; } - request |= REQUEST_MESSAGE; cv.notify_one(); } @@ -492,22 +432,18 @@ Scheduler::Sleep(uint64 time) } void -Scheduler::MonitorUpdate(Monitor *, TextScreen& screen) +Scheduler::MonitorScreen(Monitor *, TextScreen& screen) { int y; screen.Clear(); - y = gSync->MonitorUpdateSub(screen, vtime); - - int clock_khz = gMPU->GetClockSpeed(); - screen.Print(0, y++, "MPU Speed: %d.%03dMHz", - (clock_khz / 1000), (clock_khz % 1000)); + y = syncer->MonitorScreenSub(screen, vtime); // 0 1 2 3 // 012345678901234567890123456789 - // Set Time Remain Time - // 3.123'456'789 3.123'456'789 + // Event Time Remain Time + // 3.123'456'789 3.123'456'789 // // 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789 @@ -521,10 +457,12 @@ Scheduler::MonitorUpdate(Monitor *, Text screen.Puts(75, y, "Count"); y++; - for (const auto ev : all_events) { + for (auto *ev : evman->all) { uint64 rem; TA attr; + if (ev->IsRunning()) { + attr = TA::Normal; if (ev->vtime > vtime) { rem = ev->vtime - vtime; } else { @@ -535,14 +473,13 @@ Scheduler::MonitorUpdate(Monitor *, Text rem = 0; } } else { + attr = TA::Disable; rem = 0; } if (ev->count != ev->last_count) { attr = TA::Normal; ev->last_count = ev->count; - } else { - attr = TA::Disable; } // 先にイベント回数を表示。 @@ -550,7 +487,7 @@ Scheduler::MonitorUpdate(Monitor *, Text // 大勢に影響はないだろう。 std::string countstr = format_number(ev->count); screen.Print(54, y, attr, "%26s", countstr.c_str()); - screen.Print(0, y, attr, "%13s %13s %s ", + screen.Print(0, y, attr, "%14s %14s %s ", SecToStr(ev->time).c_str(), SecToStr(rem).c_str(), ev->GetName().c_str());