killコマンドのあれこれ

(´-`).。oO(killコマンドを提供するプロジェクト多いなって

killコマンドを提供するプロジェクト

とりあえず気付いた範囲で4つ。

上のリストはmanページへのリンクになっているのでオプションの違いとかはそちらを参照してください。busyboxのkillは最小構成って感じなのでオプションは1つしかないですね。-sオプションはbusybox以外は実装してますね。で、-lオプションは全てに存在。まあ、これはシグナル名を表示するので無いと困りますね。

ディストリビューションのkillコマンド

じゃあ、ディストリビューションはどのkillコマンドを採用してるのか?ということで手元にあるディストリビューションを確認したところこんなふうになってました。

ディストリビューション killコマンドを提供するパッケージ
fedora 31 util-linux
centos 8 util-linux
arch linux(docker image) util-linux
ubuntu 18.04 procps-ng
alpine linux(docker image) busybox

killコマンドの実装

ソースを見てみます。調べるときに実際に動作させてなくてコードを読んだだけなのでもしかしたら間違ってる可能性もありますが。

util-linux

kill_verbose()がkill(2)でsignalをプロセスに投げているところのようです。

static int kill_verbose(const struct kill_control *ctl)
{
    int rc = 0;

    if (ctl->verbose)
        printf(_("sending signal %d to pid %d\n"), ctl->numsig, ctl->pid);
    if (ctl->do_pid) {
        printf("%ld\n", (long) ctl->pid);
        return 0;
    }
#ifdef HAVE_SIGQUEUE
    if (ctl->use_sigval)
        rc = sigqueue(ctl->pid, ctl->numsig, ctl->sigdata);
    else
#endif
        rc = kill(ctl->pid, ctl->numsig);

    if (rc < 0)
        warn(_("sending signal to %s failed"), ctl->arg);
    return rc;
}

if文でctl->do_pidをチェックしているところは-pオプションの処理です。util-linuxのkillはsigqueue(3)が利用できる場合はkill(2)ではなくてsigqueue(3)を使うんですね。sigqueue(3)の場合はデータを付加できるのでできるならこっちを使おうってことですね。これはmanにも書かれていますが-qオプションを使うとプロセスに送るデータを設定できます。

procps-ng

procps-ngの場合はkill_main()がkillを実現する関数っぽいです。 この関数の主要なところはここです。

 for (i = 0; i < argc; i++) {
        pid = strtol_or_err(argv[i], _("failed to parse argument"));
        if (!kill((pid_t) pid, signo))
            continue;
        error(0, errno, "(%ld)", pid);
        exitvalue = EXIT_FAILURE;
        continue;
    }

こちらはこれといって特殊なことはしてないですね。

coreutils

coreutisの場合はsend_signals()という関数がkill(2)を呼んでいます。

static int
send_signals (int signum, char *const *argv)
{
  int status = EXIT_SUCCESS;
  char const *arg = *argv;

  do
    {
      char *endp;
      intmax_t n = (errno = 0, strtoimax (arg, &endp, 10));
      pid_t pid = n;

      if (errno == ERANGE || pid != n || arg == endp || *endp)
        {
          error (0, 0, _("%s: invalid process id"), quote (arg));
          status = EXIT_FAILURE;
        }
      else if (kill (pid, signum) != 0)
        {
          error (0, errno, "%s", quote (arg));
          status = EXIT_FAILURE;
        }
    }
  while ((arg = *++argv));

  return status;
}

これも短い関数ですね。こちらもprocps-ng同様にkill(2)を呼ぶだけです。

busybox

busyboxの場合はkill_main()という関数でkillの処理を行っています。余談ですがbusyboxのkill.cはprocps/にあるし、procps-ngと同じ感じにしてるんですかね?

#if ENABLE_KILL
    /* Looks like they want to do a kill. Do that */
    while (arg) {
# if SH_KILL
        /*
        * We need to support shell's "hack formats" of
        * " -PRGP_ID" (yes, with a leading space)
        * and " PID1 PID2 PID3" (with degenerate case "")
        */
        while (*arg != '\0') {
            char *end;
            if (*arg == ' ')
                arg++;
            pid = bb_strtoi(arg, &end, 10);
            if (errno && (errno != EINVAL || *end != ' ')) {
                bb_error_msg("invalid number '%s'", arg);
                errors++;
                break;
            }
            if (kill(pid, signo) != 0) {
                bb_perror_msg("can't kill pid %d", (int)pid);
                errors++;
            }
            arg = end; /* can only point to ' ' or '\0' now */
        }
# else /* ENABLE_KILL but !SH_KILL */
        pid = bb_strtoi(arg, NULL, 10);
        if (errno) {
            bb_error_msg("invalid number '%s'", arg);
            errors++;
        } else if (kill(pid, signo) != 0) {
            bb_perror_msg("can't kill pid %d", (int)pid);
            errors++;
        }
# endif
        arg = *++argv;
    }
    return errors;
#endif

コードにはSH_KILLは通常定義されてるのかどうかわかりませんが、定義の有無にかかわらずkill(2)を使うだけですね。

実装の差異

util-linuxはsigqueue(3)が利用可能ならそれを使い、利用できなければkill(2)を呼ぶという実装になっていますが、それ以外はkill(2)を呼ぶ形でした。

まとめ

Linuxでは基本的とも言えるkillコマンドですが、このコマンドを提供するパッケージは複数あり、ディストリビューションによってどのパッケージのkillコマンドを使うか選択していました。 また、実装でも微妙な違いがあるということがわかりました( ´ー`)フゥー...