Linuxでデーモンを作成する


110

Linuxでは、停止できない、ファイルシステムの変更を監視するデーモンを追加したいと思います。変更が検出された場合は、開始されたコンソールへのパスと改行を書き込みます。

ファイルシステムを変更するコードはほぼ準備ができていますが、デーモンの作成方法がわかりません。

私のコードはここからです:http : //www.yolinux.com/TUTORIALS/ForkExecProcesses.html

フォークの後に何をしますか?

int main (int argc, char **argv) {

  pid_t pID = fork();
  if (pID == 0)  {              // child
          // Code only executed by child process    
      sIdentifier = "Child Process: ";
    }
    else if (pID < 0) {
        cerr << "Failed to fork" << endl;
        exit(1);
       // Throw exception
    }
    else                                   // parent
    {
      // Code only executed by parent process

      sIdentifier = "Parent Process:";
    }       

    return 0;
}


1
重複の可能性:stackoverflow.com/questions/5384168/…デーモン化部分の場合、stackoverflow.com
/ questions / 931093 /…

POSIX準拠が必要ない場合は、inotifyAPI に興味があるかもしれません。参照:inotify_initinotify_add_watchinotify_rm_watch
patryk.beza 2016

回答:


216

Linuxでは、停止できないファイルシステムの変更を監視するデーモンを追加したいと考えています。変更が検出された場合は、開始されたコンソールへのパスと改行を書き込む必要があります。

デーモンはバックグラウンドで動作し、(通常は...)TTYに属していないため、おそらくstdout / stderrを希望どおりに使用できません。通常、syslogデーモン(syslogd)は、メッセージをファイル(デバッグ、エラーなど)に記録するために使用されます。

その上、プロセスをデーモン化するために必要ないくつかの手順があります。


私が正しく覚えている場合、これらの手順は次のとおりです。

  • 親プロセスをフォークして、フォークが成功した場合は終了させます。->親プロセスが終了したため、子プロセスはバックグラウンドで実行されます。
  • setsid-新しいセッションを作成します。呼び出しプロセスは、新しいセッションのリーダーと、新しいプロセスグループのプロセスグループリーダーになります。これで、プロセスは制御端末(CTTY)から切り離されました。
  • シグナルをキャッチ - シグナルを無視または処理します。
  • 再びforkして、親プロセスを終了させ、セッションをリードするプロセスを確実に取り除くようにします。(セッションリーダーのみが再度TTYを取得できます。)
  • chdir-デーモンの作業ディレクトリを変更します。
  • umask-デーモンの必要に応じてファイルモードマスクを変更します。
  • close-親プロセスから継承される可能性のある開いているすべてのファイル記述子を閉じます。

出発点として、基本的な手順を示すこのスケルトンコードを見てください。このコードは、GitHub:Linuxデーモンの基本スケルトンでもフォークできます。

/*
 * daemonize.c
 * This example daemonizes a process, writes a few log messages,
 * sleeps 20 seconds and terminates afterwards.
 */

