はじめに
プロセス間通信を簡単な例で紹介します。
実行中のプロセスに対して、外部からシグナルを送信することで、セグフォを発生させて落とすプログラムを作成します。
環境
sw_vers
ProductName: macOS
ProductVersion: 11.4
BuildVersion: 20F71
Server (受信する側)
int main()
{
while (1);
return 0;
}
コンパイルしてバッググラウンドで起動
$ gcc server.c -o server
$ ./server &
[1] 14380 <- こんな感じで番号が出ます。
Client (送信する側)
# include <libc.h>
int main(int ac, char **av)
{
(void)ac;
kill((pid_t)atoi(av[1]), SIGSEGV);
return 0;
}
killコマンドはシグナルを送る関数です。
コンパイルしてシグナルを送る
serverをバッググラウンドで起動した際に出たプロセスIDを引数にして、実行
gcc client -o clinet
./clinet [PID_ID]
結果
serverプログラムがセグフォします。
[1] + 14380 segmentation fault ./server
これは、プロセスがSIGSEGVを渡されると、落ちるようにデフォルトで設計されているからです。
応用編
sigactionを使ってシグナルから関数を起動
もっと複雑なことをやろうとするなら、sigaction関数を使って、シグナルが渡された際の挙動を自分で定義することも可能です。
プロセス間通信を使って思い通りの通信(動作)を実現しましょう。
データはbitなので、2つのシグナル、0と1
になるものがあれば、あらゆるデータの送受信ができます。
sigcation
sigactionを使用すれば、シグナルをトリガーにして、関数を起動できます。
sigactionは一度動かすとプロセス終了までずっと設定したシグナルをキャッチしてくれます。
詳しくはman sigaction
を見てください。シグナル関係は環境によって設定がけっこう変わるので、環境に合わせて実装したほうが良いです。
私の場合は汎用性を持たせるために関数ポインタを渡して、設定できるようにしています。
void receiver(void handler(int, siginfo_t *, void *))
{
struct sigaction act;
bzero(&act, sizeof(struct sigaction));
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGUSR2, &act, NULL);
}
お作法的な部分なのでさっくり説明します。
- bzeroで構造体の初期化
- sigemptysetでシグナルの初期化
-
act.sa_flags = SA_SIGINFO;
でhandler関数を使用する設定にする。 - sigactionは第一引数にどのシグナルをトリガーにするか設定します。複数のシグナルで同じ関数を動かすときには、上記のように複数回呼び出します。
連続してシグナルを送る場合のインターバルの設定
今回私の目的は、文字列をbitに変換し、プロセスに送ることなので、連続して何度もシグナルを送ります。
void send_char(pid_t pid, char c)
{
int bit;
int i;
unsigned char uc;
uc = (unsigned char)c;
i = 0;
while (i < 8)
{
usleep(50);
bit = (uc >> i) & 0x01;
if (kill(pid, SIGUSR1 + bit) == -1)
fatal("kill error");
i++;
}
}
どれくらいのインターバルを設定するべきか、という根拠は現状ありません。
usleep(50)くらいに設定しておくと、シグナル通信で送っているbitの情報に抜けが起こらなかったので、それを根拠に実装しています。
まとめ
送信するシグナルの量を増やさない限り、bit抜けを検知できないのが難点です。
原始的なデータの送信方法を学べたので、今度はより高度な通信手段を見てみたいと思いました。