早いもので1月も終盤を迎えてます.
そんな中、久しぶりのTECHネタ.
気が付いたら去年の7月からTECHネタ書いてなかったんですね…反省
今回は以前に書いた pidfd やsignalfd を使ってプロセス終了監視を行う件に関連した
poll()やselect()を使う場合には signal に気を付ける、という話です.
Table of Contents
ファイル・ディスクリプタのイベントを待つ
以前、プロセスの終了をpidfdというファイルディスクリプタを経由したイベントで待つ話を書きました.
その時に紹介したサンプルコードはこちらです.
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434 /* System call # on most architectures */
#endif
static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
#define TIMEOUT (5 * 1000) /* TimeOut = 5sec */
int main (int argc, char ** argv)
{
pid_t pid = fork ();
int status;
sigset_t mask;
int pfd;
if (pid == 0)
{
/* Child Process */
execv("/bin/sleep", argv);
}
else
{
struct pollfd pollfd;
int pidfd, ready;
printf ("Created process pid:%d\n", pid);
pfd = pidfd_open(pid, 0);
if (pfd == -1)
{
perror("pidfd_open");
return -1;
}
pollfd.fd = pfd;
pollfd.events = POLLIN;
ready = poll (&pollfd, 1, TIMEOUT);
if (ready < 0)
{
perror("poll");
return -1;
}
else if (ready == 0)
{
printf ("pid:%d Timeout\n",pid);
}
else
{
printf ("process: %d done\n", pid);
}
/* Parent Process */
pid_t dpid = waitpid (pid, &status, 0);
printf ("pid:%d done, exit status: %d\n", dpid, WEXITSTATUS(status));
close (pfd);
}
return 0;
}
ここでは fork() で新たに生成した子プロセスで /bin/sleep を実行し,親プロセスでは pidfd を poll() で監視することによって子プロセスの終了を待っています.
ここでpoll()で終了待ちしている間のシグナル受信について考えます.
イベント待ち中のシグナル受信
SIGURG(23) の入力を許可してpoll()でイベント待ちしているプロセスにシグナルを投げてみます. シグナル SIGURG を受け付けるようにしたコードです.
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#include <signal.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434 /* System call # on most architectures */
#endif
static
void signal_handler(int num)
{
puts("caught signal");
}
static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
#define TIMEOUT (120 * 1000) /* TimeOut = 120sec */
int main (int argc, char ** argv)
{
pid_t pid = fork ();
int status;
sigset_t mask;
int pfd;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = signal_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGURG, &sa, NULL) < 0)
{
perror("sigaction");
return -1;
}
printf ("my process id:%d\n", getpid());
if (pid == 0)
{
/* Child Process */
execv("/bin/sleep", argv);
}
else
{
struct pollfd pollfd;
int pidfd, ready;
printf ("Created process pid:%d\n", pid);
pfd = pidfd_open(pid, 0);
if (pfd == -1)
{
perror("pidfd_open");
return -1;
}
pollfd.fd = pfd;
pollfd.events = POLLIN;
ready = poll (&pollfd, 1, TIMEOUT);
if (ready < 0)
{
perror("poll");
return -1;
}
else if (ready == 0)
{
printf ("pid:%d Timeout\n",pid);
}
else
{
printf ("process: %d done\n", pid);
}
/* Parent Process */
pid_t dpid = waitpid (pid, &status, 0);
printf ("pid:%d done, exit status: %d\n", dpid, WEXITSTATUS(status));
close (pfd);
}
return 0;
}
起動するとプロセスIDを表示します.
$ ./sample00 100 my process id:4872 Created process pid:4873
そのプロセスIDに対して SIGURG(23) を投げます.
$kill -23 4872
そうすると, シグナルハンドラを実行後, poll は -1 を返して終了.
perror は以下のようなエラーメッセージを表示します.
caught signal poll: Interrupted system call
man poll によると EINTR エラー、イベント待ち中に発生したシグナルで終了した模様.
ERRORS
EINTR A signal occurred before any requested event; see signal(7).
シグナルを受信してもイベント待ちを継続したい場合, 何か対策が必要です.
EINTR エラー対策
イベント待ち中にシグナルを受信し, EINTR エラー発生の対応を行ったコードです.
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <poll.h>
#include <signal.h>
#include <errno.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434 /* System call # on most architectures */
#endif
static
void signal_handler(int num)
{
puts("caught signal");
}
static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
#define TIMEOUT (120 * 1000) /* TimeOut = 120sec */
int main (int argc, char ** argv)
{
pid_t pid = fork ();
int status;
sigset_t mask;
int pfd;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = signal_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGURG, &sa, NULL) < 0)
{
perror("sigaction");
return -1;
}
printf ("my process id:%d\n", getpid());
if (pid == 0)
{
/* Child Process */
execv("/bin/sleep", argv);
}
else
{
struct pollfd pollfd;
int pidfd, ready;
printf ("Created process pid:%d\n", pid);
pfd = pidfd_open(pid, 0);
if (pfd == -1)
{
perror("pidfd_open");
return -1;
}
retry:
pollfd.fd = pfd;
pollfd.events = POLLIN;
ready = poll (&pollfd, 1, TIMEOUT);
if (ready < 0)
{
perror("poll");
if (errno == EINTR)
goto retry;
return -1;
}
else if (ready == 0)
{
printf ("pid:%d Timeout\n",pid);
}
else
{
printf ("process: %d done\n", pid);
}
/* Parent Process */
pid_t dpid = waitpid (pid, &status, 0);
printf ("pid:%d done, exit status: %d\n", dpid, WEXITSTATUS(status));
close (pfd);
}
return 0;
}
上記のプログラムを実行し, 同じくSIGURG(23) 投げると、シグナルハンドラ実行後もイベント待ちを継続します.
caught signal poll: Interrupted system call process: 5037 done pid:5037 done, exit status: 0
sigaction の SA_RESTART では救えない
man 7 signal によると, 何やら気になる記述が.
Interruption of system calls and library functions by signal handlers
If a signal handler is invoked while a system call or library function call is blocked, then either:
* the call is automatically restarted after the signal handler returns; or
* the call fails with the error EINTR.
Which of these two behaviors occurs depends on the interface and whether or not the signal handler was established using the SA_RESTART flag (see sigaction(2)). The details vary across UNIX
systems; below, the details for Linux.
If a blocked call to one of the following interfaces is interrupted by a signal handler, then the call is automatically restarted after the signal handler returns if the SA_RESTART flag was
used; otherwise the call fails with the error EINTR:
sigaction() の SA_RESTART を使えば, シグナルハンドラ実行後, 自動的にイベント待ちに入るようなことが書いてます.
man 2 sigaction によると
SA_RESTART
Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals. This flag is meaningful only when establishing a signal handler. See
signal(7) for a discussion of system call restarting.
ふむふむ、シグナル受信後のシステムコールの再起動ができそうな感じではありますが, 詳しくは man 7 signal を読め、とあります.
再度 man 7 signal を読むと
The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART; they always fail with the error EINTR when interrupted by a
signal handler:
<snip>
* File descriptor multiplexing interfaces: epoll_wait(2), epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).
poll(), select() は残念ながら SA_RESTART の恩恵にはあずかれず, EINTR エラーで処理終了してしまう、とのこと.
ですので poll(), select() でイベントを待つ際には EINTR エラー対策が必要、となりそうです.
ただ、大抵の場合はそこまで気にしなくても…
ちょっと今まで書いていたことと矛盾するかもしれませんが, 明示的にsignalを受け付けるようにしていなければ, poll()のEINTRエラーを気にする必要はありません.
…というのも, man 7 signal によるとシグナルのデフォルトの動作は(少々乱暴ですが)
SIGABRT P1990 Core Abort signal from abort(3)
SIGALRM P1990 Term Timer signal from alarm(2)
SIGBUS P2001 Core Bus error (bad memory access)
SIGCHLD P1990 Ign Child stopped or terminated
SIGCLD - Ign A synonym for SIGCHLD
SIGCONT P1990 Cont Continue if stopped
SIGEMT - Term Emulator trap
SIGFPE P1990 Core Floating-point exception
SIGHUP P1990 Term Hangup detected on controlling terminal
or death of controlling process
SIGILL P1990 Core Illegal Instruction
SIGINFO - A synonym for SIGPWR
SIGINT P1990 Term Interrupt from keyboard
SIGIO - Term I/O now possible (4.2BSD)
SIGIOT - Core IOT trap. A synonym for SIGABRT
SIGKILL P1990 Term Kill signal
SIGLOST - Term File lock lost (unused)
SIGPIPE P1990 Term Broken pipe: write to pipe with no
readers; see pipe(7)
SIGPOLL P2001 Term Pollable event (Sys V);
synonym for SIGIO
SIGPROF P2001 Term Profiling timer expired
SIGPWR - Term Power failure (System V)
SIGQUIT P1990 Core Quit from keyboard
SIGSEGV P1990 Core Invalid memory reference
SIGSTKFLT - Term Stack fault on coprocessor (unused)
SIGSTOP P1990 Stop Stop process
SIGTSTP P1990 Stop Stop typed at terminal
SIGSYS P2001 Core Bad system call (SVr4);
see also seccomp(2)
SIGTERM P1990 Term Termination signal
SIGTRAP P2001 Core Trace/breakpoint trap
SIGTTIN P1990 Stop Terminal input for background process
SIGTTOU P1990 Stop Terminal output for background process
SIGUNUSED - Core Synonymous with SIGSYS
SIGURG P2001 Ign Urgent condition on socket (4.2BSD)
SIGUSR1 P1990 Term User-defined signal 1
SIGUSR2 P1990 Term User-defined signal 2
SIGVTALRM P2001 Term Virtual alarm clock (4.2BSD)
SIGXCPU P2001 Core CPU time limit exceeded (4.2BSD);
see setrlimit(2)
SIGXFSZ P2001 Core File size limit exceeded (4.2BSD);
see setrlimit(2)
SIGWINCH - Ign Window resize signal (4.3BSD, Sun)
- シグナルが無視される (Ign)
- シグナル受けるとプロセスが終了する (Core, Term)
のいずれか、だからです.
ただ、全体が見渡せない大規模なプログラムの場合, どこかでシグナル受信が有効になっている可能性がありますので, EINTRエラーの対応はしておいた方がよいでしょう.
今回のサンプルコード
今回紹介したサンプルコードも github のこちらにおいてます.
参考になれば幸いです.
では