#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 skeleton_daemon()
{
    pid_t pid;

    /* Fork off the parent 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 */
    if (setsid() < 0)
        exit(EXIT_FAILURE);

    /* Catch, ignore and handle signals */
    //TODO: Implement a working signal handler */
    signal(SIGCHLD, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    /* Fork off for the second time*/
    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 */
    umask(0);

    /* Change the working directory to the root directory */
    /* or another appropriated directory */
    chdir("/");

    /* Close all open file descriptors */
    int x;
    for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
    {
        close (x);
    }

    /* Open the log file */
    openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
    skeleton_daemon();

    while (1)
    {
        //TODO: Insert daemon code here.
        syslog (LOG_NOTICE, "First daemon started.");
        sleep (20);
        break;
    }

    syslog (LOG_NOTICE, "First daemon terminated.");
    closelog();

    return EXIT_SUCCESS;
}


  • コードをコンパイルします。 gcc -o firstdaemon daemonize.c
  • デーモンを起動します。 ./firstdaemon
  • すべてが適切に機能しているかどうかを確認します。 ps -xj | grep firstdaemon

  • 出力は次のようになります。

+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| PPID | PID | PGID | SID | TTY | TPGID | STAT | UID | 時間| CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ?| -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

ここに表示されるのは次のとおりです。

  • デーモンには制御端末がありません(TTY =?
  • 親プロセスID(PPID)は1(初期プロセス)
  • PID!= SID私たちのプロセスがセッションのリーダーではないことを意味
    (理由は二フォークの())
  • PID!= SIDなので、プロセスはTTYを再び制御できません。

Syslogの読み取り:

  • syslogファイルを見つけます。鉱山はここにあります:/var/log/syslog
  • 次を実行します。 grep firstdaemon /var/log/syslog

  • 出力は次のようになります。

  firstdaemon [3387]:最初のデーモンが起動しました。
  firstdaemon [3387]:最初のデーモンが終了しました。


注: 実際には、シグナルハンドラーを実装し、ログを適切に設定することもできます(ファイル、ログレベル...)。

参考文献:


わぁありがとう!それは素晴らしいことです。だから私は私のコードをwhileループに入れなければなりません、そしてそれはそれですか?
chrisMe 2013

基本的にそうです。しかし、このコードは単なる例です。デーモンプロセスを使用して何を実現たいかによります。この回答も必ずお読みください:@Edwin
Pascal Werkl 2013

1
2番目の代わりにfork()、なぜ単に使用しないのsetsid()ですか?
キメラ2013

1
このsigaction()関数は、信号を制御するためのより包括的で信頼性の高いメカニズムを提供するsigaction()signal()
patryk.beza 16

4
この方法は「古い」方法であることを視聴者に注意してください。デーモンを作成するための新しい推奨される方法は、「新しいスタイルデーモン」であるここで見つける:0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons
スター・ロード

30

man 7 daemonデーモンの作成方法について詳しく説明しています。私の答えはこのマニュアルからの抜粋です。

デーモンには少なくとも2つのタイプがあります。

  1. 従来のSysVデーモン(旧式)、
  2. systemdデーモン(新しいスタイル)。

SysVデーモン

従来のSysVデーモンに関心がある場合は、次の手順を実装する必要があります

  1. 標準入力出力、およびエラー(つまり、最初の3つのファイル記述子0、1、2)を除く、開いているすべてのファイル記述子を閉じます。これにより、誤って渡されたファイル記述子がデーモンプロセス内に留まることがなくなります。Linuxでは、これはを介して反復することで最適に実装され/proc/self/fd、ファイル記述子3からgetrlimit()for によって返された値に反復するフォールバックがありますRLIMIT_NOFILE
  2. すべてのシグナルハンドラをデフォルトにリセットします。これは、利用可能な信号を制限するまで繰り返し、_NSIGそれらをSIG_DFLです。
  3. を使用して信号マスクをリセットしsigprocmask()ます。
  4. デーモンのランタイムに悪影響を及ぼす可能性のある環境変数を削除またはリセットして、環境ブロックをサニタイズします。
  5. コール fork()、バックグラウンドプロセスを作成します。
  6. 子供の中で、 setsid()任意の端末からデタッチして独立したセッションを作成するための
  7. 子供の中で、 fork()もう一度て、デーモンが再び端末を再取得できないようにします。
  8. コール exit()最初の子をて、2番目の子(実際のデーモンプロセス)だけが残るようにします。これにより、すべてのデーモンがそうであるように、デーモンプロセスがinit / PID 1に再親化されます。
  9. デーモンプロセスで、/dev/null標準入力出力、およびエラーます
  10. デーモンプロセスで、umaskを0 にリセットし、ファイルモードがに渡されるようにしますopen()mkdir()直接作成されたファイルやディレクトリのアクセスモードを制御するそのようなもの。
  11. デーモンプロセスで、変更します(ルートディレクトリに現在のディレクトリを/デーモン思わずブロックがアンマウントされてからマウントポイントことを回避するために、)。
  12. デーモンプロセスで、デーモンPIDgetpid())をPIDファイルにます。次に例を示します。/run/foobar.pid(、架空のデーモン「foobar」の場合)にで、デーモンが2回以上起動できないようにします。これは競合のない方法で実装する必要があります。これにより、PIDファイルに以前に保存されたPIDが存在しないか、外部プロセスに属していることが確認されたときにのみPIDファイルが更新されます。
  13. デーモンプロセスで、可能で該当する場合は、特権を削除します。
  14. デーモンプロセスから、初期化が完了したことを開始した元のプロセスに通知します。これは、名前のないパイプまたは最初の前に作成された同様の通信チャネルを介して実装できますfork()、元のプロセスとデーモンプロセスの両方で使用できるます。
  15. exit()元のプロセスで呼び出します。デーモンを呼び出したプロセスは、初期化が完了し、すべての外部通信チャネルが確立されてアクセス可能になった後にこれexit()が発生することを信頼できる必要があります。

この警告に注意してください:

BSD daemon()関数、これらのステップのサブセットのみを実装するため、使用しないでください。

SysVシステムとの互換性を提供する必要があるデーモンは、上記で指摘したスキームを実装する必要があります。ただし、デバッグを容易にし、systemdを使用したシステムへの統合を簡素化するために、この動作をコマンドライン引数を介してオプションにして構成可能にすることをお勧めします。

daemon()POSIXに準拠していないことに注意してください。


新しいスタイルのデーモン

新しいスタイルのデーモンの場合、次の手順が推奨されます。

  1. SIGTERM受信した場合は、デーモンをシャットダウンして正常に終了します。
  2. SIGHUPを受け取った場合は、構成ファイルを再ロードします(該当する場合)。
  3. メインデーモンプロセスから正しい終了コードを提供します。これは、サービスエラーおよび問題を検出するためにinitシステムによって使用されるためです。SysV initスクリプトのLSB推奨事項で定義されている終了コードスキームに従うことをお勧めします。
  4. 可能で適切な場合は、D-Bus IPCシステムを介してデーモンの制御インターフェースを公開し、初期化の最後のステップとしてバス名を取得します。
  5. systemdに統合するには、デーモンの起動、停止、その他のメンテナンスに関する情報を含む.service ユニットファイルを提供します。詳細systemd.service(5)については、を参照してください。
  6. 可能な限り、initシステムの機能に依存して、ファイル、サービス、その他のリソースへのデーモンのアクセスを制限します。つまり、systemdの場合、独自の実装ではなくsystemdのリソース制限制御に依存し、systemdの特権の削除に依存します。デーモンに実装する代わりに、コードなどを使用します。systemd.exec(5)使用可能なコントロールについては、を参照してください。
  7. D-Busを使用する場合は、D-Busサービスアクティベーション構成ファイルを指定して、デーモンをバスでアクティブ化できるようにします。これには複数の利点があります。デーモンはオンデマンドで遅延して起動される可能性があります。それは、それを必要とする他のデーモンと並行して開始することができます—並列化と起動速度を最大化します。バスはアクティブ化可能なサービスの要求をキューに入れるので、バス要求を失うことなく、障害時にデーモンを再起動できます。詳細については、以下を参照してください。
  8. デーモンがソケットを介して他のローカルプロセスまたはリモートクライアントにサービスを提供する場合は、以下に示すスキームに従って、ソケットをアクティブ化できるようにする必要があります。D-Busのアクティブ化と同様に、これによりサービスのオンデマンドの起動が可能になるだけでなく、サービスの起動の並列化が向上します。また、ステートレスプロトコル(syslog、DNSなど)の場合、ソケットベースのアクティブ化を実装するデーモンは、1つの要求を失うことなく再起動できます。詳細については、以下を参照してください。
  9. 該当する場合、デーモンは、sd_notify(3)インターフェイスを介して起動の完了またはステータスの更新についてinitシステムに通知する必要があります。
  10. syslog()呼び出しを使用してシステムのsyslogサービスに直接ログを記録する代わりに、新しいスタイルのデーモンはfprintf()、を介して標準エラーにログを記録することを選択できます。これは、initシステムによってsyslogに転送されます。ログレベルが必要な場合、Linuxカーネルのprintk()レベルシステムと同様のスタイルに従って、個々のログ行の先頭に "<4>"(syslog優先順位スキームのログレベル4 "WARNING"のような)の文字列を付けることで、これらをエンコードできます。詳細については、sd-daemon(3)およびを参照してくださいsystemd.exec(5)

詳細については、全体をお読みくださいman 7 daemon


11

Linuxで強制終了できないプロセスを作成することはできません。rootユーザー(uid = 0)はプロセスにシグナルを送信でき、キャッチできないシグナルが2つあります(SIGKILL = 9、SIGSTOP = 19)。また、他の信号(キャッチされなかった場合)もプロセスの終了につながる可能性があります。

より一般的なデーモン化機能が必要な場合があります。ここでは、プログラム/デーモンの名前と、プログラムを実行するためのパス(おそらく "/"または "/ tmp")を指定できます。stderrとstdoutのファイルを提供することもできます(場合によってはstdinを使用した制御パス)。

ここに必要なものが含まれます:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

そして、これはより一般的な関数です、

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
    if(!path) { path="/"; }
    if(!name) { name="medaemon"; }
    if(!infile) { infile="/dev/null"; }
    if(!outfile) { outfile="/dev/null"; }
    if(!errfile) { errfile="/dev/null"; }
    //printf("%s %s %s %s\n",name,path,outfile,infile);
    pid_t child;
    //fork, detach from process group leader
    if( (child=fork())<0 ) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if (child>0) { //parent
        exit(EXIT_SUCCESS);
    }
    if( setsid()<0 ) { //failed to become session leader
        fprintf(stderr,"error: failed setsid\n");
        exit(EXIT_FAILURE);
    }

    //catch/ignore signals
    signal(SIGCHLD,SIG_IGN);
    signal(SIGHUP,SIG_IGN);

    //fork second time
    if ( (child=fork())<0) { //failed fork
        fprintf(stderr,"error: failed fork\n");
        exit(EXIT_FAILURE);
    }
    if( child>0 ) { //parent
        exit(EXIT_SUCCESS);
    }

    //new file permissions
    umask(0);
    //change to path directory
    chdir(path);

    //Close all open file descriptors
    int fd;
    for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
    {
        close(fd);
    }

    //reopen stdin, stdout, stderr
    stdin=fopen(infile,"r");   //fd=0
    stdout=fopen(outfile,"w+");  //fd=1
    stderr=fopen(errfile,"w+");  //fd=2

    //open syslog
    openlog(name,LOG_PID,LOG_DAEMON);
    return(0);
}

