File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / scheduler.cpp
Revision 1.1.1.22 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:01 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// スケジューラ
//

// VM スケジューラは、仮想時間とイベントを管理する。
// 仮想時間はすべて tsec で表し、StartTime() から単調増加する。
// VM 界からは仮想時間は gScheduler->GetVirtTime() で取得する。
//
// 実時間は Syncer が管理し、こちらも tsec で表す。
// このストップウォッチも StartTime() から単調増加する。
// 実時間、仮想時間とも電源オフ中でも単調増加することに注意。
//
// 「実経過時間」は gRealtime.Elapsed_nsec() で取得できるが tsec で保持する。
// o (たぶん) Scheduler スレッド開始時から、常に増加。
// o (たぶん)ホストの時刻変更やサスペンドの影響を受けず、常に増加。
// o デバッガプロンプトで停止中は進行が停止する。
// という性質を持つ。
//
// 同期走行モードにおいては rtime_epoch, vtime_epoch をそれぞれの基準点とし
// これらと現在の実経過時間、仮想経過時間の差が同期するよう調整する。
// 高速走行モードにおいてはこれらは必要ないので特に参照しない。
//
// 一定間隔で行うパフォーマンス測定は、前回計測時の実および仮想経過時間を
// last_perf_rtime, last_perf_vtime として持っているので、これと現在の
// 経過時間との比で求める。同期走行/高速走行の影響は受けない。

#include "scheduler.h"
#include "event.h"
#include "monitor.h"
#include "rtc.h"
#include "syncer.h"

//#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::Scheduler()
	: inherited(OBJ_SCHEDULER)
{
	// オブジェクト名は Scheduler だがここのスレッド名は VM くらいのほうが
	// 通りがよさそうだ。
	SetThreadName("VM");

	slow_top = -1;
	slow_top_vtime = UINT64_MAX;

	monitor = gMonitorManager->Regist(ID_MONITOR_SCHEDULER, this);
	monitor->SetCallback(&Scheduler::MonitorScreen);
	// サイズは最終的に Init2() で決まる。
	monitor->SetSize(80, 9);
}

// デストラクタ
Scheduler::~Scheduler()
{
	TerminateThread();
}

// 初期化
bool
Scheduler::Init()
{
	evman = GetEventManager();
	syncer = GetSyncer();

	return true;
}

// 初期化(VM から呼ばれる)
bool
Scheduler::Init2()
{
	// イベントの個数が確定した(してなければならない)
	monitor->AddHeight(evman->all.size());

	return true;
}

// スレッドエントリ関数
void
Scheduler::ThreadRun()
{
	SetThreadAffinityHint(AffinityClass::Heavy);

	// 電源オンオフに関わらず、ここが rtime, vtime の基準点
	StartTime();

	for (;;) {
		// 条件変数の正しい使い方としては request の参照にも mtx ロックが必要
		// だが、ここでロック取らずに参照したことによって request が立った
		// ことを見落としても、ループ一回回ってもう一度ここに来るだけなので
		// たぶん致命的なことはないはず。
		if (__predict_false(request)) {
			uint32 req;
			{
				std::lock_guard<std::mutex> lock(mtx);
				req = request;
				request = 0;
			}
			if ((req & REQUEST_EXIT)) {
				break;
			}

			for (; req; req &= req - 1) {
				uint id = __builtin_ctz(req);
				if (id < MessageID::MAX_REQUEST) {
					InvokeMessage(id, 0);
				} else {
					DispatchMessage();
				}
			}
		}

		// 先頭のイベントを取得して停止
		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;

		// 時刻更新
		vtime = ev->vtime;

		// コールバック
		ev->count++;
		auto dev  = ev->GetOwner();
		auto func = ev->GetCallback();
		(dev->*(func))(ev);
	}
}

