--- nono/vm/scheduler.cpp 2026/04/29 17:04:36 1.1.1.4 +++ nono/vm/scheduler.cpp 2026/04/29 17:04:50 1.1.1.8 @@ -16,10 +16,8 @@ // VM スケジューラは、仮想時間とイベントを管理する。 // -// 仮想時間は MPU の起動時からのクロックサイクルの経過数で表すこととする。 -// このエミュレータはシングルプロセッサシステムのみがターゲットであり、 -// 実行中に動作クロックが変わることはないので、これで問題ない。 -// VM 界からは gMPU->total_cycle() で取得する。 +// 仮想時間は起動時からの nsec で表し、MPU デバイス (実際はその向こうの +// CPU コア) が管理している。VM 界からは gMPU->GetVirtTime() で取得する。 static void *scheduler_run(void *arg); @@ -30,7 +28,6 @@ Scheduler::Scheduler() { logname = "scheduler"; devname = "Scheduler"; - monitor.Init(68, 30); eventlist.clear(); } @@ -42,36 +39,9 @@ Scheduler::~Scheduler() } // 初期化 -// ここで 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(); - return false; - } - // 起動時引数指定の高速モード SetFullSpeed(gMainApp.fast_mode); @@ -105,7 +75,6 @@ scheduler_run(void *arg) void Scheduler::ThreadRun() { - uint64 cycle; // 今回実行するサイクル数 uint32 req; // 最初の電源オンを待つ @@ -117,8 +86,8 @@ Scheduler::ThreadRun() if ((req & REQ_EXIT)) { return; } - // REQ_POWER_ON なら他のフラグを書き戻して抜ける。 - // REQ_{SYNC|FAST} は以降のメインループで処理するため。 + // REQ_POWER_ON ならこれだけ下げて抜ける。 + // 他のフラグはメインループで処理するため維持。 if ((req & REQ_POWER_ON)) { req &= ~REQ_POWER_ON; atomic_reqflag.store(req); @@ -131,43 +100,49 @@ Scheduler::ThreadRun() rtimestart = GetRealTime(); rtc_last_clock = rtimestart; - cycle = INT64_MAX; mode = 0; for (;;) { req = atomic_reqflag.exchange(0); - if (req) { - if ((req & REQ_SYNC)) { - // スケジューラを同期モードに - mode |= SCHED_SYNC; - } - if ((req & REQ_FAST)) { - // スケジューラを高速モードに - mode &= ~SCHED_SYNC; - } - if ((req & REQ_RUN)) { - // MPU が通常状態になった - mode &= ~SCHED_STOP; - } - if ((req & REQ_STOP)) { - // MPU が STOP 状態になった - mode |= SCHED_STOP; - } - if ((req & REQ_MODEMASK)) { - // mode がどれかにでも変わったら - static const char *modestr[] = { - "高速", - "高速 & STOP状態", - "通常", - "通常 & STOP状態", - }; - putlog(1, "モード変更 mode=%d (%s)", mode, modestr[mode]); + 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 = GetVirtTime(); - - // 該当ビットを落としておく - req &= ~REQ_MODEMASK; + vtimebase = gMPU->GetVirtTime(); } if ((req & REQ_POWER_OFF)) { @@ -179,6 +154,15 @@ Scheduler::ThreadRun() req &= ~REQ_POWER_OFF; } + if ((req & REQ_RESET)) { + // リセット要求の場合 + + // 全デバイスをリセット + gVM->ResetHard(); + + req &= ~REQ_RESET; + } + if ((req & REQ_EXIT)) { // 終了要求 // この for ブロックをいきなり抜けるだけなので @@ -198,57 +182,72 @@ Scheduler::ThreadRun() rtc_last_clock += 31.25_msec; } - // まず CPU を駆動する。MPU::Run() はデバイスアクセスなどによって - // スケジューラにイベントが登録されると、その命令の終了とともに - // 処理を打ち切って戻ってくる。 - // ここでイベントリストを調べ、その時点から最も早く発生するイベント - // までの必要サイクル数が、次のループでの駆動サイクル数になる。 - - // CPU 駆動 - uint32 outer; - outer = gMPU->Run(cycle); + // イベントリストを調べ現時点から最も早く発生するイベントまでの必要 + // 時間を求めて、この時間分だけ 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 (;;) { - evcs.lock(); - if (eventlist.empty()) { - cycle = INT64_MAX; - break; + 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(); } - Event *e = eventlist.front(); - if (gMPU->total_cycle() < e->cycle) { - // まだ時刻に到達していない - cycle = e->cycle - gMPU->total_cycle(); - break; - } - - // 到達したのでこのイベントをリストから削除 - e->active = false; - eventlist.pop_front(); - evcs.unlock(); // コールバック - e->dev->putlog(3, "イベント '%s' 時刻到達", e->GetName().c_str()); - ((e->dev)->*(e->func))(e->code); + e->dev->putlogf(3, lstr("イベント '%s' 時刻到達", + e->GetName().c_str())); + ((e->dev)->*(e->func))(*e); // 同時刻のイベントがあるかも知れないのでなくなるまで調べる } - evcs.unlock(); + + // CPU を駆動 + new_cpumode = gMPU->Run(delta_vtime); + + // CPU 駆動によって進んだ仮想時刻を再取得 + vtime = gMPU->GetVirtTime(); // 同期モードなら実時間調整。 // あるいは高速モードであってもストップ状態なら実時間駆動する。 if (mode != 0) { // 通常モード - uint64 vtime; - uint64 rtime; + uint64 vspan; + uint64 rspan; // 基準時からの経過時間 - vtime = GetVirtTime() - vtimebase; - rtime = GetRealTime() - rtimebase; + vspan = vtime - vtimebase; + rspan = GetRealTime() - rtimebase; - if (vtime > rtime) { + if (vspan > rspan) { // 仮想時間のほうが進んでいれば、スリープして待つ - uint64 diff = vtime - rtime; + uint64 diff = vspan - rspan; struct timespec ts; ts.tv_sec = diff / (1000 * 1000 * 1000); ts.tv_nsec = diff % (1000 * 1000 * 1000); @@ -260,16 +259,9 @@ Scheduler::ThreadRun() } } - // 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; - } + // CPU 側の走行状態が変わったら、このループに通知 + if (__predict_false(((mode ^ new_cpumode) & SCHED_CPU_MASK))) { + atomic_reqflag |= REQ_CPU_MODE; } } } @@ -279,22 +271,32 @@ void Scheduler::Terminate() { if (thread_created) { - atomic_reqflag |= REQ_EXIT; + RequestExit(); pthread_join(thread, NULL); thread_created = false; } } +// スレッド終了を指示する。 +void +Scheduler::RequestExit() +{ + std::unique_lock lock(cvmtx); + atomic_reqflag |= REQ_EXIT; + cv.notify_one(); +} + // 動作モードを設定する。 // true なら高速モード、false なら同期モード。 void Scheduler::SetFullSpeed(bool enable) { if (enable) { - atomic_reqflag |= REQ_FAST; + new_usermode = 0; } else { - atomic_reqflag |= REQ_SYNC; + new_usermode = SCHED_SYNC; } + atomic_reqflag |= REQ_USER_MODE; } // 指定のイベントを開始する。 @@ -307,8 +309,8 @@ Scheduler::StartEvent(Event *newev) bool inserted = false; bool updated = false; - // 相対 time から絶対 cycle を計算 - newev->cycle = gMPU->total_cycle() + Vtime2Cycle(newev->time); + // time は仮想相対時間 [nsec]、vtime は仮想絶対時刻 [nsec] + newev->vtime = gMPU->GetVirtTime() + newev->time; evcs.lock(); // すでにあれば削除 @@ -325,7 +327,7 @@ Scheduler::StartEvent(Event *newev) // ソートされているところに自身を挿入 for (auto it = eventlist.begin(); it != eventlist.end(); ++it) { Event *e = *it; - if (newev->cycle <= e->cycle) { + if (newev->vtime <= e->vtime) { // このイベントの前に入れる eventlist.insert(it, newev); inserted = true; @@ -341,11 +343,11 @@ Scheduler::StartEvent(Event *newev) gMPU->Release(); - newev->dev->putlog(3, "イベント '%s' %s %d.%03d usec 後", + newev->dev->putlogf(3, lstr("イベント '%s' %s %d.%03d usec 後", newev->GetName().c_str(), updated ? "更新" : "開始", (int)(newev->time / 1000), - (int)(newev->time % 1000)); + (int)(newev->time % 1000))); } // 指定のイベントを停止する。 @@ -373,7 +375,8 @@ Scheduler::StopEvent(Event *event) // そのまま 1msec 走って問題ない。 if (found) { - event->dev->putlog(3, "イベント '%s' 停止", event->GetName().c_str()); + event->dev->putlogf(3, lstr("イベント '%s' 停止", + event->GetName().c_str())); } } @@ -420,8 +423,17 @@ TimeToStr(uint64 t) return std::string(buf, p - buf); } -bool -Scheduler::MonitorUpdate() +// モニターサイズ取得 +nnSize +Scheduler::GetMonitorSize() +{ + // 本当は Init() 完了時点でイベント個数は確定しているはずだけど、 + // そのタイミングで一度だけ呼ばれるフックが今の所ない。 + return nnSize(68, 11 + gEvents.size()); +} + +void +Scheduler::MonitorUpdate(TextScreen& monitor) { int x, y; @@ -429,28 +441,45 @@ Scheduler::MonitorUpdate() x = 0; y = 0; - monitor.Print(0, y++, "Mode: %s %s", - (mode & SCHED_SYNC) ? "Sync" : "Full", - (mode & SCHED_STOP) ? "STOP" : "Run "); + int clock_khz = gMPU->GetClockSpeed(); + + 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", - (mpu_clock / 1000), (mpu_clock % 1000)); - uint64 cycle = gMPU->total_cycle(); - uint64 vt = GetVirtTime(); - uint64 rt = GetRealTime(); - uint64 relvt = vt - vtimebase; - uint64 relrt = rt - rtimebase; + (clock_khz / 1000), (clock_khz % 1000)); + uint64 vtime = gMPU->GetVirtTime(); + uint64 rtime = GetRealTime(); + uint64 vspan = vtime - vtimebase; + uint64 rspan = rtime - 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; + 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(relrt).c_str()); - monitor.Print(0, y++, " Virtual Time: %18s", TimeToStr(relvt).c_str()); + 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)relvt / relrt * 100; + double relratio = (double)vspan / rspan * 100; monitor.Print(0, y++, " Ratio : %3d.%01d%%", (int)relratio, ((int)(relratio * 10) % 10)); @@ -465,12 +494,21 @@ Scheduler::MonitorUpdate() monitor.Puts(x + 32, y, "Description"); monitor.Puts(x + 58, y, "Code"); y++; + for (const auto& e : gEvents) { uint64 rem; TA attr; if (e->active) { attr = TA::Normal; - rem = Cycle2Vtime(e->cycle - cycle); + if (e->vtime > vtime) { + rem = e->vtime - vtime; + } else { + // XXX 実際には起きないけど、この表示をするにあたって + // スケジューラスレッドと一切調停していないので、仮想時刻が + // e->cycle (イベント発生時刻) をすぎていることがある。 + // すぎてるので残り時間 0 と表示しておく。 + rem = 0; + } } else { attr = TA::Disable; rem = 0; @@ -488,6 +526,4 @@ Scheduler::MonitorUpdate() e->GetName().c_str(), e->code); } - - return true; }