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

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

//
// シグナルスレッド
//

// このプロセス内で処理が必要な UNIX シグナルは一旦このスレッドで受け取り、
// 必要に応じてスレッドに通知などをする。
//
// スレッド作成前に StartThread() にてプロセス全体で該当シグナルをブロック
// する。他のスレッドはこの後生成するので、全スレッドがこの特性を引き継ぐ。
//
// シグナルスレッドは、該当シグナルをブロックしたままシグナルが発生するのを
// sigwait(3) で待つ (ブロックはデフォルト動作やハンドラ呼び出しが起きるのを
// ブロックするがペンディングにはなる、sigwait(3) はこのペンディングに反応
// する)。
//
// o SIGTERM、SIGINT(^C) を受け取ると UIMessage でメインスレッドに通知し、
//   そこから安全にプロセスを終了させる。
//
// o SIGHUP、SIGPIPE、SIGUSR1、SIGUSR2 はデフォルトがプロセス終了なので
//   ブロックして無視する。
//
// Linux (Ubuntu 20.04) ではシグナルの配送が POSIX とは異なっているらしく、
// プロセスに届いたシグナルを sigwait しているスレッドに配送してくれない。
// glibc のマニュアルには記載があるようだ。
// https://linuxjm.osdn.jp/html/glibc-linuxthreads/man3/pthread_kill.3.html
// Linux のマニュアルでは確認できなかった。

#include "signalthread.h"
#include "mainapp.h"
#include "uimessage.h"
#include <csignal>

static void signal_handler(int signo);

static void
signal_handler(int signo)
{
#if defined(__linux__)
	// Linux ではプロセスに届いたシグナルを自動でスレッドに配送して
	// くれないので、自前でやる必要があるようだ。
	SignalThread *sigthread = GetSignalThread();
	if (sigthread) {
		sigthread->Kill(signo);
	}
#endif
}

// コンストラクタ
SignalThread::SignalThread()
	: inherited(OBJ_SIGNAL_THREAD)
{
	sigemptyset(&waitset);
}

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

// 常にブロックするシグナル集合
static std::vector<int> siglist_always = {
	SIGHUP,		// ignore
	SIGINT,		// プロセス終了
	SIGPIPE,	// ignore
	SIGTERM,	// プロセス終了
	SIGUSR1,	// ignore
	SIGUSR2,	// ignore
};

// スレッド開始 (override)
//
// SIGINT(^C) など緊急性の高いシグナルのブロック期間を極力短くするため、
// シグナルのブロック(マスク)は、極力スレッドを開始する直前に行いたい。
// SignalThread は Device リストの先頭なので (see ../vm/vm.cpp)、この
// StartThread() 時点ではまだ (少なくとも自分が関与できる) 他のスレッドは
// 生成されておらず、ここでのこのスレッドに対する Mask() は、この後生成
// されるスレッドに引き継がれるベースになる。
bool
SignalThread::StartThread()
{
	for (auto signo : siglist_always) {
		Mask(SIG_BLOCK, signo);
	}

	// で、スレッドを開始。
	return inherited::StartThread();
}

// スレッド実行
void
SignalThread::ThreadRun()
{
	SetThreadAffinityHint(AffinityClass::Light);

	request_terminate = false;

	for (;;) {
		int signo;
		int r;

		// sigwait() は成功なら 0、失敗ならエラーコードを返す。
		putmsg(2, "sigwait...");
		r = sigwait(&waitset, &signo);
		// 終了要求が立っていたら、どのシグナルでも、エラーが起きてても
		// とにかく先にスレッドを終了する。
		if (request_terminate) {
			break;
		}
		if (r != 0) {
			if (r == EINTR) {
				continue;
			}
			putmsg(0, "sigwait: %s", strerror(r));
			// XXX どうする?
			continue;
		}
		putmsg(1, "sigwait got %d", signo);

		// ここからシグナル別の処理
		switch (signo) {
		 case SIGTERM:
		 case SIGINT:
			// メインスレッドから終了してもらう
			gMainApp.GetUIMessage()->Post(UIMessage::APPEXIT);
			break;

		 default:		// 他は無視する
			break;
		}
	}

	putmsg(1, "ThreadRun terminating");
}

void
SignalThread::Terminate()
{
	putmsg(1, "Terminate");

	// フラグを立ててシグナルで通知
	request_terminate = true;
	Kill(SIGTERM);
}

// SignalThread にシグナルを投げる (他スレッドからも使用可)
void
SignalThread::Kill(int signo)
{
	assert((bool)thread);

	putmsg(1, "Kill(%d)", signo);
	pthread_kill(thread->native_handle(), signo);
}

// SIG_BLOCK ならブロックする集合に追加 (=配送する)。
// SIG_UNBLOCK ならブロックする集合から削除 (=こちらで処理する)。
void
SignalThread::Mask(int how, int signo)
{
	const char *howstr = (how == SIG_BLOCK) ? "BLOCK" : "UNBLOCK";

	putmsg(1, "Mask(%s, %d)", howstr, signo);

	// シグナルハンドラを設定 (シグナルハンドラはプロセスグローバル)
	// signal(3) は挙動が違う可能性があるので使わないこと。
	struct sigaction act;
	memset(&act, 0, sizeof(act));
	if (how == SIG_BLOCK) {
		act.sa_handler = signal_handler;
	} else {
		act.sa_handler = SIG_DFL;
	}
	if (sigaction(signo, &act, NULL) < 0) {
		putmsg(0, "sigaction(%d, %s): %s", signo, howstr, strerror(errno));
		return;
	}

	// このスレッドのマスクを設定
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, signo);
	if (pthread_sigmask(how, &set, NULL) < 0) {
		putmsg(0, "pthread_sigmask(%s, %d): %s",
			howstr, signo, strerror(errno));
		return;
	}

	// sigwait() 用のセットを更新
	if (how == SIG_BLOCK) {
		sigaddset(&waitset, signo);
	} else {
		sigdelset(&waitset, signo);
	}

	// 適当デバッグ表示
	if (loglevel >= 1) {
		std::string str;

#define CHECK(NAM)	do {	\
	if (sigismember(&waitset, __CONCAT(SIG,NAM))) {	\
		str += " ";		\
		str += #NAM;	\
	}	\
} while (0)
		CHECK(HUP);
		CHECK(INT);
		CHECK(PIPE);
		CHECK(TERM);
		CHECK(USR1);
		CHECK(USR2);

		putmsgn("waitset =%s", str.c_str());
	}
}

unix.superglobalmegacorp.com

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