以下は、デーモンになり、ぶらぶらしてから離れるサンプルプログラムです。

int
main()
{
    int res;
    int ttl=120;
    int delay=5;
    if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
        fprintf(stderr,"error: daemonize failed\n");
        exit(EXIT_FAILURE);
    }
    while( ttl>0 ) {
        //daemon code here
        syslog(LOG_NOTICE,"daemon ttl %d",ttl);
        sleep(delay);
        ttl-=delay;
    }
    syslog(LOG_NOTICE,"daemon ttl expired");
    closelog();
    return(EXIT_SUCCESS);
}

SIG_IGNはシグナルをキャッチして無視することを示していることに注意してください。シグナルの受信をログに記録し、フラグ(正常なシャットダウンを示すフラグなど)を設定できるシグナルハンドラーを構築できます。


8

daemon関数を使用してみてください:

#include <unistd.h>

int daemon(int nochdir, int noclose);

manページから:

daemon()関数は、制御端末から自分自身を切り離して、システムデーモンとしてバックグラウンドで実行するプログラム用です。

nochdirがゼロの場合、daemon()は呼び出しプロセスの現在の作業ディレクトリをルートディレクトリ( "/")に変更します。それ以外の場合、現在の作業ディレクトリは変更されません。

nocloseがゼロの場合、daemon()は標準入力、標準出力、および標準エラーを/ dev / nullにリダイレクトします。それ以外の場合、これらのファイル記述子は変更されません。


