PK
パーネルカニック研究所
今日もどこかでkernel panic。
root@pk-lab:~#

daemonとは何か

連載ナビ

この連載は「Linuxを使えるようになる」ではなく、「Linuxがどう動いているのか」を理解するための実践シリーズです。

今回は Linuxを理解する 〜 LFSで学ぶLinux内部構造 〜 の Part 5 / 第4回として、仕組みのつながりを意識しながら学びます。

—

daemonは「端末から切り離されて常駐するプロセス」

前回、systemdが起動・監視する「サービス」の話をしました。そのサービスの実体がdaemon(デーモン)です。

結論から言うと、daemonとは、制御端末(ターミナル)から切り離され、バックグラウンドで常駐し続けるプロセスのことです。SSH接続を受け付ける sshd、定期実行を担う cron、Webリクエストを処理する nginx はすべてdaemonです。慣習として名前の末尾に d が付くものが多く(sshd、systemd、journald、containerd)、名前を見ただけで「常駐プロセスだな」と推測できます。

普段のコマンド(ls や cp)との違いは、ライフサイクルにあります。

  • 通常のコマンド: 端末から起動され、仕事を終えたらすぐ終了する
  • daemon: システム起動時などに起動され、リクエストを待ち受けて終了しない。ログアウトしても動き続ける

Dockerを使っている方なら、docker コマンドを打つたびに裏で応答している dockerd がまさにこれです。

「ログアウトしたら死ぬ」プロセスではサーバーが成り立たないから

なぜ「端末から切り離す」ことがdaemonの定義に入っているのか。理由は、端末に結びついたままのプロセスは、その端末が閉じられると道連れに終了してしまうからです。

Linuxでは、端末で起動したプロセスはその端末のセッションに属します。SSH接続が切れたり端末を閉じたりすると、セッション内のプロセスにはSIGHUP(ハングアップシグナル)が送られ、通常は終了します。サーバーで ./myserver & と起動してログアウトしたらサーバーも死んでいた、という事故はこの仕組みによるものです(nohup はこのSIGHUPを無視させるためのコマンドです)。

そこで伝統的なdaemonは、起動時に自分自身を端末から切り離す「daemon化」という一連の処理を行ってきました。

1. fork() して親プロセスは終了する(起動したシェルへ制御を返すため)

2. setsid() で新しいセッションを作り、制御端末との関係を断つ

3. 作業ディレクトリを / に移す(マウント解除の邪魔をしないため)

4. 標準入出力・標準エラー出力を閉じるか /dev/null に付け替える(端末がもうないため)

ただし現代では、この作法を自前で実装する必要はほぼありません。systemdがサービスとして起動するプロセスは最初から端末を持たないため、unitファイルに Type=simple と書けば、プログラム側はフォアグラウンドで動くだけのシンプルな作りで済みます。「コンテナ内のプロセスはフォアグラウンドで動かす」というDockerの定石と同じ発想が、systemd管理下のdaemonにも当てはまるわけです。

psコマンドでdaemonの「特徴」を観察する

daemonかどうかは ps の出力から見分けられます。


ps -ef | grep -E 'sshd|cron|nginx' | grep -v grep

出力例です(環境により異なります)。


UID          PID    PPID  C STIME TTY          TIME CMD
root         820       1  0 10:00 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener]
root         825       1  0 10:00 ?        00:00:00 /usr/sbin/cron -f
root        1234       1  0 10:01 ?        00:00:00 nginx: master process /usr/sbin/nginx
www-data    1235    1234  0 10:01 ?        00:00:00 nginx: worker process

注目すべき列は2つです。

  • TTY列が ?: 制御端末を持っていない証拠です。自分が端末から起動したプロセス(たとえば vim)は pts/0 のような端末名が入りますが、daemonは ? になります
  • PPID(親プロセスID)が 1: 親がPID 1、つまりsystemd直下で動いていることを示します。前回見た「systemdがサービスを起動・監視する」構図が、この数字に表れています

