Annotation of nono/host/signalthread.cpp, revision 1.1.1.2

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

unix.superglobalmegacorp.com

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