UNIX/Linuxの「デーモン」はこうやって作る

by Jeff Hitchcock
WindowsやAndroidを搭載したデバイスの動作を軽快にする方法を調べると「タスク」や「プロセス」「デーモン」といった言葉が目に入ります。特にデーモンはUNIX/Linuxにおいて、ユーザーとの対話を行うための制御端末を持たないバックグラウンドプロセスとして、ウェブサーバーやメールサーバーの役割を担う存在。そのデーモンの生成方法について、エンジニアのAryaman Sharda氏が説明しています。
Understanding Daemons (in Unix) - Digital Bunker
https://blog.digitalbunker.dev/2020/09/03/understanding-daemons-unix/
デーモンとは、バックグラウンドで動作し特定のイベント発生を待機しているプロセスのこと。代表的なデーモンとしてはウェブサーバーのhttpdや、SSH接続を提供するsshdなどがあります。
UNIX/LinuxはOSブート時、initと呼ばれるプロセスを起動し、「1」をプロセスの識別番号であるPIDとしてinitに割り当てるとSharda氏。initは制御端末を持たないデーモンで、すべてのプロセスはinitによって起動される子プロセスとなります。Linuxで「pstree」コマンドを実行すると、initがすべてのプロセスの頂点に位置していることがよくわかります。

ブートが完了すると、OSはあらかじめ用意されたデーモンを起動していくとのこと。これらのデーモンは子プロセスを生成するfork関数とプロセスを正常終了するexit関数によって生成され、最終的にはinitプロセスを親プロセスに持つプロセスとなります。
また、最近のLinuxディストリビューションはsystemdを採用しているため、すべてのプロセスの親プロセスであるPID 1のプロセスはsystemdとなっているのが一般的です。

新しくデーモンを生成するには、まずfork関数を呼び出して、子プロセスを生成するとのこと。

子プロセスは、親プロセスのCPUレジスタを共有するので、生成された子プロセスと親プロセスは両方ともfork関数後の命令を並列実行するとSharda氏は説明。親プロセスをexit関数で終了すれば、子プロセスは自動的にinitを親プロセスとするデーモンになります。

