シリーズ3か月近く間が空きました。気を許すと練習頻度を忘却速度が上回ります。練習再開、テーマは「シグナル」です。マイコンの場合のハードウエア割り込みのようなアカラサマなものではなく、Linuxの管理下にある「ソフトウエア割り込み」的なやつです。今回はシグナルを無視するやり方を練習。
※ソフトな忘却力 投稿順 index はこちら
※実機確認に使用したのは、Raspberry Pi 4 model B機(Arm Cortex-A72)です。Raspberry Pi OS<32bit版、Linux>です。OSバージョンが以下に。多分、大抵のLinuxで同じように走るんじゃないかと思いますが未確認デス。
以前は、Raspbianと呼んでいたのに、最近公式では Raspberry Pi OSと呼んでいますな。でも、こうして本人?に聞くと Raspbianと名乗ると。。。
シグナル
シグナル、実行中のプログラムをCTRL-Cして止めるとか、暴走しているプログラムをSIGKILL送って止めるとか、時々お世話になるアイツです。
今回はシグナルを無視する技、を練習します。目標としては、キーボードからCTRL-Cで発生するSIGINTを無視するように設定しておいて、「待ち」に入り、CTRL-Cの連打にもかかわらず戻ってこない、でも、他のキッカケでちゃんとプログラム終了する、ということにいたしました。
朧気な記憶によると、Unix()には signal()という関数あり、こいつでシグナルのハンドラを設定できた筈。そして、ハンドラの代わりに SIG_IGN を渡せば、該当シグナルは無視される(OSは該当のシグナルを対象のプロセスに配送しなくなる)筈。以下のMan page(日本語)を参照させていただきました。
しかし、ということで1か所引用させていただきます。
signal() の動作は UNIX のバージョンにより異なる。 また、歴史的に見て Linux のバージョンによっても異なっている。 このシステムコールの使用は避け、 代わりに sigaction(2) を使用すること。
あれま、signal()は使わないことになっていたのね。しかし、別にシグナルのハンドラを設定したいわけでないので、sigaction()する必要も無さそうです。単にシグナルを無視したりするだけであれば、以下が。
上記は呼び出したスレッドのシグナルマスクの取得/変更を行うための関数みたいです。
実験用のコード
上記のsigprocmask()関数を使って、SIGINTを無視するだけのプログラムを書いてみました。こんな感じ。
// sigintBLK.c #include <stdio.h> #include <signal.h> #include <unistd.h> int main(void) { sigset_t newset; sigemptyset(&newset); sigaddset(&newset, SIGINT); if (sigprocmask(SIG_BLOCK, &newset, NULL) != 0) { perror("ERROR: sigprocmask."); } else { printf("MASK SIGINT, use CTRL-\\ for exit.\n"); pause(); printf("RETURN FROM pause()\n"); } return 0; }
なお、pause()関数のMan Pageが以下に。
上記は、「シグナルが配送されるまでスリープ」する関数です。今回 SIGINTは「無視」しているのでCTRL-Cしても反応しない筈ですが、他のシグナルを与えれば反応して戻ってくる、という期待。
ビルドは以下で。
$ gcc -g -O0 -Wall sigintBLK.c
実機確認
生成された a.out を走らせると以下のような感じです。
お約束どおり CTRL-C(SIGINT) を連打しましたが、^Cと画面に表示されるばかりで戻ってきません(マスクしないと pause()はCTRL-Cで戻ってきます。)CTRL-¥(日本語キーボードでは円マークだけれど、バックスラッシ)を打てば、これは SIGQUIT という別なシグナルになるので受付られます。
ちゃんと終わって戻ってきました。とりあえず、シグナルの無視はできた、と。小さな一歩だな。