|
|
1.1 ! root 1: // ! 2: // nono ! 3: // Copyright (C) 2021 nono project ! 4: // Licensed under nono-license.txt ! 5: // ! 6: ! 7: // ! 8: // シグナルスレッド ! 9: // ! 10: ! 11: // このプロセス内で処理が必要な UNIX シグナルは一旦このスレッドで受け取り、 ! 12: // 必要に応じてスレッドに通知などをする。 ! 13: // ! 14: // スレッド作成前に StartThread() にてプロセス全体で該当シグナルをブロック ! 15: // する。他のスレッドはこの後生成するので、全スレッドがこの特性を引き継ぐ。 ! 16: // ! 17: // シグナルスレッドは、該当シグナルをブロックしたままシグナルが発生するのを ! 18: // sigwait(3) で待つ (ブロックはデフォルト動作やハンドラ呼び出しが起きるのを ! 19: // ブロックするがペンディングにはなる、sigwait(3) はこのペンディングに反応 ! 20: // する)。 ! 21: // ! 22: // o SIGTERM を受け取ると UIMessage でメインスレッドに通知し、そこから安全に ! 23: // プロセスを終了させる。 ! 24: // ! 25: // o SIGINT(^C)、SIGTSTP(^Z)、SIGQUIT(^\) は HostCOM のうち stdio ドライバが ! 26: // 必要とするため動作を切り替える。HostCOM が stdio なら、これらのシグナル ! 27: // は処理せず、HostCOM スレッドにパイプで通知する。 ! 28: // HostCOM が stdio 以外なら、これらのシグナルは HostCOM に通知せず、 ! 29: // こちらで処理する。 ! 30: // ! 31: // o SIGHUP、SIGPIPE、SIGUSR1、SIGUSR2 はデフォルトがプロセス終了なので ! 32: // ブロックして無視する。 ! 33: // このうち無難そうな SIGPIPE をこのスレッドへのメッセージ通知として使う。 ! 34: // SIGPIPE 発生時に特定のメンバ変数がセットされているとそれに従って ! 35: // アクションを起こす。 ! 36: // ! 37: // Linux (Ubuntu 20.04) ではシグナルの配送が POSIX とは異なっているらしく、 ! 38: // プロセスに届いたシグナルを sigwait しているスレッドに配送してくれない。 ! 39: // glibc のマニュアルには記載があるようだ。 ! 40: // https://linuxjm.osdn.jp/html/glibc-linuxthreads/man3/pthread_kill.3.html ! 41: // Linux のマニュアルでは確認できなかった。 ! 42: ! 43: #include "signalthread.h" ! 44: #include "uimessage.h" ! 45: #include <climits> ! 46: #include <csignal> ! 47: ! 48: // グローバル参照用 ! 49: SignalThread *gSignalThread; ! 50: ! 51: void signal_handler(int signo); ! 52: void ! 53: signal_handler(int signo) ! 54: { ! 55: #if defined(__linux__) ! 56: // Linux ではプロセスに届いたシグナルを自動でスレッドに配送して ! 57: // くれないので、自前でやる必要があるようだ。 ! 58: if (gSignalThread) { ! 59: gSignalThread->Kill(signo); ! 60: } ! 61: #endif ! 62: } ! 63: ! 64: // コンストラクタ ! 65: SignalThread::SignalThread() ! 66: : inherited("SignalThread") ! 67: { ! 68: wpipe = -1; ! 69: sigemptyset(&waitset); ! 70: } ! 71: ! 72: // デストラクタ ! 73: SignalThread::~SignalThread() ! 74: { ! 75: TerminateThread(); ! 76: gSignalThread = NULL; ! 77: } ! 78: ! 79: // 常にブロックするシグナル集合 ! 80: static std::vector<int> siglist_always = { ! 81: SIGHUP, // ignore ! 82: SIGPIPE, // ignore ! 83: SIGTERM, // プロセス終了 ! 84: SIGUSR1, // ignore ! 85: SIGUSR2, // ignore ! 86: }; ! 87: ! 88: // HostCOM が受け取るかどうかによって動作を変えるシグナル集合 ! 89: static std::vector<int> siglist_com = { ! 90: SIGINT, // HostCOM (^C) ! 91: SIGQUIT, // HostCOM (^\) ! 92: SIGTSTP, // HostCOM (^Z) ! 93: }; ! 94: ! 95: // スレッド開始 (override) ! 96: // ! 97: // SIGINT(^C) など緊急性の高いシグナルのブロック期間を極力短くするため、 ! 98: // シグナルのブロック(マスク)は、極力スレッドを開始する直前に行いたい。 ! 99: // SignalThread は Device リストの先頭なので (see ../vm/vm.cpp)、この ! 100: // StartThread() 時点ではまだ (少なくとも自分が関与できる) 他のスレッドは ! 101: // 生成されておらず、ここでのこのスレッドに対する Mask() は、この後生成 ! 102: // されるスレッドに引き継がれるベースになる。 ! 103: bool ! 104: SignalThread::StartThread() ! 105: { ! 106: for (auto signo : siglist_always) { ! 107: Mask(SIG_BLOCK, signo); ! 108: } ! 109: for (auto signo : siglist_com) { ! 110: Mask(SIG_BLOCK, signo); ! 111: } ! 112: ! 113: // で、スレッドを開始。 ! 114: return inherited::StartThread(); ! 115: } ! 116: ! 117: // スレッド実行 ! 118: void ! 119: SignalThread::ThreadRun() ! 120: { ! 121: request_terminate = false; ! 122: request_wpipe = -1; ! 123: ! 124: for (;;) { ! 125: int signo; ! 126: int r; ! 127: ! 128: // sigwait() は成功なら 0、失敗ならエラーコードを返す。 ! 129: putmsg(2, "sigwait..."); ! 130: r = sigwait(&waitset, &signo); ! 131: if (r != 0) { ! 132: if (r == EINTR) { ! 133: continue; ! 134: } ! 135: putmsg(0, "sigwait: %s", strerror(r)); ! 136: // XXX どうする? ! 137: continue; ! 138: } ! 139: putmsg(1, "sigwait got %d", signo); ! 140: ! 141: // 終了要求が立っていたら、どのシグナルでもスレッドを終了する ! 142: if (request_terminate) { ! 143: break; ! 144: } ! 145: ! 146: // ここからシグナル別の処理 ! 147: switch (signo) { ! 148: case SIGPIPE: // コマンド ! 149: { ! 150: // 本来は無視するだけなので、ついでにこのスレッドに対する ! 151: // メッセージ通知として使用する。フラグが立ってなければ単に無視。 ! 152: // ! 153: // SIGPIPE 発生時に request_wpipe が非負なら、HostCOM からの ! 154: // パイプ接続もしくは切断の要求。 ! 155: int req = request_wpipe.exchange(-1); ! 156: if (req >= 0) { ! 157: if (loglevel >= 1) { ! 158: if (req == INT_MAX) { ! 159: putmsg(1, "SIGPIPE: HostCOM del"); ! 160: } else { ! 161: putmsg(1, "SIGPIPE: HostCOM add %d", req); ! 162: } ! 163: } ! 164: // HostCOM スレッドへのシグナル通知を変更 ! 165: ChangeCOM(req); ! 166: } ! 167: break; ! 168: } ! 169: ! 170: case SIGTERM: // SIGTERM なら即終了 ! 171: // メインスレッドから終了してもらう ! 172: UIMessage::Post(UIMessage::APPEXIT); ! 173: break; ! 174: ! 175: case SIGINT: // パイプがあれば配送、なければここで処理 ! 176: if (wpipe >= 0) { ! 177: WriteSignal(wpipe, signo); ! 178: } else { ! 179: // メインスレッドから終了してもらう ! 180: UIMessage::Post(UIMessage::APPEXIT); ! 181: } ! 182: break; ! 183: ! 184: case SIGTSTP: // パイプがあれば配送、なければデフォルト動作 ! 185: case SIGQUIT: ! 186: if (wpipe >= 0) { ! 187: // パイプがあれば書き込んで通知。 ! 188: WriteSignal(wpipe, signo); ! 189: } else { ! 190: // パイプがなければ、 ! 191: // ここでデフォルト動作を起こしたい。 ! 192: ! 193: // 一旦解除して.. ! 194: Mask(SIG_UNBLOCK, signo); ! 195: ! 196: // シグナルを自身に送信。 ! 197: Kill(signo); ! 198: ! 199: // そして再度ブロックしておく。 ! 200: // こうしないと、2回目の ^Z が効かないとかになる。 ! 201: Mask(SIG_BLOCK, signo); ! 202: } ! 203: break; ! 204: ! 205: default: // 他は無視する ! 206: break; ! 207: } ! 208: } ! 209: ! 210: putmsg(1, "ThreadRun terminating"); ! 211: } ! 212: ! 213: void ! 214: SignalThread::Terminate() ! 215: { ! 216: putmsg(1, "Terminate"); ! 217: ! 218: // フラグを立ててシグナルで通知 ! 219: request_terminate = true; ! 220: Kill(SIGPIPE); ! 221: } ! 222: ! 223: // HostCOM スレッドへのパイプを接続する (HostCOM スレッドから呼ばれる) ! 224: void ! 225: SignalThread::AddPipe(int fd) ! 226: { ! 227: putmsg(1, "AddPipe(fd=%d)", fd); ! 228: ! 229: if ((bool)thread == false) { ! 230: // HostCOM から呼ばれるが、起動時の初期設定は Init() フェーズ内の ! 231: // ため、その時点ではまだどちらもスレッドはない。 ! 232: // この場合は単にここで設定するだけでよい。 ! 233: ChangeCOM(fd); ! 234: } else { ! 235: // スレッド開始後なら、フラグを立ててシグナルで通知 ! 236: request_wpipe = fd; ! 237: Kill(SIGPIPE); ! 238: } ! 239: } ! 240: ! 241: // HostCOM スレッドへのパイプを削除する ! 242: void ! 243: SignalThread::DeletePipe() ! 244: { ! 245: putmsg(1, "DeletePipe(current=%d)", wpipe); ! 246: ! 247: // フラグを立ててシグナルで通知 ! 248: request_wpipe = INT_MAX; ! 249: Kill(SIGPIPE); ! 250: } ! 251: ! 252: // シグナルをパイプに再配送する ! 253: void ! 254: SignalThread::WriteSignal(int wfd, int signo) ! 255: { ! 256: char buf[1]; ! 257: int r; ! 258: ! 259: buf[0] = signo; ! 260: r = write(wpipe, buf, sizeof(buf)); ! 261: if (r < 0) { ! 262: // エラーが起きたパイプは閉じればいいか… ! 263: ChangeCOM(INT_MAX); ! 264: } ! 265: } ! 266: ! 267: // HostCOM スレッドとのパイプを接続/切断する。 ! 268: // SignalThread 側処理。 ! 269: void ! 270: SignalThread::ChangeCOM(int reqfd) ! 271: { ! 272: // reqfd が INT_MAX なら現在のディスクリプタを切断。 ! 273: // そうでなければ、reqfd を接続。 ! 274: // そんな高頻度で投げないので運用上は問題にならないはず。 ! 275: ! 276: putmsg(1, "ChangeCOM(current=%d request=%d)", wpipe, reqfd); ! 277: ! 278: if (reqfd == INT_MAX) { ! 279: // パイプを切断してからシグナルのブロックを解除 ! 280: close(wpipe); ! 281: wpipe = -1; ! 282: ! 283: for (auto signo : siglist_com) { ! 284: Mask(SIG_UNBLOCK, signo); ! 285: } ! 286: } else { ! 287: // 必要なシグナルをブロックしてからパイプを接続 ! 288: for (auto signo : siglist_com) { ! 289: Mask(SIG_BLOCK, signo); ! 290: } ! 291: ! 292: wpipe = reqfd; ! 293: } ! 294: } ! 295: ! 296: // SignalThread にシグナルを投げる (他スレッドからも使用可) ! 297: void ! 298: SignalThread::Kill(int signo) ! 299: { ! 300: assert((bool)thread); ! 301: ! 302: putmsg(1, "Kill(%d)", signo); ! 303: pthread_kill(thread->native_handle(), signo); ! 304: } ! 305: ! 306: // SIG_BLOCK ならブロックする集合に追加 (=配送する)。 ! 307: // SIG_UNBLOCK ならブロックする集合から削除 (=こちらで処理する)。 ! 308: void ! 309: SignalThread::Mask(int how, int signo) ! 310: { ! 311: const char *howstr = (how == SIG_BLOCK) ? "BLOCK" : "UNBLOCK"; ! 312: ! 313: putmsg(1, "Mask(%s, %d)", howstr, signo); ! 314: ! 315: // シグナルハンドラを設定 (シグナルハンドラはプロセスグローバル) ! 316: // signal(3) は挙動が違う可能性があるので使わないこと。 ! 317: struct sigaction act; ! 318: memset(&act, 0, sizeof(act)); ! 319: if (how == SIG_BLOCK) { ! 320: act.sa_handler = signal_handler; ! 321: } else { ! 322: act.sa_handler = SIG_DFL; ! 323: } ! 324: if (sigaction(signo, &act, NULL) < 0) { ! 325: putmsg(0, "sigaction(%d, %s): %s", signo, howstr, strerror(errno)); ! 326: return; ! 327: } ! 328: ! 329: // このスレッドのマスクを設定 ! 330: sigset_t set; ! 331: sigemptyset(&set); ! 332: sigaddset(&set, signo); ! 333: if (pthread_sigmask(how, &set, NULL) < 0) { ! 334: putmsg(0, "pthread_sigmask(%s, %d): %s", ! 335: howstr, signo, strerror(errno)); ! 336: return; ! 337: } ! 338: ! 339: // sigwait() 用のセットを更新 ! 340: if (how == SIG_BLOCK) { ! 341: sigaddset(&waitset, signo); ! 342: } else { ! 343: sigdelset(&waitset, signo); ! 344: } ! 345: ! 346: // 適当デバッグ表示 ! 347: if (loglevel >= 1) { ! 348: std::string str; ! 349: ! 350: #define CHECK(NAM) do { \ ! 351: if (sigismember(&waitset, __CONCAT(SIG,NAM))) { \ ! 352: str += " "; \ ! 353: str += #NAM; \ ! 354: } \ ! 355: } while (0) ! 356: CHECK(HUP); ! 357: CHECK(INT); ! 358: CHECK(QUIT); ! 359: CHECK(PIPE); ! 360: CHECK(TERM); ! 361: CHECK(TSTP); ! 362: CHECK(USR1); ! 363: CHECK(USR2); ! 364: ! 365: putmsgn("waitset =%s", str.c_str()); ! 366: } ! 367: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.