nginxの行をよく見ると、master(PID 1234)とworker(PPID 1234)の親子関係も読み取れます。また、sshdは接続を受けるたびに接続処理用の子プロセスをforkするため、誰かがSSHログイン中なら sshd: user@pts/0 のような子プロセスの行が増えます。「待ち受ける親」と「処理する子」という構成は、多くのサーバーソフトウェアに共通するパターンです。

最小のdaemonを自分で作って動かす

理解を確実にするために、自作のプロセスをsystemd管理のdaemonにしてみます。まず、10秒おきにメッセージを出し続けるだけのスクリプトを用意します。


sudo tee /usr/local/bin/hello-loop.sh << 'EOF' > /dev/null
#!/bin/sh
while true; do
  echo "hello from my daemon"
  sleep 10
done
EOF
sudo chmod +x /usr/local/bin/hello-loop.sh

次に、前回学んだunitファイルを /etc/systemd/system/ に作ります。


sudo tee /etc/systemd/system/hello.service << 'EOF' > /dev/null
[Unit]
Description=Minimal example daemon

[Service]
Type=simple
ExecStart=/usr/local/bin/hello-loop.sh
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

起動して観察します。


sudo systemctl daemon-reload
sudo systemctl start hello
systemctl status hello
ps -ef | grep hello-loop | grep -v grep

ps の出力で、TTYが ?、PPIDが 1 になっていることを確認してください。スクリプト自体はただの無限ループで、daemon化の処理は一切書いていません。それでも端末から切り離された常駐プロセスになっているのは、systemdが端末なしで起動してくれているからです。確認が終わったら sudo systemctl stop hello で停止し、sudo rm /etc/systemd/system/hello.service と sudo systemctl daemon-reload で片付けられます。

常駐プロセスの次の疑問は「出力はどこへ行くのか」

daemonとは、制御端末を持たず(TTYが ?)、多くはsystemd直下(PPIDが1)で常駐し続けるプロセスであり、現代ではdaemon化の作法をsystemdが肩代わりしてくれる——これが今回のポイントです。

ここで1つ疑問が残ります。先ほどの自作daemonは echo でメッセージを出していましたが、端末がないのにあの出力はどこへ行ったのでしょうか。答えは「systemd-journaldが回収してログとして保存している」です。その確認方法、つまりsystemd環境でのログの読み方が、次回のテーマjournalctlです。

図解テキスト


client request
  -> daemon process(TTYなし・常駐・多くはPID 1直下)
  -> response

伝統的なdaemon化:  fork -> setsid -> chdir / -> 標準入出力を閉じる
現代のやり方:      systemdのunitで Type=simple(切り離しはsystemdが担当)

この回で実行するコマンド

上から順番に実行すれば、この回の内容を体験できます。

まずはそのままコピペして実行し、次に1行ずつ意味を確認してください。


# 代表的なdaemonを観察(TTY列とPPID列に注目)
ps -ef | grep -E 'sshd|cron|nginx' | grep -v grep

# 自分の端末で動くプロセスと比較(TTY列に pts/ が入る)
ps -o pid,ppid,tty,comm

# systemd直下(PPID=1)のプロセスを数えてみる
ps -ef | awk '$3 == 1' | head -n 20

ハンズオン手順

1. ターミナルを開いて、上のコマンドを1行ずつ実行する

2. daemonのTTY列(?)と、自分のシェルのTTY列(pts/0 など)の違いをメモする

3. 余裕があれば本文の hello.service を作成・起動し、ps でTTYとPPIDを確認して片付ける

4. エラーが出たら、そのエラーメッセージをそのまま残す

チェックポイント

  • コマンドが最後まで実行できた
  • ps の出力からdaemonを見分ける2つの特徴(TTYが ?、PPIDが1)を言える
  • 「なぜ端末から切り離す必要があるのか」をSIGHUPと絡めて説明できる
  • 次の回に進む前に、分からない単語を1つ調べた

今回理解できたこと

  • daemonはサーバー機能の実体(端末を持たない常駐プロセス)
  • systemdと組み合わせて安定運用する(daemon化・監視・再起動はsystemdの仕事)
  • ps -ef のTTY列とPPID列でdaemonを見分けられる