// 時間の始まり
void
Scheduler::StartTime()
{
	// イベントキューを空にする
	for (auto *ev : evman->all) {
		if (ev->active) {
			StopEvent(ev);
		}
		ev->count = 0;
	}

	// 時間をリセットする。
	// 呼び出しの依存関係に注意。
	vtime = 0;					// required by Sync
	syncer->StartTime();		// required by RTC
	GetRTCDevice()->StartTime();
}

// スレッド終了指示
// (当然他スレッドから呼ぶことになる)
void
Scheduler::Terminate()
{
	std::lock_guard<std::mutex> lock(mtx);
	request |= REQUEST_EXIT;
	cv.notify_one();
}

// 指定のイベントを開始する。
// すでに同イベントが登録されている場合は古いイベントを削除してから
// 新しいイベントを再登録となる。
// イベントはワンショットのみ。
void
Scheduler::RestartEvent(Event *ev)
{
	if (ev->IsRunning()) {
		StopEvent(ev);
	}

	StartEvent(ev);
}

// 指定のイベントを開始する。
// このイベントが登録されてないことが確定できる場合のみこちらが使える。
// イベントはワンショットのみ。
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 は仮想絶対時刻 [tsec]、time は仮想相対時間 [tsec]
	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);
	}
}

// 実時間間隔を指定してイベントを開始する。
// rt_now はイベント発行者の実時間での現在時刻で、
// rt_period は次回のイベントまでの実時間間隔。
void
Scheduler::StartRealtimeEvent(Event *ev, uint64 rt_now, uint64 rt_period)
{
	// 実時間をもとに仮想時間軸上の推定を行う。

	uint64 rtime = syncer->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];
		}
	}
	slow[0] = ev;
}

void
Scheduler::PushSlow(Event *ev)
{
	CT(pslow);

	slow[++slow_top] = ev;
	slow_top_vtime = ev->vtime;
}

// 指定のイベントを停止する。
// 指定されたイベントが登録されていなければ何もしない。
void
Scheduler::StopEvent(Event *ev)
{
	ev->active = false;

	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;
		}
	}
}

// メッセージハンドラを登録する。
void
Scheduler::ConnectMessage(MessageID msgid, Device *dev, MessageCallback_t func)
{
	assert(msgid < MessageID::MAX);

	auto& mh = message_handlers[msgid];
	assertmsg(mh.dev == NULL,
		"%s: msgid=%d connected again", __func__, (int)msgid);
	mh.dev = dev;
	mh.func = func;
}

// メッセージをディスパッチして、登録されていればハンドラを呼び出す。
void
Scheduler::DispatchMessage()
{
	uint64 msg;

	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);
		}
	}
}

// メッセージハンドラを呼び出す。登録されてなければ何もしない。
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::lock_guard<std::mutex> lock(mtx);

	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;
	}
	cv.notify_one();
}

// 指定時間が経過するか、リクエストが起きるまでスリープ
void
Scheduler::Sleep(uint64 time)
{
	std::unique_lock<std::mutex> lock(mtx);
	cv.wait_for(lock, std::chrono::nanoseconds(time), [&] {
		return (request != 0);
	});
	// リクエストフラグが立ってもここでは何もしない
}

void
Scheduler::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y;

	screen.Clear();

	y = syncer->MonitorScreenSub(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++;
	screen.Puts(0, y, "Event Time");
	screen.Puts(15, y, "Remain Time");
	screen.Puts(30, y, "Description");
	screen.Puts(75, y, "Count");
	y++;

	for (auto *ev : evman->all) {
		uint64 rem;
		TA attr;

		if (ev->IsRunning()) {
			attr = TA::Normal;
			if (ev->vtime > vtime) {
				rem = ev->vtime - vtime;
			} else {
				// XXX 実際には起きないけど、この表示をするにあたって
				// スケジューラスレッドと一切調停していないので、仮想時刻が
				// ev->vtime (イベント発生時刻) をすぎていることがある。
				// すぎてるので残り時間 0 と表示しておく。
				rem = 0;
			}
		} else {
			attr = TA::Disable;
			rem = 0;
		}

		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++;
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.