技術情報

デーモンプロセスの実像にせまる!
デーモンプロセスのヒ・ミ・ツ


一般にデーモンプロセスとは,一般的にはインタラクティブに処理を行わず, 時間やある一定のトリガーによって動作する常駐プロセスのことを指す.
そもそもデーモン(daemon)注1とは,ギリシャ神話に登場する「神々と人間の間に介在する 二次的な神」や「守護神」のことで,コンピューターの中で,人知れず動作する 「縁の下の力持ち」の常駐プロセスには,ふさわしい愛称と言えるだろう.
さて,デーモンプロセスは,一般的なコマンドとして動作するアプリケーションと比較 してもそれほど複雑ではない.しかし,作り方によっては守護神にも悪魔に もなってしまう可能性を秘めている. デーモンプロセスを「守護神」にするか「悪魔」にしてしまうかは, プログラムの作者が正しい知識を持っているかどうかにかかっているといっても 過言ではない.

注1 デーモンといえば demon も日本語読みでは同じ発音になるが, demonのほうは,神々と人間の中間にある考えられる悪魔で,キリスト教ではdevilを 意味する.

戻る


デーモンプロセスとその仲間たち

デーモンプロセスは,一般的に次のような特徴を持ちます.
  • システムの起動時に起動される
  • システムが終了するときに終了する
  • 実行中はほとんどの時間をイベント待ちのままで過ごす
  • 実際の処理は,子プロセスを起動しそこで処理を行う

    「デーモンプロセス」は,UNIXの用語ですが,同じような動作を行う処理は 他のOSにも存在します.
    たとえば,Windows9xでは,タスクトレイにアイコンを表示し常駐しているアプリケーションがありますし,WindowsNT/2000は,サービスと呼ばれる特別な機能をサポートしたアプリケーションがあります.
    これらのアプリケーションは,常駐処理を実現するために特別なAPIを利用していますが,UNIXの場合,デーモンプロセスを作成するための特別なシステムコールが用意されている訳ではありません.すこし乱暴な言い方かもしれませんが,telnetのターミナルから起動されているシェルから親子関係を切り離されたプロセスは,すべてデーモンプロセス化してしまうといってもよいでしょう.
    つまり,ターミナルから
    % command &
    のように,"&"をつけてコマンドを実行すると,コマンドが自発的に終了するかkillコマンドで強制終了させない限り,システムが終了するまで動作し続けます.
    このコマンドが,システムを停止するまで処理を続けるのであれば,デーモンプロセス として作成されたコマンドとの区別を表面的に行うのは難しいと言えます.

    コマンドをデーモンプロセスのように動作させる

    UNIXシステムは,小さなコマンドを組み合わせて大きな処理を行うことが基本になっています.これはデーモンプロセスのような常駐処理に関しても当てはまります.
    汎用化されたデーモン処理を活用することで,不必要にデーモンプロセスを増やして, システムを圧迫させないためにも活用できるものは最大限活用すべきでしょう.
    所望の処理がそれでも実現できないときに,はじめてデーモン化することを検討すべきです.

    ●定期的に処理を行う crond
    定期的に処理を実行したいといったことはよくあります. 日常でも,「作業ファイルのバックアップを定期的に行う」,「Webでの検索システムで 利用するインデックスを定期的に最新の状態にする」といった作業を自動化することが 考えられます.
    このような処理は,perlやシェルスクリプトで記述することがほとんど でしょう.crond はそのような処理を簡単に定期実行してくれます.
    crond に処理をスケジュールするには,通常crontabコマンドを利用します. このコマンドを実行するには,まず以下の設定が行われていることを確認します.
  • ・環境変数 EDITOR に viなどのエディタを設定
     (設定されていない場合は,edコマンドが起動されます)
  • 処理を実行したいユーザアカウントでログイン
    crontab ファイルはそれぞれ6つのフィールドを持つ行で構成されます. フィールドは空白またはタブで区切られており,初めの5つは表の値を表す整数です. 各パターンは1つのアスタリスク(すべての有効な値を示す) か,コンマで区切られた 要素のリストです.各要素は1つの数値か,マイナス符号で区切られた2つの数値 (両端を含んだ範囲)で,日付の指定は 2つのフィールド(日と曜日)になります. 両方とも整数列で指定された場合は,両方の日付が指示されます(表1).

    0 0 1,15 * 1
    各月の1日と15日,毎月曜日にコマンドを実行します.
    日を1つのフィールドだけで指定するには,他のフィールドには * を設定します. 以下は,は毎月曜日のみにコマンドを実行します.
    0 0 * * 1
    crontab ファイルの各行の6番目のフィールドは指定した時間にシェルが実行する スクリプトです.このフィールドではパーセント記号は(\でエスケープしていない 限り)復帰改行文字と解釈されます.コマンドフィールドの最初の行(% まで, または行の終わりまで)のみがシェルによって実行されます(リスト1).
    その他の行は標準入力としてコマンドによって使用されます.なお,"#"で始まる任意の行は注釈を示し,その行の設定や記述は無視されます.

    表1 日付の表記
  • 0〜59
    0〜23
    1〜31
    1〜12
    曜日0〜6 (0 が日曜日)

    リスト1 毎日00:00に,/home/user/my_cmd を実行
    
    0 0 * * * /home/user/my_cmd
    


    ●あるポートに対してアクセスが行われたときに通信処理を開始するinetd

    一般的にデーモンプロセスが処理を行う時間は,デーモンプロセスが起動され, メモリ中に常駐している時間と比較すると,わずかな時間であるため必要なときに 起動させる場合がよいこともあります.
    なんらかの通信を行うサーバーー処理は,かならず自分が待っているポートを監視し, そのポートへのアクセスがあったときに処理を開始するようになっています. その処理を汎用化したものが,inetd です.
    inetdは,/etc/services(リスト3) で定義されたポート名のうち,/etc/inetd.conf(リスト4) で指定されたポートに対してアクセスがあったときに,指定されたコマンドを起動します.
    例えばLinuxの場合,
    inetd → tcpd → in.telnetd, in.ftpd ...
    の順でモジュールが呼び出されます.inetd を利用すれば,個別に常駐処理を作成することなく必要な処理を実現することが可能になります.
    なお,tcpd は,サービスに対するリクエストを監視し,syslog へのログ出力などを 行います.Linux以外のシステムでは,inetd がこの機能を持っている場合もあります.

    リスト2 RedhatLinux6.2J の /etc/services (抜粋)
    services の書式
    
      サービス名   ポート番号/プロトコル
    
    ---------
                             :
                             :
                             :
    ftp-data 20/tcp
    ftp      21/tcp
    fsp      21/udp  fspd
    ssh      22/tcp             # SSH Remote Login Protocol
    ssh      22/udp             # SSH Remote Login Protocol
    telnet   23/tcp
    # 24 - private
    smtp     25/tcp mail
    # 26 - unassigned
                             :
                             :
                             :
    #
    webster  765/tcp            # Network dictionary
    webster  765/udp
    swat     901/tcp            # Samba Web Administration Tool
    #
                             :
                             :
                             :
    

    リスト3 RedhatLinux6.2J の /etc/inetd.conf (抜粋)
    inet.conf の書式
    
    サービス名  ソケットタイプ  プロトコル  タイムアウト待ち時間  実行ユーザID  コマンド  コマンドの引数
    
    
    ---------
                             :
                             :
                             :
    #
    # These are standard services.
    #
    ftp       stream  tcp nowait     root        /usr/sbin/tcpd in.ftpd -l -a
    telnet    stream  tcp nowait     root        /usr/sbin/tcpd in.telnetd
    #
    # Shell, login, exec, comsat and talk are BSD protocols.
    #
    shell     stream  tcp nowait     root        /usr/sbin/tcpd in.rshd
    login     stream  tcp nowait     root        /usr/sbin/tcpd in.rlogind
    #exec     stream  tcp nowait     root        /usr/sbin/tcpd in.rexecd
    #comsat   dgram   udp wait       root        /usr/sbin/tcpd in.comsat
    talk      dgram   udp wait       nobody.tty  /usr/sbin/tcpd in.talkd
    ntalk     dgram   udp wait       nobody.tty  /usr/sbin/tcpd in.ntalkd
    #dtalk    stream  tcp wait       nobody.tty  /usr/sbin/tcpd in.dtalkd
                             :
                             :
                             :
    #
    # End of inetd.conf
    
    linuxconf stream  tcp wait       root       /bin/linuxconf  linuxconf --http
    swat      stream  tcp nowait.400 root       /usr/sbin/swat  swat
    


    なぜ inetd が必要なのか?

    inetd は crond と同様に,汎用的なデーモンプロセスとして設計されています. このコマンドを利用することで,特定のポートを待つ処理を作成することなく, 実際に行うべき処理に集中することができます.このような汎用デーモンを利用する ことによって,多くの恩恵を受けることができますが,問題点がまったくない訳では ありません.
    例として,httpd を見てみることにしましょう. httpd も inetd を利用して起動することができます.しかし,一般的にはそのような 起動方法を採らず,単体のデーモンプロセスとして起動させます. telnet や ftp(anonymous ftpを除く) の,利用者は基本的に,接続するサーバーに アカウントがある利用者に限られます.しかし,httpd は,認証を設定せずにサービス を行う場合,サーバーにHTTPでアクセスすることができる,不特定多数の利用者を 対象にしています.このため,telnet や ftp などとは比較にならない量の要求を 処理する必要があるのです.
    一般的な httpd は,これらの要求を処理するために複数のhttpd を起動しておき, 負荷が軽いプロセスに処理を振り分け,各プロセスはマルチスレッドで同時に複数の 接続要求を処理できるようになっています.
    もし,httpd が inted から起動する仕組みになっていたら,サーバーの利用者から 毎日のように苦情や問い合わせが殺到する事態になってしまうでしょう.

    ●samba の swat はinetd から起動
    逆に,inetd を活用しているコマンドとしては,smbd (samba:UNIX/LinuxでWindowsの 共有ディレクトリ・プリンタを実現するサーバー)の管理ツールである swat が あります.
    swat は,samba の設定をWebブラウザで行うための機能です.実際は,httpd のような ものですが,限られた利用者しかアクセスできないことが前提になっています. このため,inetd から起動することが前提になっています.
    crondやinetdを利用するときの利点と欠点を表2に示します。

    表2 crondやinetdを利用するときの利点と欠点
    利点
    ・プログラマーは最小の機能を作成することで,必要な機能を実現できる
    ・機能が細分化されているため,コマンドの一部を差し替えることにより他の機能を実現することができる
    ・処理を行うときに必要なコマンドを起動し,処理が終わると終了するため, 不必要なプロセスがシステム内に常駐しない
    ・コマンドの処理にメモリリークなどがあっても,処理が終了するとプロセスが管理するリソースはすべて解放されるため,システムに影響を与えない.(ただし,デーモンプロセスにメモリリークがある場合を除く)
    欠点
    ・中核になるコマンド(ここではcrond, inetd)にトラブルが発生すると,それに依存するすべての処理が停止してしまう可能性がある.
    ・コマンドが細分化しているため同じ処理を大量に実行すると,処理の実行個数分のプロセスを起動してシステムを圧迫,最悪の場合システムクラッシュの原因になる.
    ・利用しているコマンドが,なんらかの理由で差し替えられると正しく動作しなくなる可能性がある
    ・プロセスの起動に費やされるCPUは,スレッドよりはるかに大きいため処理速度が要求される処理には向かない


    デーモンプロセスプログラミング

    それでは,実際にデーモンプロセスを作成する方法について説明していきましょう.

    ●そのデーモンプロセスは「守護神」か「悪魔」か?
    デーモンプロセスが,「守護神」になったり「悪魔」になるのはなぜでしょう.
    デーモンプロセスとして動作することを前提としたアプリケーションは,一般的に 図1のような流れで処理を行います.

    図1 デーモンプロセスの処理の流れ
      ○初期化
      | ・不必要なシステムリソースを解放する
      | ・不要なシグナルの発生を無視する設定を行う
      ↓ ・起動した親プロセスから切り離す
    +→○待ち
    | ↓ ・処理を実行するためのトリガーが発生するまで待つ
    | ○処理実行
    | | ・トリガーが発生すると処理を行う
    | | ・処理を終了すると再びトリガーが発生するまで待つ
    +−+ ・システムが終了するまで,待ちと処理の実行を繰り返す
    

    この中で,デーモンプロセスが「悪魔」になる可能性をはらんでいるのは,初期化時と 処理実行時です.その様子を観察してみることにしてみましょう.

    ●初期化
    デーモンプロセスは,システムが起動して終了するまで動作し続けます. コンソールにメッセージを出力することができるのは通常,初期化途中でエラーが 発生した場合など,デーモンとして動作する以前の処理に限られます.
    ◆ファイルハンドル
    デーモンの動作を行うと,少なくとも stdin, stdout, stderr は必要なくなります. これらのハンドル以外に,親プロセスからファイルを引き継いだとしても利用すること はありません.それどころか,そのまま放置しておくとデーモンプロセスを起動した 親プロセスの動作に悪影響を及ぼす場合があります.
    子プロセスが,ファイルハンドルをclose しないことで親プロセスの動作に,悪影響が 及ぶ例として,CGIから他のプロセスを起動した場合にみることができます. CGIをトリガーにしてHTTPリクエストのタイムアウト時間を越える可能性がある処理を 実行したい場合,CGIから子プロセスを起動して処理を行うことを考えます. このとき,子プロセスがstdin, stdout, stderr を close せずに処理をおこなうと, 当然のことながら,CGIが起動した子プロセスが終了されるまでそれらのファイル ハンドルは閉じられません.このため,Webサーバーは,CGIがまだ実行中であると判断 し,クライアントとのセッションを切断せずに待ち続けます.
    このため,Webクライアントからは,いつまでたっても処理が終了しないように 見えてしまいます.
    ◆シグナル
    シグナルは,システムからプロセスに対して割り込みで通知を行うための仕組みです. たとえば, % kill -9 xxxx (xxxxはプロセスID)を実行すると,指定されたプロセスID を持つプロセスは,強制終了します.
    処理中に,意図しないシグナルを受信すると正しく処理を行うことができなくなる場合 があります.特に,socketで通信を行う処理を持つ場合,socketのシステムコール内で 処理中にシグナルを受信すると,プロセスがハングアップを起こす場合があります. また,ターミナル制御用のシグナルは,デーモンプロセスには必要ありませんので, 無視させるようにしておいたほうがよいでしょう.
    シグナルは,OSによってサポートされるものが異なりますので,作成するデーモン プロセスがサポートするOSの対応状態を調べておく必要があります.
    ◆システム内に「ゾンビ」が現れる
    プロセスが,子プロセスの終了状態待ちのままシステムリソースを解放され, タスクリストに残った状態を「ゾンビプロセス」と呼びます.
    ゾンビプロセスをkillコマンドで強制終了させると,依存する子プロセスも終了して しまいます.ゾンビプロセスは,システムに残しておいても何の役にも立ちませんが, システムはプロセスとして管理するため,システムリソースを無意味に浪費します.
    したがって,プロセスをゾンビ化させないように注意を払う必要があります.
    ◆ゾンビプロセスを作らないために
    デーモンプロセスは,自分が起動されたシェルなど,外界から切り離すために,常駐 処理をforkして親プロセスを終了させます.
    通常の処理では,親プロセスは子プロセスの終了状態(終了ステータス)を監視しますが,デーモンプロセスは,常駐処理が動き始めると不要になります.このため,子プロセスの終了状態が必要ないことをシステムに宣言しなければなりません.そうすれば,常駐処理が終了しなくても親プロセスは終了してしまい,ゾンビプロセスとしてシステムに残ることはなくなります.
    実際には,SIGCHLDが必要ないことをシステムに宣言する処理が必要に なります.BSD系UNIXでは,SIGCHLDに「ゾンビ止め」処理を必要としますが,Linuxや SystemV系UNIXでは,SIGCHLDをSIG_IGNとするだけで対処できます.


    表3 初期化に利用する主なシステムコール
    void signal(int sig, void (*func)(int))
    プロセスが受信するシグナルの設定を行う

    #include <signal.h>

    引数
    int sig ... 設定するシグナル void (*func)(int) ... 指定したシグナルが発生した場合の
     コールバック関数へのポインタシグナルを無視する場合は SIG_IGNを指定
    戻り値
    なし

    POSIX.1 に定義されているシグナル
    
    シグナル 値       内容
    --------------------------------------------
    SIGINT   2        キーボード割り込み
    SIGQUIT  3        キーボードによる中止
    SIGILL   4        不正な命令
    SIGABRT  6        abort からの中断シグナル
    SIGFPE   8        浮動小数点例外
    SIGKILL  9        Kill シグナル
    SIGSEGV 11        不正なメモリ参照
    SIGPIPE 13        パイプ破壊: 読み手の無いパイプへの書き出し
    SIGALRM 14        alarm からのタイマーシグナル
    SIGTERM 15        終了シグナル
    SIGUSR1 30,10,16  ユーザ定義シグナル1
    SIGUSR2 31,12,17  ユーザ定義シグナル2
    SIGCHLD 20,17,18  子プロセスの一旦停止または終了
    SIGCONT 19,18,25  一旦停止からの再開
    SIGSTOP 17,19,23  プロセスの一旦停止
    SIGTSTP 18,20,24  端末より入力された一旦停止
    SIGTTIN 21,21,26  バックグランドプロセスのtty入力
    SIGTTOU 22,22,27  バックグランドプロセスのtty出力
    
    pid_t setsid(void)
    セッションを作成し,プロセス・グループIDを設定します
    呼び出したプロセスは新しいセッションのリーダー,
    新しいプロセスグループのプロセスグループリーダーとなり,
    tty の制御を持ちません.

    #include <unistd.h>

    引数
    なし

    戻り値
    正常終了 呼び出したプロセスのセッションID
    異常終了 -1
    pid_t fork(void)
    子プロセスを生成します

    #include <unistd.h>

    引数
    なし

    戻り値
    正常終了 親の実行スレッドには子プロセスのPIDを返却する
     子の実行スレッドには0を返却する
    異常終了 -1
    mode_t umask(mode_t mask)
    ファイル作成マスクを設定します
    umask 値はopenで新しくファイルを作成する時に,
    アクセス件を設定するために使用されます.
    具体的には umask 値に設定されている許可がopenのmode引き数から
    取り消されます.(例えば,umask値=022とした場合modeに0666
    が与えられると,新たに作成されたファイルは 0666~022 = 0644
    が設定されます.

    #include <sys/types.h>

    引数
    mode_t mask

    戻り値
    以前のumask値


    リスト4 初期化処理の例
    /*------------------------------------------------------
        "testd.c"
            Daemon Sample
                 24 Jul. 2000
         Redhat-Linux 6.2J/GNU-C  Programmed by S.Tsuneoka
    ------------------------------------------------------*/
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <errno.h>
    
    #define WAIT_TIME   10   /* [sec] */
    #define LOGFILENAME "testd.log"
    
    
    /*------------------------------------------------------
    	初期化
    ------------------------------------------------------*/
    int init_daemon(int debug_mode)
    {
        if(debug_mode){
            fprintf(stdout, "-- Debug mode --\n");
            return 0;
        }
    
        switch(fork()){
            case -1:
                perror("fork");
                return errno;
            case 0:
                setsid();   /* 子プロセスをターミナルから切り離す */
                break;
            default:
                exit(0);    /* 親プロセスを終了させる */
        }
    
        /* 標準(エラー)入出力を閉じて,/dev/nullで埋めておく */
        close(0);
        close(1);
        close(2);
        open("/dev/null", O_RDWR);
        dup2(0, 1);
        dup2(0, 2);
    
        /* 不要なシグナルを禁止する */
        signal(SIGALRM, SIG_IGN);
        signal(SIGCHLD, SIG_IGN);    /* ゾンビ止め */
        signal(SIGHUP , SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
    
        /* 親のMASKを引き継がない */
        umask(0);
    
        return 0;
    }
    


    ●処理実行

    ◆問題なさそうな処理でも...
    デーモンプロセスの処理は,デーモンプロセス自身が内部で行う場合と,forkして 子プロセスを作成し,処理を行うモジュールをexecで起動する場合があります.
    自分自身で処理を行う場合,処理中に巨大な automatic変数を宣言したり, 深い再帰呼び出しを行うなど,無計画にスタックエリアを利用すると,プロセスの ワーキングセット(プロセスのメモリ常駐サイズ)が広がってしまいます.
    ワーキングセットがいったん拡大してしまうと元に戻らないため,そのような処理を 常駐処理で繰り返すと,スワップファイルを食いつぶしてシステム全体に悪影響を 及ぼしてしまう可能性があります.
    ◆システムリソースを確保したら...
    mallocでメモリを確保を行う必要がある場合は,できる限り同じサイズのメモリを 確保するように処理を行うようにしておいたほうがよいでしょう.サイズの異なる領域 の取得・解放を繰り返すと,ヒープ領域が拡大してしまい,これもプロセスの ワーキングセットを拡大させる要因になります.
     ファイルアクセスなど,システムリソースを利用するときは必ず解放しなければ なりません.これはどのようなプロセスでも常識ですが,通常のプロセスは終了 するとシステムリソースは自動的に解放されます.しかし,デーモンプロセスは システムが終了するまで動作を続けます.LinuxやUNIXはサーバーOSとして長い時間を 無停止で動作させることが多いため,1バイトのメモリリークでさえシステム全体に 影響を及ぼす可能性があることを認識しなければなりません.



    表4 モジュールを実行するシステムコール
    extern char **environ;
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg , ..., char * const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    現在のプロセスイメージを新しいプロセスイメージで置き換えます.
    ※Win32APICreateProcessのように新しいプロセスを生成しませんので,
    あらかじめforkで子プロセスを生成し,子プロセスで本関数を実行
    する必要があります.
    なお,system関数は内部でこれらの処理を行って,シェルを起動して
    います.

    #include <unistd.h>

    引数
    各関数で異なる

    戻り値
    正常終了 -1以外
    異常終了 -1 (エラーコードはerrnoに設定される)


    リスト5 処理実行プログラムの例
    
    /*------------------------------------------------------
    	イベント待ち
    ------------------------------------------------------*/
    int wait_event(void)
    {
        FILE    *fp = NULL;
        char    buf[256];
        int     ret_code = 0;
    
        while(1){
            /* 待ち */
            sleep(WAIT_TIME);
    
            /* 処理:ファイルに1行ログを出力する */
            if((fp = fopen(LOGFILENAME, "a")) == NULL){
                ret_code = errno;
                perror("fopen");
                break;
            }
            sprintf(buf, "%d - processed\n", time(NULL));
            fputs(buf, fp);
            if(fclose(fp) == EOF){
                ret_code = errno;
                perror("fclose");
                break;
            }
            printf(buf);    /* デバッグ用 */
        }
    
        return ret_code;
    }
    

    ◆安全に処理を行うには
    処理のバグなどによる問題でシステムに影響を与えなくする一番簡単な方法は,fork して子プロセスに処理を行わせることです.バグによりシステムリソースリークが あったとしても,子プロセスが終了すると利用していたリソースはすべて解放され, システム内に蓄積されることはありません.
    このとき注意しなければならないのは,プロセスを大量に起動させないようにすること です.一度に大量のプロセスを起動させると,最悪の場合システムの処理もリソース 不足で実行できなくなります.このような場合,自分自身の処理も正しく行えなく なりますし,shutdownコマンドも実行できずシャットダウンさえできなくなって しまいます.仕方がなく,リセットスイッチでシステムを停止ということになります が,問題なく起動できるかどうかは「神のみぞ知る」ということになるでしょう.
    このようなことはあまりないと思いますが,予期しない大量の処理要求が発生したり, プログラムのバグによってプロセス生成処理を無限ループさせてしまうと簡単に 起こってしまいます.
    子プロセスを生成して処理を行わせるときの,利点と注意点をまとめると以下のように なります.どのような形態にするかは,処理を行うためのトリガーや処理の内容を 考慮して,最適な方法を選択する必要があります.

    表5 子プロセスを生成して処理を行わせるときの利点と注意点
    利点
    ・デーモンプロセスの常駐量が最小になる
    ・処理のバグ(メモリリークなど)でシステムに影響を与えない
    ・デーモン部分と処理部分を別モジュールにすることで,個別にデバッグが行える
    注意点
    ・同時期に大量の処理を行うとシステムや他のプロセスに悪影響を与える恐れがある
    ・バグによってプロセス生成処理が無限ループすると,システムがデッドロック状態になる


    デーモンプロセスをテストするには

    まずは,デーモンプロセスでなく通常のプロセスとして動作させます. プログラムを作成するときに,「デバッグモード」として,プロセスをデーモン化 させずにデバッグログを標準出力や標準エラーに出力させるようにしておくと, いつでもトラブル解析ができるようになります.デーモンプロセスとして動作させる ときは,標準(エラー)入出力はクローズしますので,printfやputsで出力した メッセージはどこにも出力されません.
    開発環境では,gdbコマンドでデーモンプロセスにアタッチしてデバッグすることが 可能ですが,当然のことながら最適化オプション利用できませんし,モジュールに デバッグシンボルがリンクされている必要があります.客先にリリースするモジュール にデバッグシンボルをつけたまま出荷することも考えられますが,デーモンプロセス のように常駐するプロセスは,デバッグシンボルはストリップし,できる限り コンパクトにすることが望ましいでしょう.
    常駐部分の処理に,メモリリークや無限ループなどが発生しないことが確認できたら, デーモンプロセスとして動作させてみます.
    このとき,ゾンビプロセスができていないことをpsコマンドで確認します. さらに,数日間動作させ続けて,プロセスが利用するメモリ領域が増加していないかを 確認します.メモリリークがなくても,mallocで領域を確保する回数が多く,領域の サイズが不規則な場合,新たなヒープ領域が確保されプロセスのサイズが大きく なっていく場合がありますので,注意が必要です.
    メイン関数の例をリスト6に,プログラムの実行例をリスト7に示します.

    リスト6 メイン関数の例
    /*------------------------------------------------------
    	メイン
    ------------------------------------------------------*/
    int main(int argc, char *argv[])
    {
        int debug_mode = 0;
        int ret_code   = 0;
    
        if(argc >= 2){
            if(strcmp(argv[1], "-d") == 0){
                debug_mode = 1;
            }
            else{
                puts("usage: testd [-d]");
                puts("       -d ... debug mode");
                return 1;
            }
        }
    
        if((ret_code = init_daemon(debug_mode)) != 0){
            fprintf(stderr, "init daemon failed code = %d\n", ret_code);
            return 2;
        }
        if((ret_code = wait_event()) != 0){
            fprintf(stderr, "wait event failed code = %d\n", ret_code);
            return 3;
        }
    
        return 0;
    }
    


    リスト7 プログラムの実行例
    
    (a)コンパイル
    
    [shinji@mystiy daemon]$ gcc -o testd testd.c
    [shinji@mystiy daemon]$ 
    
    (b)デーモンとして実行
    
    [shinji@mystiy daemon]$ ./testd -debug
    -- Debug mode --
    962022442 - processed
    962022452 - processed
    962022462 - processed
    962022472 - processed
    962022564 - processed
    962022574 - processed        ← [ctrl]+[c] で停止
    [shinji@mystiy daemon]$ 
    
    (c)デーモンとして実行
    
    [shinji@mystiy daemon]$ ./testd      ←実行後,すぐにコマンドプロンプトに戻る
    [shinji@mystiy daemon]$ ps -eaf | grep testd
    shinji     963     1  0 21:29 ?        00:00:00 ./testd
    [shinji@mystiy daemon]$ kill -9 963  ←数十秒待ってから実行する
    [shinji@mystiy daemon]$ ps -eaf | grep testd
    shinji     972   894  0 21:30 pts/0    00:00:00 grep testd
    [shinji@mystiy daemon]$ cat testd.log
    962022584 - processed
    962022594 - processed
    962022604 - processed
    962022614 - processed
    962022624 - processed
    [shinji@mystiy daemon]$ 
    

    参考文献
    日本のLinux情報 - JM Project;http://www.linux.or.jp/JM/
       マニュアル検索
       ftpd, telnetd, inetd, tcpd, crond, signal, setsid, fork, exec, umask
    RedhatLinux 6.2J;/etc/services, /etc/inet.conf