2
daemon(7)マニュアルはデーモンを作成するための手順を述べており、次のように警告していることに注意してください:BSD daemon()関数はこれらの手順のサブセットのみを実装するため、使用しないでください。 daemon関数は、最初に登場し4.4BSDとされていない POSIX準拠
patryk.beza 2016

2
また、daemon()の使用に関する警告は、daemon(7)のmanページの古いスタイルのSysVセクションにあります。systemdでは、daemon()の使用は推奨されません。
グレッグマクペラン

7

最初の要件「停止できないデーモン...」で停止できます

私の友人は不可能です。ただし、はるかに優れたツールであるカーネルモジュールを使用して同じことを実現できます。

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

すべてのデーモンを停止できます。他のものより簡単に停止できるものもあります。パートナーがホールドダウンしているデーモンペアでさえ、パートナーが失われた場合に再起動することで停止できます。もう少し一生懸命働かなければならないだけです。


7
「止められないデーモン」と言って、作者はセッション終了時にデーモンが常にバックグラウンドで動いていることを実感しています。
FaceBro 2014年

6

アプリが次のいずれかである場合:

{
  ".sh": "bash",
  ".py": "python",
  ".rb": "ruby",
  ".coffee" : "coffee",
  ".php": "php",
  ".pl" : "perl",
  ".js" : "node"
}

