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

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

//
// スレッドを持つデバイス
//

#include "thread.h"
#include "mainapp.h"
#include "mythread.h"
#include <time.h>
#include <algorithm>

static void PrintSuggestion(int) __unused;

// コンストラクタ
ThreadDevice::ThreadDevice(uint objid_)
	: inherited(objid_)
{
	// SetName() はこのクラスでオーバーライドしているので
	// ここで(改めて)実行する必要がある。
	SetName(GetName());
}

// デストラクタ
ThreadDevice::~ThreadDevice()
{
	// ここで virtual の Terminate() は呼べないので、
	// 継承側のデストラクタで TerminateThread() をそれぞれ呼ぶこと。
}

// オブジェクト名を設定(更新)するとともにスレッド名も更新する。
// Object::SetName() のオーバーロード。
void
ThreadDevice::SetName(const std::string& newname)
{
	inherited::SetName(newname);
	SetThreadName(newname);
}

// スレッドにつけるスレッド名を覚えておく
// (このスレッドにスレッド名を設定する、ではない)。
void
ThreadDevice::SetThreadName(const std::string& threadname_)
{
	threadname = threadname_;
}

// スレッド開始
bool
ThreadDevice::StartThread()
{
	// ここまでに名前はセットしておくこと。
	assert(threadname.empty() == false);

	// スレッド起動。
	std::lock_guard<std::mutex> lock(thread_starter);
	try {
		thread.reset(new std::thread(&ThreadDevice::OnStart, this));
	} catch (...) { }
	if ((bool)thread == false) {
		warnx("Failed to initialize thread(%s) at %s",
			threadname.c_str(), __method__);
		return false;
	}
	return true;
}

// 開始されたスレッドでのエントリポイント。
void
ThreadDevice::OnStart()
{
	// このスレッドにスレッド名を設定。
	PTHREAD_SETNAME(threadname.c_str());

	// パフォーマンス計測のためのリストに追加。
	// デバッガスレッドはなくてもいいか。
	if (GetId() != OBJ_DEBUGGER && GetId() != OBJ_HOSTCOM_DBG) {
		auto thman = gMainApp.GetThreadManager();
		thman->RegistThread(this, threadname);
	}

	// 実行開始。
	std::lock_guard<std::mutex> lock_sub(this->thread_starter);
	ThreadRun();
}

// スレッドに終了を要求し、その終了を待つ。
// スレッド外から呼ぶこと。
void
ThreadDevice::TerminateThread()
{
	if ((bool)thread) {
		auto thman = gMainApp.GetThreadManager();
		thman->UnregistThread(this);

		Terminate();

		if (thread->joinable()) {
			thread->join();
		}

		thread.reset();
	}
}

