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) { /* プロセスが生きてるあいだはpidfd_send_signalが届く */ 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) { /* /proc/PID も pidfd として使える */ perror("pidfd_send_signal"); return 1; } int fd2 = openat(pidfd2, "cmdline", O_RDONLY); if (fd2 < 0) { /* プロセスが生きてる間は openat で cmdline が開ける */ 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 # killを使う use kill child = 7416 kill: Success # PID一周するとシグナルが送れてしまう (Successと出てるが、ここは失敗してほしい) open: Success # /proc/PID/cmdline も開ける $ ./a.out 1 # pidfd_send_signalを使う use pidfd_send_signal child = 7418 send signal: No such process # PID 一周するとシグナル送れない openat: No such process # /proc/PID/cmdine も開けない
まあでも今のpkillはstraceで見たけどそういう実装にはなってないっぽい?
/proc/PID を開いても、pidfd は取得できる。これで、安全なpkillができるはず。