--- nono/vm/scheduler.h 2026/04/29 17:04:56 1.1.1.9 +++ nono/vm/scheduler.h 2026/04/29 17:06:01 1.1.1.17 @@ -4,236 +4,115 @@ // Licensed under nono-license.txt // +// +// スケジューラ +// + #pragma once -#include "device.h" -#include "event.h" -#include "eventqueue.h" -#include "fixedqueue.h" -#include "monitor.h" -#include "mpu.h" -#include "statistics.h" -#include "stopwatch.h" -#include +#include "thread.h" +#include "message.h" +#include "spscqueue.h" +#include #include -#include #include -#include + +class EventManager; +class Syncer; // スケジューラ -class Scheduler : public Device +class Scheduler : public ThreadDevice { - using inherited = Device; - - public: - // 動作モード - // 動作モード変数 (mode) はスケジューラの走行状態を表している。以下の - // ビットがすべて %0 なら高速モード、どれかでも %1 なら通常モードになる。 - // - // | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | - // mode | |KEY |BOOT|SYNC| CPUSTAT | - // | | | +---+---- CPU の状態 - // | | +-------------- 高速モード抑制 - // | +------------------- ブートページ実行中 - // +------------------------ キー入力中 - // - // bit1,0 (CPUSTAT) は CPU の状態を示す。%00 の時だけ高速走行が可能。 - // これは MPUDevice::Run() の戻り値でもある。 - // b1 b0 - // 0 0 通常状態 - // 0 1 STOP 状態 (m88k なら擬似 STOP 状態) - // 1 0 HALT 状態 (ダブルバスフォールト) - // 1 1 HALT 状態だがこちらのビットパターンは使わない - // - // bit2 (SYNC) が UI からの高速モード指示。%0 なら高速、%1 なら通常。 - // なのでどっちかというと高速モード抑制指示というほうがいいかも。 - - static const uint32 SCHED_CPU_STOP = 0x0001; // CPU が STOP 状態 - static const uint32 SCHED_CPU_HALT = 0x0002; // CPU が HALT 状態 - static const uint32 SCHED_SYNC = 0x0004; // 高速モード抑制指示(UI) - static const uint32 SCHED_BOOT = 0x0008; // ブートページ実行中 - static const uint32 SCHED_KEY = 0x0010; // キー入力中 - - // 便宜上用意しておく - static const uint32 SCHED_CPU_NORMAL= 0x0000; // CPU が通常状態 - static const uint32 SCHED_CPU_MASK = (SCHED_CPU_STOP | SCHED_CPU_HALT); + using inherited = ThreadDevice; private: // リクエストフラグ - // REQ_CPU_MODE は CPU mode が何らか変化することのフラグ。どの状態に変化 - // するかは new_cpumode 変数で示す。1回のループ期間中に複数回の変化が - // 起きても最終状態だけに変化するため。 - // REQ_USER_MODE と new_usermode も同様。 - static const uint REQ_CPU_MODE = 0x0001; // CPU 状態を変更 - static const uint REQ_USER_MODE = 0x0002; // 同期/高速モード変更 - static const uint REQ_BOOT_MODE = 0x0004; // ブートページモード変更 - static const uint REQ_KEY_MODE = 0x0008; // キー入力モード変更 - static const uint REQ_POWER_ON = 0x0010; // 電源オン - static const uint REQ_POWER_OFF = 0x0020; // 電源オフ - static const uint REQ_EXIT = 0x0040; // アプリケーション終了 - static const uint REQ_RESET = 0x0080; // ハードウェアリセット - static const uint REQ_RESTART = 0x0100; // 電源入れ直し + static const uint32 REQUEST_EXIT = 0x00000001; // 終了要求 + static const uint32 REQUEST_MESSAGE = 1U << MessageID::MAX_REQUEST; + + // メッセージハンドラを覚えておくための構造体 + struct MessageHandler + { + Device *dev {}; + MessageCallback_t func {}; + }; public: Scheduler(); - virtual ~Scheduler() override; + ~Scheduler() override; bool Init() override; + bool Init2(); - // スレッド開始 - void ThreadRun(); - - // スレッド終了 - void Terminate(); + void StartTime(); - // スレッド終了を待機する - void Wait(); + // スレッド終了指示 + void Terminate() override; - // スレッド終了を指示する - void RequestExit(); - - // 電源オン要求 - // 当然電源オフ時に呼ぶので VM スレッド外から呼ばれることになる。 - void RequestPowerOn() { - atomic_reqflag |= REQ_POWER_ON; - } - - // 電源オフ要求 - // → ここでフラグを立ててスケジューラスレッドに通知、 - // → スケジューラスレッドが各デバイスの PowerOff() をコール、 - // → 電源オフ状態に移行、 - // → (コールバックが登録されていれば) UI スレッドに通知。 - void RequestPowerOff() { - atomic_reqflag |= REQ_POWER_OFF; - } - - // リセット要求 - void RequestResetHard() { - atomic_reqflag |= REQ_RESET; - } - - // 電源入れ直し要求 - void RequestRestartVM() { - atomic_reqflag |= REQ_RESTART; - } - - // 現在の実行モードを返す。 - uint32 GetRunMode() const { return mode; } - - // 電源オンなら true を返す。 - bool IsPower() const { return ispower; } - - // 高速モード(UIによる指示)なら true を返す。 - // ここでいう高速モードは STOP 状態中でも高速モード。 - bool GetFullSpeed() const { return ((mode & SCHED_SYNC) == 0); } - - // 高速モードを指示する。 - // true なら高速モード、false なら同期モード。 - void SetFullSpeed(bool enable); + // 現在のマスター仮想時間を返す + uint64 GetVirtTime() const { return vtime; } - // ブートページモードの設定。 - void RequestBootPageMode(bool isrom); + // イベントを開始する + void StartEvent(Event *ev); + void RestartEvent(Event *ev); - // キー入力中モードの設定。 - void RequestKeyPressed(bool pressed); + // 実時間間隔を指定してイベントを開始する。 + // rt_now はイベント発行者の実時間での現在時刻で、 + // rt_period は次回のイベントまでの実時間間隔。 + void StartRealtimeEvent(Event *ev, uint64 rt_now, uint64 rt_period); - // イベントを開始する - void StartEvent(Event *newev); // イベントを停止する - void StopEvent(Event *event); + void StopEvent(Event *ev); - // VM 電源オフ時のコールバックを設定する。(GUI から呼ばれる) - void SetPowerOffCallback(void (*callback)()); + // メッセージハンドラを登録する + void ConnectMessage(MessageID, Device *, MessageCallback_t); - // パフォーマンスカウンタの値取得 (UI 表示用) - int GetPerfCounter() const { return perf_counter; } + // メッセージを送る。VM スレッド以外からも呼び出して良い。 + void SendMessage(MessageID, uint32 arg = 0); - EventQueue eventq {}; // 有効なイベントキュー - std::mutex evcs {}; // イベントリストのロック + // 指定時間が経過するか、リクエストが起きるまでスリープ + void Sleep(uint64 time); private: - // 同期イベント - void SyncCallback(Event& ev); - - DECLARE_MONITOR_CALLBACK(MonitorUpdate); - - // メインループ - bool RunOff(); - bool RunOn(); + // スレッド + void ThreadRun() override; - // 同期処理 - uint64 Sync(); + void EnqueueSlow(Event *); + void PushSlow(Event *); + void StopSlowEvent(Event *); - // RTC を進める。 - void RunRTC(); + // メッセージディスパッチ + void DispatchMessage(); + void InvokeMessage(MessageID, uint32); - // REQ_* フラグのデバッグ表示 - void PutlogReqFlag(uint32 req) const; + DECLARE_MONITOR_SCREEN(MonitorScreen); - pthread_t thread {}; - bool thread_created {}; // スレッドが作成されたら true + // 有効なイベントキュー + uint64 slow_top_vtime {}; + Event *fast {}; + int slow_top {}; + std::array slow {}; - // スケジューラスレッドに処理を要求する場合はこのフラグを立てる - std::atomic atomic_reqflag {}; + // 現在の仮想経過時間 + uint64 vtime {}; - // 電源オンなら true - bool ispower {}; + // 外部スレッドからのリクエスト + uint32 request {}; + std::mutex mtx {}; + std::condition_variable cv {}; - // スケジューラの動作モード。SCHED_* - uint32 mode {}; + // メッセージハンドラリスト + std::array message_handlers {}; - // REQ_CPU_MODE フラグで変更するモード - uint32 new_cpumode {}; + // メッセージキュー + SPSCQueue msgq {}; - // REQ_USER_MODE フラグで変更するモード - uint32 new_usermode {}; + EventManager *evman {}; + Syncer *syncer {}; - // REQ_BOOT_MODE フラグで変更するモード - uint32 new_bootmode {}; - - // REQ_KEY_MODE フラグで変更するモード - uint32 new_keymode {}; - - uint64 rtime {}; // 現在の実経過時間 - uint64 vtime {}; // 現在の仮想経過時間 - - // 同期調整用 - uint64 rtime_epoch {}; - uint64 vtime_epoch {}; - - // sleep しすぎた時間の最大値 - uint64 overslept {}; - - // 同期回数 - uint sync_count {}; // 現在の1秒間での同期回数 - uint last_sync_count {}; // 前回の1秒間での同期回数 - - // 次回 RTC へ 32Hz 入力パルスを入れる予定の実経過時間 - uint64 next_rtc_rtime {}; - - // パフォーマンスカウンタ - // perfq が移動平均用のバッファ。四捨五入用に10倍値を持っておく - // (100% なら 1000)。perf_counter は perfq から求めた実行率で - // 小数以下を四捨五入する (のでこっちは 100% なら 100)。 - int perf_counter {}; // 実行率 [%] - FixedQueue perfq {}; // 移動平均用のバッファ - uint64 next_perf_rtime {}; // 次回測定予定の実経過時間 - uint64 last_perf_rtime {}; // 前回測定時の実経過時間 - uint64 last_perf_vtime {}; // 前回測定時の仮想経過時間 - - Statistics last_event_stat {}; // 直近実1秒のイベント統計 - - // 同期イベント - Event sync_event { this }; - - Monitor monitor { this }; - - // 電源オフを UI に通知するためのコールバック - void (*poweroff_callback)() {}; + Monitor *monitor {}; }; -extern const std::string TimeToStr(uint64 t); - -extern Stopwatch gRealtime; - -extern std::unique_ptr gScheduler; +inline Scheduler *GetScheduler() { + return Object::GetObject(OBJ_SCHEDULER); +}