はじめに
Linux を使っているとよく出てくる「デーモン」という存在。
「そもそもデーモンって何?」「なぜそんな儀式めいたことをするの?」
そういった問いを、手元でデーモンを再現してみながら観察・まとめた記録です。
ちゃんと理解してるかどうかの確認も兼ね、観察ログや再現コードも含めて書き残しておきます。
デーモン を ひとこと で
バックグラウンドに常駐し、黙々と自分の責務を果たすべく孤児となったプロセスのこと。
生まれながらにデーモンだった者はいない ――
孤児となることで、初めて一人前のデーモンとなります。
デーモンにならなくても、 &
や nohup
でバックグラウンドプロセスとして動かしておくだけではだめ?
&
や nohup
では TTY やセッションとの結びつきが残るため、完全に自律したサービスとは言えません。
「init や systemd に管理してほしい」「ログを一元化したい」「再起動も任せたい」「ほかのサービスと連携したい」といった目的のためには、デーモン化する必要があります。
ただし、デーモンになっただけでは init や systemd にサービスとして自動的に “管理” されるわけではありません。
init 時代には、デーモン化したプロセスを PID ファイルなどで明示的に管理対象とする必要がありました。
一方、systemd では、サービス用の unit ファイルを用意しておくことで、起動・停止・監視まで一貫して任せることができます。
なぜ デーモン は孤児となる必要があるのか?
孤児になったプロセスは、カーネルにより、PID 1 のプロセス(init や systemd)が親となり引き取ってくれるためです。
親(プロセス)は子のために自ら命を絶つ。
孤児となることで、誰からも干渉されず独立して動けるようになります (= 以下が可能となる)
- TTY(端末)やユーザーセッションとの切り離し
- ログインユーザーがログアウトしても終了しない(セッション切断耐性)
TTY とは?
TTY(テレタイプ端末)とは、もともと物理的な端末装置(キーボード + プリンタのような機械)に由来する、文字情報の入出力を行う装置のことです。
現代の Linux では、その流れを引き継いで、ターミナルとの接続を表すデバイスファイル(例: /dev/pts/0 など)が「TTY」と呼ばれます。
プロセスは、自分にひもづけられた TTY を通じてキーボード入力を受け取り、画面出力を行います。
セッションリーダー とは?
自らセッションを開始し(setsid())、そのセッションの代表となったプロセスのことです。
また、TTY が切断されたときに送られる SIGHUP(Hang Up シグナル)を受け取るのもセッションリーダーです。
SID が同じプロセスは同じセッション。
bash 自身の PID = SID ← 自分でセッションを開始してるプロセスがセッションリーダー。
セッションリーダーを起点にプロセスグループが構成される。
手元のターミナルで PID や SID を観察してみる
PID と SID が一致しているため、この bash は自らセッションを開始した「セッションリーダー」
$ ps -o pid,ppid,sid,pgid,tty,cmd
PID PPID SID PGID TT CMD
114587 114581 114587 114587 pts/1 bash
167542 114587 114587 167542 pts/1 ps -o pid,ppid,sid,pgid,tty,cmd
デーモンのお作法
python による "もどき" 作成例
デーモン化には、親を殺し、セッションを抜け、入出力を閉じ……といった一連の“儀式”があります。
かつては各サービスがそれを自前で書いていた時代もありました。
ここでは、その流れを Python で再現してみます。
import os
import time
def log(msg):
with open("/logs/fork_test.log", "a") as f:
f.write(f"{msg}\n")
try:
tty = os.ttyname(0)
except OSError:
tty = 'no tty'
# 生まれたてのプロセス
log(f"[start] pid={os.getpid()} ppid={os.getppid()} sid={os.getsid(0)} pgid={os.getpgrp()} tty={tty}")
# 1回目の fork 、そして親プロセスを終了(ターミナルから切り離す)
pid = os.fork()
if pid > 0:
log(f"[exit 1st parent] pid={os.getpid()}")
os._exit(0)
# setsid() でセッションリーダーになる
os.setsid()
log(f"[setsid] pid={os.getpid()} sid={os.getsid(0)}")
# 2回目の fork、そして脱セッションリーダー
pid = os.fork()
if pid > 0:
log(f"[exit 2nd parent] pid={os.getpid()}")
os._exit(0)
# カレントディレクトリを / に変更(マウント保持防止)
os.chdir("/")
# 標準入出力を /dev/null にリダイレクト
fd = os.open('/dev/null', os.O_RDWR)
os.dup2(fd, 0) # stdin
os.dup2(fd, 1) # stdout
os.dup2(fd, 2) # stderr
log(f"[daemon-ish] pid={os.getpid()} sid={os.getsid(0)}")
time.sleep(30)
ログの出力結果
# cat /logs/fork_test.log
[start] pid=13 ppid=7 sid=7 pgid=13 tty=/dev/pts/1 # tty 持ってる。親と同じセッション
[exit 1st parent] pid=13
[setsid] pid=14 sid=14 # 自分がセッションリーダーになった
[exit 2nd parent] pid=14
[daemon-ish] pid=15 sid=14 # pid != SID なのでセッションリーダーではない
init 時代のデーモン
各サービスが、自分でデーモンのお作法を持つ必要があった。
このときの問題点:
- 各プロセスが PID ファイルを自前で管理 → 競合や消し忘れの温床
- ログを
/var/log/xxx.log
に書く → 出力先がバラバラで追跡が面倒-
syslog
といったログを統一的に管理するための仕組みも存在していたが、
パフォーマンスや柔軟性の課題から、アプリケーション側で独自管理されるケースも多かった
-
- プロセス監視や自動再起動を各自で工夫 → 品質・安定性に差が出る
- スクリプトの依存関係(ネットワーク・DB など)を順番で解決していた ← しんどい
systemd 登場以降のデーモン
サービスは systemd に管理されることを前提として作られるようになり、お作法を持つことをやめた。
systemd に管理を任せることで、以下を自動でやってもらえるようになった:
- サービスのデーモン化(バックグラウンド化)
- セッションの儀式(TTY切断、親プロセスとの分離)
- 自動再起動(
Restart=
オプション) - 起動順や依存関係の制御(
After=
,Requires=
) - リソース制御(CPU/メモリ制限)
- ログの集約と一元管理(
journalctl
)
→ サービス側は「フォアグラウンドで黙々と本業に専念するだけ」でよくなり、開発者の負担が大きく減った
おわりに
ここまでまとめてみての感想は、「デーモン化」とは単なるテクニックではなく、
プロセスが自分だけの責務に集中するために、環境とのつながりを意図的に断ち切る手順なんだな、と感じました。
セッションを断ち、TTY と別れ、親の手を離れ、自分の責務だけを静かに果たす――
そんなプロセスの旅路を、実際に ps
を眺めたり、fork してみたりしながら追いかけたことで、ようやく頭の中に「構造」として定着してきたような気がします。
同じように「デーモンって何だろう……」と思っている人の助けになればうれしいです!
🔗 関連記事