|
|
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.