// このスレッドのスレッドアフィニティを設定する。
/*static*/ void
ThreadBase::SetThreadAffinityHint(AffinityClass hint)
{
#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET)
	std::lock_guard<std::mutex> lock(exlock);

	// policy, hint から cpuset を作成する。
	//
	// policy	hint		動作
	// -------	-----		----
	// Neutral	*			nop
	// Binding	Light		set Lo	(if 高性能コア数 < 3)
	// Binding	Light		nop		(if 高性能コア数 >= 3)
	// Binding	Heavy		set Hi

	if (gMainApp.affinity_policy == MainApp::AffinityPolicy::Neutral) {
		return;
	}

	const std::vector<bool>& fastcore = gMainApp.fastcore;

	// 高性能コアが少ない場合、Light スレッドに高性能コアを奪われたくないので
	// Light スレッドを低性能コアに縛っておきたい。
	// 高性能コアの数が多ければ、そこまでする必要もないか。
	if (hint == AffinityClass::Light) {
		uint nhigh = std::count(fastcore.begin(), fastcore.end(), true);
		if (nhigh >= 3) {
			return;
		}
	}

	// ここまで来ると、このスレッドが Heavy なら高性能コアに、
	// Light なら低性能コアにバインドすることが確定。
	MICPUSet cset;
	bool target = (hint == AffinityClass::Heavy);
	for (uint i = 0, end = fastcore.size(); i < end; i++) {
		if (fastcore[i] == target) {
			cset.Set(i);
		}
	}

	// 設定。
	int r = PTHREAD_SETAFFINITY(pthread_self(), cset);

	if (gMainApp.hostcpu->loglevel >= 1) {
		std::string csetstr;
		for (uint i = 0, end = fastcore.size(); i < end; i++) {
			// 読みやすさのための区切り。先頭の1つは表示時に捨てる。
			uint n = i % 16;
			if (n == 0) {
				csetstr += '\'';
			}
			if (cset.Get(i)) {
				csetstr += "0123456789ABCDEF"[n];
			} else {
				csetstr += '_';
			}
		}
		gMainApp.hostcpu->putmsgn("SetAffinity:%-12s(%s) %s%s%s",
			PTHREAD_GETNAME().c_str(),
			(hint == AffinityClass::Light ? "Light" : "Heavy"),
			csetstr.c_str() + 1,
			(r == 0 ? "" : " = "),
			(r == 0 ? "" : strerror(r)));

		// この後のエラーメッセージより先にログスレッドが動いたら嬉しい。
		usleep(1);
	}

	// エラーなら errno ではなく戻り値にエラー番号が返ってくる。
	// NetBSD では sysctl で許可しないと一般ユーザはこの機能を使えない。
	if (r != 0) {
		errno = r;
		warn("PTHREAD_SETAFFINITY");
		PrintSuggestion(r);
		// 本当は終了したいが、この時点からではちょっと面倒なので放置。
		// 以降は何もしない。
		gMainApp.affinity_policy = MainApp::AffinityPolicy::Neutral;
	}
#else
	// なければ無視する。
#endif
}

/*static*/ std::mutex ThreadBase::exlock;


//
// スレッドマネージャ
//

// コンストラクタ
ThreadManager::ThreadManager()
	: inherited(OBJ_NONE)
{
	SetName("ThreadManager");
}

// デストラクタ
ThreadManager::~ThreadManager()
{
}

// 表示順のスレッド名
static const std::vector<const char *>
disp_order = {
	"Main",
	"Logger",
	"SignalThread",
	"VM",
	"VirtIOBlock0",
	"VirtIOBlock1",
	"VirtIOBlock2",
	"VirtIOBlock3",
	"VirtIOBlock4",
	"VirtIOBlock5",
	"VirtIOBlock6",
	"VirtIOBlock7",
	"VirtIONet",
	"VirtIOSCSI",
	"VirtIOEntropy",
	"VideoRenderer",
	"SoundRenderer",
	"HostNet0",
	"HostNet1",
	"HostCOM0",
	"HostCOM1",
	"HostCOM2",
	"HostSound",
};

// スレッドリストに登録する。
// 登録したいスレッド自身から呼ぶこと。
void
ThreadManager::RegistThread(Object *obj, const std::string& name)
{
	// パフォーマンス計測のため自スレッドの clockid を取得。
	clockid_t clockid;
	pthread_getcpuclockid(pthread_self(), &clockid);

	std::lock_guard<std::mutex> lock(threads_mutex);
	threads.emplace_back(obj, name, clockid);
	// 都度表示順にソートする。
	std::sort(threads.begin(), threads.end(),
		[](const ThreadInfo& a, const ThreadInfo& b) {
			auto ia = std::find(disp_order.begin(), disp_order.end(), a.name);
			auto ib = std::find(disp_order.begin(), disp_order.end(), b.name);
			return ia < ib;
		}
	);
}

// スレッドリストから obj を削除する。
void
ThreadManager::UnregistThread(Object *obj)
{
	std::lock_guard<std::mutex> lock(threads_mutex);

	for (auto it = threads.begin(); it != threads.end(); ++it) {
		if ((*it).obj == obj) {
			threads.erase(it);
			break;
		}
	}
}

