pidfd_open
pkill 打つたびに、「これpid再利用されたら地球が爆発するよな…」とか思っていたのだけど、今はpidfd_openがあるので助かった。
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <poll.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags) {
return syscall(__NR_pidfd_send_signal, pidfd, sig, info,
flags);
}
static int
pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
int
main(int argc, char **argv)
{
int child;
int use_pidfd = 1;
if (argc > 1) {
use_pidfd = atoi(argv[1]);
}
if (use_pidfd) {
puts("use pidfd_send_signal");
} else {
puts("use kill");
}
if ((child=fork()) == 0) {
exit(0);
} else {
printf("child = %d\n", child);
int status;
int pidfd = pidfd_open(child, 0);
int r = pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
if (r < 0) {
perror("pidfd_send_signal");
return 1;
}
char path[1024];
sprintf(path, "/proc/%d", child);
int pidfd2 = open(path, O_RDONLY);
r = pidfd_send_signal(pidfd2, SIGUSR1, NULL, 0);
if (r < 0) {
perror("pidfd_send_signal");
return 1;
}
int fd2 = openat(pidfd2, "cmdline", O_RDONLY);
if (fd2 < 0) {
perror("cmdline");
return 1;
}
close(fd2);
wait(&status);
for (int i=0; i<1000000; i++) {
int child2;
if ((child2 = fork()) == 0) {
int self = getpid();
if (self == child) {
sleep(10);
}
exit(0);
} else {
if (child2 == child) {
if (use_pidfd) {
int r = pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
perror("send signal");
int fd = openat(pidfd2, "cmdline", O_RDONLY);
perror("openat");
close(fd);
exit(0);
} else {
int r = kill(child, SIGUSR1);
perror("kill");
sprintf(path, "/proc/%d/cmdline", child);
int fd = open(path, O_RDONLY);
perror("open");
close(fd);
exit(0);
}
}
wait(&status);
}
}
}
}
この実験プログラムはpid一周するのを期待しているけど、今のLinuxデフォルト値だとpid一周するまで時間かかるので、pid最大値下げて実験するのをおすすめする。
$ echo 65536 > /proc/sys/kernel/pid_max
最初に子プロセスを立ち上げて、即終了する。このpidに対して、シグナルを送った場合、どのプロセスにもシグナルが届いてほしくない。
しかし、シグナル送るまでに大量のforkを実行すると、pidが一周して、既存の kill(2) だと、新しくできたプロセスにシグナルを送ってしまう。
そこで、pidfdを使う。
pidfd_open で開いた FD は、そのプロセスがいなくなるとどのプロセスも指さなくなる。pid が一周したあと、pidfd に対してシグナルを送っても、意図しないプロセスには届かない。
$ ./a.out 0
use kill
child = 7416
kill: Success
open: Success
$ ./a.out 1
use pidfd_send_signal
child = 7418
send signal: No such process
openat: No such process
まあでも今のpkillはstraceで見たけどそういう実装にはなってないっぽい?
/proc/PID を開いても、pidfd は取得できる。これで、安全なpkillができるはず。