この記事の内容は、PHP でのシステムプログラミングのためのデーモンの作成について詳しく説明したもので、必要な方は参考にしてください
(1)プロセスグループ、セッション、制御ターミナル、制御プロセスなど。概念
: 各プロセスには、プロセス グループ リーダーが存在します。したがって、プロセス グループ ID は、プロセス リーダーのプロセス番号です。がプロセス グループ リーダーである場合、プロセス名がそのプロセス グループ ID と等しいかどうかを比較するだけで済みます。PHP では、関数 posix_getpgrp() を使用して現在のプロセスのプロセス グループ ID を取得し、posix_getpid() を使用できます。現在のプロセス グループ ID を取得します。
<?php function isGroupLeader() { return posix_getpgrp() == posix_getpid(); } $pid = pcntl_fork(); if($pid == 0) { echo '子进程:' . PHP_EOL; } elseif($pid > 0) { sleep(2); echo '父进程:' . PHP_EOL; } echo "当前进程组gid:" . posix_getpgrp() . PHP_EOL; echo "当前进程号pid:" . posix_getpid() . PHP_EOL; if (isGroupLeader()) { echo 'Is a process group leader' . PHP_EOL; } else { echo 'Is not a process group leader' . PHP_EOL; }上記のルーチンは次の出力を行います:
子进程: 当前进程组gid:15827 当前进程号pid:15828 Is not a process group leader 父进程: 当前进程组gid:15827 当前进程号pid:15827 Is a process group leader
Session: セッションは複数のプロセス グループの集合であり、セッション内の 1 つのプロセス グループがセッション リーダー、つまりセッションです。 ID はセッション リーダーのプロセス グループ ID です。PHP では、関数 posix_getsid(int $pid) を使用して指定したプロセスのセッション ID を取得するか、関数 posix_setsid() を使用して新しいセッションを作成できます。この時点で、プロセスは新しいセッションのセッション リーダーになります。この関数呼び出しは、正常に作成されたセッション ID を返します。失敗した場合は -1 を返します。Linux の呼び出しに注意してください posix_setsid()。 関数のプロセスをプロセス グループ リーダーにすることはできません。そうでない場合、プロセス グループ内のプロセスは同時に複数のセッションにまたがることができないため、呼び出しは失敗します。
setsid に関する Linux ドキュメント:
setsid() creates a new session if the calling process is not a process group leader. The calling process is the leader of the new session, the process group leader of the new process group, and has no controlling tty. The process group ID and session ID of the calling process are set to the PID of the calling process. The calling process will be the only process in this new process group and in this new session.
<?php function isGroupLeader() { return posix_getpgrp() == posix_getpid(); } echo "当前会话id: " . posix_getsid(0) . PHP_EOL; //传0表示获取当前进程的会话id if (isGroupLeader()) { echo "当前进程是进程组长\n"; } $ret = posix_setsid(); //创建一个新会话 var_dump($ret); //由于当前进程是进程组长,此处会返回-1, 表示调用失败
上記のルーチンは次のように出力します:
当前会话id: 13000 当前进程是进程组长 int(-1)
次に、新しいセッションを作成するために pcntl_fork() を使用したことに気付きました。子プロセスが作成された後は、子プロセスはプロセス リーダーではないため、子プロセスを使用して新しいセッションを作成できます。
<?php function isGroupLeader() { return posix_getpgrp() == posix_getpid(); } echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL; $pid = pcntl_fork(); if ($pid > 0) { exit(0); // 让父进程退出 } elseif ($pid == 0) { if (isGroupLeader()) { echo "是进程组组长\n"; } else { echo "不是进程组组长\n"; } echo "进程组ID:" . posix_getpgrp() . PHP_EOL; echo "进程号pid: " . posix_getpid() . PHP_EOL; $ret = posix_setsid(); var_dump($ret); echo "当前进程所属会话ID:" . posix_getsid(0) . PHP_EOL; }
上記のルーチンは次のように出力します:
当前进程所属会话ID:13000 [root@localhost php]# 不是进程组组长 进程组ID:15856 进程号pid: 15857 int(15857) 当前进程所属会话ID:15857
子プロセスを使用して新しいセッションが正常に作成されました。
制御端末
と制御プロセス: (端末とは、すべての入出力デバイスの総称であり、キーボード、マウス、モニターなどはすべて端末です) セッションには 1 つの制御端末と 1 つの制御端末を含めることができますセッション専用です。セッション作成当初は制御端末が存在しませんが、セッションリーダーは端末の開設を申請することができ、この端末が他のセッションの制御端末でない場合には、その時点の端末がセッションの制御端末となります。セッションリーダーは制御プロセスと呼ばれます。
Linux でセッションに制御端末があるかどうかを判断するには、実際の制御端末を指す特別なファイル /dev/tty を開いてみることができます。それが正常に開かれた場合、それは存在することを意味します。は制御端子です。それ以外の場合は制御端子がありません。
<?php function isGroupLeader() { return posix_getpgrp() == posix_getpid(); } $pid = pcntl_fork(); if ($pid > 0) { sleep(1); $fp = fopen("/dev/tty", "rb"); if ($fp) { echo "父进程会话 " . posix_getsid(0) . " 拥有控制终端\n"; } else { echo "父进程会话 " . posix_getsid(0) . " 不拥有控制终端\n"; } exit(0); // 让父进程退出 } elseif ($pid == 0) { if (isGroupLeader()) { echo "是进程组组长\n"; } else { echo "不是进程组组长\n"; } $ret = posix_setsid(); var_dump($ret); $fp = fopen("/dev/tty", "rb"); if ($fp) { echo "子进程会话 " . posix_getsid(0) . " 拥有控制终端\n"; } else { echo "子进程会话 " . posix_getsid(0) . " 不拥有控制终端\n"; } }
上記のルーチンの子プロセスは新しいセッションを作成し、親プロセスと子プロセスの両方がファイル /dev/tty を開こうとします。ルーチンの出力は次のとおりです。
不是进程组组长 int(15906) PHP Warning: fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30 Warning: fopen(/dev/tty): failed to open stream: No such device or address in /root/php/setsid.php on line 30 子进程会话 15906 不拥有控制终端 父进程会话 13000 拥有控制终端は SIGHUP シグナル
2. さらに、制御プロセスが終了すると、カーネルは端末のフォアグラウンド プロセス グループのすべてのメンバーに SIGHUP シグナルも送信します。
<?php $callback = function($signo){ $sigstr = 'unkown signal'; switch($signo) { case SIGINT: $sigstr = 'SIGINT'; break; case SIGHUP: $sigstr = 'SIGHUP'; break; case SIGTSTP: $sigstr = 'SIGTSTP'; break; } file_put_contents("daemon.txt", "catch signal $sigstr\n", FILE_APPEND); }; pcntl_signal(SIGINT, $callback); pcntl_signal(SIGHUP, $callback); pcntl_signal(SIGTSTP, $callback); while(1) { sleep(100); pcntl_signal_dispatch(); }php sighup.php を使用してプログラムを実行し、ターミナルを直接閉じて再度シェルにログインすると、プログラムがまだ実行中であり、キャプチャされた SIGHUP シグナルがデーモンに記録されていることがわかります。 .txt ファイル。
[root@localhost php]# cat daemon.txt catch signal SIGHUP [root@localhost php]# ps aux | grep sighup.php root 18438 0.0 0.4 191600 8996 ? S 16:48 0:00 php sighup.php root 18443 0.0 0.0 103328 896 pts/0 S+ 16:53 0:00 grep sighup.php
同時に、Linux は nohup コマンドを提供します。これにより、プロセスは、
[root@localhost php]# nohup php sighup.php nohup: 忽略输入并把输出追加到"nohup.out"
標準入力、標準出力、標準エラー出力
php では、STDIN、STDOUT、STDERR という 3 つのファイル ハンドルがデフォルトで開かれており、それぞれ上記の 3 つのファイル記述子に対応しています。標準入力と標準出力は端末に関連しているため、これらは役に立ちません。ただし、直接閉じると問題が発生する可能性がありますので、次のコードを参照してください <?php
fclose(STDOUT);
$fp = fopen("stdout.log", "a");
echo "hello world\n";
上記のコードを実行すると、画面にはエコー情報が出力されませんが、ファイルを開く これは、STDOUT ファイルを閉じるためです。ハンドルの後、対応するファイル記述子が解放され、Linux はファイルを開くために常に使用可能な最小のファイル記述子を使用するため、このファイル記述子は fopen によって開かれたファイルを指すようになります。最初に標準出力に書き込まれた情報が、内部のファイルに書き込まれるようになります。この奇妙な動作を回避するには、次のような 3 つのファイル ハンドルを閉じた後、Linux によって提供されるブラック ホール ファイル /dev/null をすぐに開くことができます。
<?php fclose(STDIN); fclose(STDOUT); fclose(STDERR); fopen('/dev/null', 'r'); fopen('/dev/null', 'w'); fopen('/dev/null', 'w'); $fp = fopen("stdout.log", "a"); echo "hello world\n";
上面这个例程关闭STDIN,STDOUT, STDERR立马打开 /dev/null 三次,这样echo的信息会直接写到黑洞中,避免了前面出现的怪异的问题。
(三)编写守护进程涉及的其他问题
编写守护进程还涉及工作目录、文件掩码、信号处理、热更新、安全的启动停止等等问题,这里先留给大家自己百度,后期有空再来补充。
(四)一个守护进程的示例
<?php //由于进程组长无法创建会话,fork一个子进程并让父进程退出,以便可以创建新会话 switch(pcntl_fork()) { case -1: exit("fork error"); break; case 0: break; default: exit(0); //父进程退出 } posix_setsid(); //创建新会话,脱离原来的控制终端 //再次fork并让父进程退出, 子进程不再是会话首进程,让其永远无法打开一个控制终端 switch(pcntl_fork()) { case -1: exit("fork error"); break; case 0: break; default: exit(0); //父进程退出 } //关闭标准输入输出 fclose(STDIN); fclose(STDOUT); fclose(STDERR); fopen('/dev/null', 'r'); fopen('/dev/null', 'w'); fopen('/dev/null', 'w'); //切换工作目录 chdir('/'); //清除文件掩码 umask(0); //由于内核不会再为进程产生SIGHUP信号,我们可以使用该信号来实现热重启 pcntl_signal(SIGHUP, function($signo){ //重新加载配置文件,重新打开日志文件等等 }); for(;;) { pcntl_signal_dispatch(); //处理信号回调 //实现业务逻辑 }
to be continue!
相关推荐:
PHP实现系统编程之本地套接字(Unix Domain Socket)
以上がPHPでシステムプログラミングを実装するためのデーモンプロセスの書き方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。