// スレッドの CPU 利用率を記録する。
void
ThreadManager::CalcPerf(uint64 rtime)
{
	std::lock_guard<std::mutex> lock(threads_mutex);

	for (auto& info : threads) {
		struct timespec ts;
		clock_gettime(info.clockid, &ts);
		uint64 t = nsec_to_tsec(sec_to_nsec(ts.tv_sec) + ts.tv_nsec);
		if (__predict_true(last_rtime != 0)) {
			// 2回目以降
			uint percent = (t - info.last_clock) * 100 / (rtime - last_rtime);
			info.load->EnqueueForce(percent);
		}
		info.last_clock = t;
	}
	last_rtime = rtime;
}

#define hostcpu_putmsg(lv, fmt...)	do {	\
	if (hostcpu->loglevel >= (lv))		\
		hostcpu->putmsgn(fmt);			\
} while (0)

#if defined(__x86_64__)
#include <cpuid.h>
// x86_64 CPU の P コア、E コアを調べて fastcore 配列に返す。
// fastcore は事前にコア数分確保してあること。
// 検出できれば fastcore を更新して true を返す。
// 検出できなければ fastcore は不定で false を返す。
// 失敗ならエラーメッセージも表示する。
// MainApp から呼ばれるが、スレッド関連なのでこっちに置く。
/*static*/ bool
ThreadManager::DetectCPUAffinity_x86(std::vector<bool>& fastcore)
{
	Object *hostcpu = gMainApp.hostcpu.get();

	// CPUID(0x1a, 0) は Intel Core 12世代以降でハイブリッドアーキテクチャ
	// 情報を返す。eax の bit31-24 (最上位バイト) が 0x40 なら P コア、
	// 0x20 なら E コア。0 ならたぶんハイブリッド構成でない CPU。
	//
	// まずどのコアでもいいので現在のコアで実行してみる。
	// ここでハイブリッド構成だと分かれば、個別のコアについて調べるのだが、
	// ただしそのためのスレッドアフィニティ指定には特権が必要な場合があるので、
	// 特権を要求する作業が必要になるかどうかを先に調べておきたいということ。

#if defined(HAVE_PTHREAD_SETAFFINITY_NP) && defined(HAVE_MICPUSET)
	// まず現在のコアで。
	uint32 eax, ebx, ecx, edx;
	__cpuid_count(0x1a, 0, eax, ebx, ecx, edx);

	if ((eax >> 24) == 0) {
		// CPU はハイブリッド構成ではない。
		hostcpu_putmsg(1, "%s: Not a hybrid type core", __func__);
		return false;
	} else {
		// CPU はハイブリッド構成なので、個別のコアについて調べる。
		for (uint i = 0, end = fastcore.size(); i < end; i++) {
			int r;
			std::thread th([&] {
				MICPUSet cset;
				cset.Set(i);
				r = PTHREAD_SETAFFINITY(pthread_self(), cset);
				__cpuid_count(0x1a, 0, eax, ebx, ecx, edx);
				if ((eax >> 24) == 0x40) {
					fastcore[i] = true;
				}
			});
			th.join();

			if (r != 0) {
				errno = r;
				warn("PTHREAD_SETAFFINITY");
				PrintSuggestion(r);
				return false;
			}
			hostcpu_putmsg(1, "%s: Core%u is %c",
				__func__, i, (fastcore[i] ? 'P' : 'E'));
		}
		return true;
	}
#else
	hostcpu_putmsg(1, "%s: No PTHREAD_SETAFFINITY", __func__);
	return false;
#endif
}
#endif // __x86_64__

// PTHREAD_SETAFFINITY() が EPERM になった時に表示するお知らせ。
// 2箇所から呼ばれるので関数にしてある。
static void
PrintSuggestion(int errorcode)
{
#if defined(__NetBSD__)
	if (errorcode == EPERM) {
		fprintf(stderr,
		" ** To allow non-privileged users to set thread affinity on NetBSD:\n"
		" **  # sysctl -w security.models.extensions.user_set_cpu_affinity=1\n"
		);
	}
#endif
}

unix.superglobalmegacorp.com

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