実際にデーモンを実装する場合は、もう少し複雑な処理が必要とのこと。Sharda氏は以下のサンプルコードをもとに、デーモンの作り方を説明しています。
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <signal.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <syslog.h> | |
static void create_daemon() | |
{ | |
pid_t pid; | |
/* Fork off the parent process */ | |
/* Remember fork() returns the child's PID in the parent */ | |
/* and return 0 as the PID to the child process */ | |
pid = fork(); | |
/* An error occurred */ | |
if (pid < 0) | |
exit(EXIT_FAILURE); | |
/* Success: Let the parent terminate */ | |
if (pid > 0) | |
exit(EXIT_SUCCESS); | |
/* On success: The child process becomes session leader */ | |
/* setsid() creates a session and returns the (new) session ID of the calling process */ | |
/* A session is a group of processes running under control of a single user */ | |
/* We use setsid() because if we just kill the parent the child will be killed too */ | |
/* Creating a session in the child process allows it to live even when the parent dies */ | |
if (setsid() < 0) | |
exit(EXIT_FAILURE); | |
/* Catch, ignore and handle signals */ | |
/* A signal is an asynchronous notification sent to a process or to a */ | |
/* specific thread within the same process in order to notify it of an event */ | |
/* that occurred. In the following 2 lines, we are intentionally ignoring the */ | |
/* SIGCHILD and SIGHUP signals */ | |
/* When a child process stops or terminates, SIGCHLD is sent to the parent process. */ | |
signal(SIGCHLD, SIG_IGN); | |
/* SIGHUP ("signal hang up") is a signal sent to a process when its controlling | |
/* terminal is closed. */ | |
signal(SIGHUP, SIG_IGN); | |
/* Fork off for the second time (more details about this after the code)*/ | |
pid = fork(); | |
/* An error occurred */ | |
if (pid < 0) | |
exit(EXIT_FAILURE); | |
/* Success: Let the parent terminate */ | |
if (pid > 0) | |
exit(EXIT_SUCCESS); | |
/* Set new file permissions */ | |
/* unmask() is used to control the file-creation mode mask, which determines */ | |
/* the initial value of file permission bits for newly created files. */ | |
/* ******************************************************************** */ | |
/* unmask(0) means that newly created files or directories created will have no | |
/* privileges initially revoked. In other words, a umask of zero will cause all */ | |
/* files to be created as 0666 or world-writable. Directories created while umask is | |
/* 0 will be 0777 */ | |
umask(0); | |
/* Change the working directory to the root directory */ | |
/* or another appropriated directory */ | |
chdir("/"); | |
/* Close all open file descriptors */ | |
/* sysconf() returns configuration information at run time */ | |
int x; | |
for (x = sysconf(_SC_OPEN_MAX); x>=0; x--) | |
{ | |
close (x); | |
} | |
} | |
int main() | |
{ | |
create_daemon(); | |
while (1) | |
{ | |
/* Respond to HTTP requests */ | |
/* Run time based operations (cron jobs) */ | |
/* Monitor disk health */ | |
/* Clean up unused resources */ | |
/* Any other process you want to run in the background */ | |
/* You could also introduce a delay between iterations */ | |
// sleep (20); | |
/* Or terminate the daeom when some other condition is met */ | |
// if (...) { | |
// break; | |
// } | |
} | |
return EXIT_SUCCESS; | |
} |
デーモンを生成するためのcreate_daemon関数内の最初の処理として、変数pidを初期化してfork関数を呼び出し、子プロセスを生成しています。
static void create_daemon() { pid_t pid; pid = fork();
fork関数が正しく終了しているかどうかの確認も実行。PIDが正の値であれば子プロセスが正しく生成されたと判定しているとのこと。
if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS);
続いて、setsid関数で新しいセッションを作成します。セッションはひとつの制御端末に対応するプロセスの集まりで、セッションリーダーとなる親プロセスを終了すると、すべての子プロセスも同じく終了します。ただし、新しいセッションを作成した時点では、セッションは制御端末を持っていません。制御端末を持たないプロセスを生成できたことでデーモン生成は完了したかに思えますが、処理は続きます。
。
if (setsid() < 0) exit(EXIT_FAILURE);
制御端末や子プロセスから送信されるシグナルを無視し、デーモン生成に影響が出ないように処理が行われています。
signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN);
新しいセッションを作成した後、fork関数をもう一度呼び出し。setsid関数で制御端末を持たないプロセスを生成していますが、このままではプロセスが新規セッションのセッションリーダーであるため、ユーザーが別の端末を開いた場合などに制御端末を取得してしまう可能性があるとのこと。制御端末を持たないデーモンを確実に生成するために、まずsetsid関数で既存のセッションとは別の新しいセッションにプロセスを移動し、さらに制御端末との関連付けを防ぐために、もう一度子プロセスを生成して親プロセスを終了することで、制御端末を取得する可能性をゼロにしているというわけです。
pid = fork(); if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS);
続いて新しくファイルやディレクトリを作成する際のumaskを設定。UNIX/Linuxではファイルを新しく作成する際の権限は0666(所有者、グループ、その他ともに読み書き可能)、ディレクトリは0666に実行権限を追加した0777で作成されますが、例えばumaskを0022に設定すると、ファイルの権限は0644(所有者は読み書き可能、グループ、その他は読み取りのみ可能)、ディレクトリの権限は0755(所有者は読み書き実行可能、グループ、その他は読み取りと実行のみ可能)で新規作成されるようになります。このコードではumaskを0000に設定しています。
umask(0);
ルートディレクトリに移動して、プロセスが持つすべてのファイルディスクリプタを閉じます。
chdir("/"); int x; for (x = sysconf(_SC_OPEN_MAX); x>=0; x--) { close (x); }
後はメイン関数内でcreate_daemon関数を呼び出し、無限ループ内に処理を記述すればデーモンの完成。
int main() { create_daemon(); while (1) { 何らかの処理 } return EXIT_SUCCESS; }
ちなみに、デーモンという単語はエントロピー増大則に反する思考実験上の存在「マクスウェルの悪魔(Maxwell's demon)」のように、裏側でこっそり仕事を行うことから名付けられています。
UNIX/Linuxでよく使われる「Daemon」(デーモン)プロセスの語源とは? - GIGAZINE

・関連記事
Windowsの「タスクマネージャー」を開発した本人が直々に使い方や知られざる機能を伝授 - GIGAZINE
全能テキストエディタ「Vim」の歴史と開発者に広く普及した理由 - GIGAZINE
「Linux搭載PC」を名刺にしてしまった猛者が登場 - GIGAZINE
無料でiPhoneやiPad上でコマンドやプログラムを実行できるターミナルアプリ「a-shell」 - GIGAZINE
「Goの父」ロブ・パイクの「プログラミング5カ条」、ネット上で話題に - GIGAZINE
おなじみ「ping」コマンドの生みの親が20年以上前に開発秘話を記したブログ - GIGAZINE
・関連コンテンツ
in ソフトウェア, Posted by darkhorse_log
You can read the machine translated English article How to make a UNIX/Linux 'daemon'….