NodeJSの依存関係を気にせず、NodeJSをインストールしてから:

npm install -g pm2

pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above

pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores

pm2 list

すべてのアプリを再起動時に実行し続けるには(そしてpm2をデーモン化するには):

pm2 startup

pm2 save

今することができます:

service pm2 stop|restart|start|status

(アプリのディレクトリでコードの変更を監視し、コードが変更されたときにアプリのプロセスを自動的に再起動することも簡単にできます)


2
これはC.とは何の関係もありません
メルポメネ

4
Cタグがあるとありがたいです。ただし、OPは質問の中でCに関する要件について言及していません。タイトルはLinuxで悪魔を作成しています。この答えはそれを満たしています。
danday74、2017年

1
ああ、あなたは正しい。これにはCのタグが付いていますが、実際の要件はC ++です(OPのコードとリンクされた記事で証明されています)。
メルポメネ2017年

3

fork()を呼び出すことで、子プロセスを作成しました。フォークが成功した場合(フォークがゼロ以外のPIDを返した場合)、この時点から子プロセス内から実行が続行されます。この場合、親プロセスを適切に終了し、子プロセスで作業を続行します。

多分これは役立つでしょう:http : //www.netzmafia.de/skripten/unix/linux-daemon-howto.html


2

デーモンはバックグラウンドでの単なるプロセスです。OSの起動時にプログラムを起動する場合は、Linuxで、起動コマンドを/etc/rc.d/rc.local(他のすべてのスクリプトの後に実行)または/etc/startup.shに追加します。

Windowsでは、サービスを作成し、サービスを登録して、管理->サービスパネルで起動時に自動的に開始するように設定します。


1
ありがとう。それでは、「デーモン」と通常のプログラムの間に違いはありませんか?簡単に閉じてほしくない。
chrisMe 2013

1
いいえ、デーモンは単なるバックグラウンドプロセスです。より具体的には、親からforkし、子プロセスを実行して、親を終了します(これにより、プログラムへの端末アクセスがなくなります)。それは「デーモン」であるために必要ではありません:en.wikipedia.org/wiki/Daemon_
computing

1

デーモンテンプレート

新しいスタイルのデーモンに続いて、デーモンテンプレートを作成しました:link

テンプレートコード全体は、GitHubで見つけることができます:こちら

Main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
    LOG_INFO("Reload function called.");
}

int main(int argc, char **argv) {
    // The Daemon class is a singleton to avoid be instantiate more than once
    Daemon& daemon = Daemon::instance();
    // Set the reload function to be called in case of receiving a SIGHUP signal
    daemon.setReloadFunction(reload);
    // Daemon main loop
    int count = 0;
    while(daemon.IsRunning()) {
        LOG_DEBUG("Count: ", count++);
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
    public:

    static Daemon& instance() {
        static Daemon instance;
        return instance;
    }

    void setReloadFunction(std::function<void()> func);

    bool IsRunning();

    private:

    std::function<void()> m_reloadFunc;
    bool m_isRunning;
    bool m_reload;

    Daemon();
    Daemon(Daemon const&) = delete;
    void operator=(Daemon const&) = delete;

    void Reload();

    static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
    m_isRunning = true;
    m_reload = false;
    signal(SIGINT, Daemon::signalHandler);
    signal(SIGTERM, Daemon::signalHandler);
    signal(SIGHUP, Daemon::signalHandler);
}

void Daemon::setReloadFunction(std::function<void()> func) {
    m_reloadFunc = func;
}

bool Daemon::IsRunning() {
    if (m_reload) {
        m_reload = false;
        m_reloadFunc();
    }
    return m_isRunning;
}

void Daemon::signalHandler(int signal) {
    LOG_INFO("Interrup signal number [", signal,"] recived.");
    switch(signal) {
        case SIGINT:
        case SIGTERM: {
            Daemon::instance().m_isRunning = false;
            break;
        }
        case SIGHUP: {
            Daemon::instance().m_reload = true;
            break;
        }
    }
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget

[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template

[Install]
WantedBy=multi-user.target
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.