Maison  >  Article  >  développement back-end  >  Explication détaillée du processus d'écriture du démon pour l'implémentation de la programmation système en PHP

Explication détaillée du processus d'écriture du démon pour l'implémentation de la programmation système en PHP

不言
不言original
2018-04-13 10:48:051226parcourir

Le contenu de cet article est de partager avec vous une explication détaillée de l'écriture de démons pour la programmation système en PHP. Il a une certaine valeur de référence. Les amis dans le besoin peuvent se référer à

(1) Processus. Groupe, concepts tels que session, terminal de contrôle et processus de contrôle

Groupe de processus : Chaque processus a un groupe de processus (groupe de processus) auquel il appartient, et le groupe de processus a un chef de processus (chef de groupe de processus), l'ID de groupe de processus est le numéro de processus du chef de groupe de processus, donc pour déterminer si un processus est le chef de groupe de processus, il vous suffit de comparer si le nom du processus est égal à son processus identifiant de groupe. Vous pouvez l'utiliser en PHP. La fonction posix_getpgrp() obtient l'ID de groupe de processus du processus actuel, et posix_getpid() est utilisée pour obtenir l'ID de processus du processus actuel.


<?php

function isGroupLeader() {
    return posix_getpgrp() == posix_getpid();
}

$pid = pcntl_fork();

if($pid == 0) {
    echo &#39;子进程:&#39; . PHP_EOL;
}
elseif($pid > 0) {
    sleep(2);
    echo &#39;父进程:&#39; . PHP_EOL;
}

echo "当前进程组gid:" . posix_getpgrp() . PHP_EOL;
echo "当前进程号pid:" . posix_getpid() . PHP_EOL;

if (isGroupLeader()) {
    echo &#39;Is a process group leader&#39; . PHP_EOL;
}
else {
    echo &#39;Is not a process group leader&#39; . PHP_EOL;
}

La routine ci-dessus affichera :



子进程:
当前进程组gid:15827
当前进程号pid:15828
Is not a process group leader
父进程:
当前进程组gid:15827
当前进程号pid:15827
Is a process group leader

Session : Une session est une collection de plusieurs groupes de processus. Un groupe de processus dans la session est le leader de la session, et l'ID de session est l'identifiant du groupe de processus du leader de la session. fonction posix_getsid(int $pid) pour obtenir l'identifiant de session du processus spécifié, ou vous pouvez utiliser la fonction posix_setsid() pour créer une nouvelle session. À ce stade, le processus devient le leader de la nouvelle session. renvoie avec succès l'ID de session nouvellement créé, ou -1 en cas d'échec Notez que posix_setsid() est appelé sous Linux. Le processus de la fonction ne peut pas être le chef du groupe de processus , sinon l'appel échouera car un processus dans un groupe de processus ne peut pas s'étendre sur plusieurs sessions en même temps .


Introduction au document sur setsid sous 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, 表示调用失败

La routine ci-dessus affichera :



当前会话id: 13000
当前进程是进程组长
int(-1)

Alors comment créer une nouvelle session Nous avons remarqué qu'après avoir utilisé pcntl_fork() pour créer un processus enfant, ce Le Le processus enfant n'est pas le leader du processus, vous pouvez donc utiliser le processus enfant pour créer une nouvelle session.



<?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;
}

La routine ci-dessus affichera :



当前进程所属会话ID:13000
[root@localhost php]# 不是进程组组长
进程组ID:15856
进程号pid: 15857
int(15857)
当前进程所属会话ID:15857

Une nouvelle session a été créée avec succès à l'aide du processus enfant.

Terminal de contrôle et Processus de contrôle : (Le terminal est le terme général désignant tous les périphériques d'entrée et de sortie, tels que le clavier, la souris et le moniteur. un terminal) Une session Il peut y avoir un terminal de contrôle, et un terminal de contrôle est exclusif à une session. Lors de la première création de la session, il n'y a pas de terminal de contrôle, mais le responsable de la session peut demander à ouvrir un terminal. Si ce terminal n'est pas le terminal de contrôle des autres sessions, le terminal deviendra à ce moment le terminal de contrôle de la session. Le leader de la session est appelé le processus de contrôle.


Pour déterminer si une session a un terminal de contrôle sous Linux, on peut essayer d'ouvrir un fichier spécial /dev/tty, qui pointe vers le Véritable terminal de contrôle, s'il est ouvert avec succès, cela signifie que vous avez un terminal de contrôle, sinon, il n'y a pas de terminal de contrôle.


<?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";
    }
}

Le processus enfant de la routine ci-dessus crée une nouvelle session, puis les processus parent et enfant tentent d'ouvrir le fichier /dev/tty. de la routine est la suivante :



不是进程组组长
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 拥有控制终端

Générer le signal SIGHUP

1 . Lorsqu'une session perd le contrôle du terminal, le noyau Le processus de contrôle de la session envoie un signal SIGHUP, et généralement le processus de contrôle de la session est le processus shell. Lorsque le shell reçoit un signal SIGHUP, il enverra également un. Signal SIGHUP à tous les groupes de processus (groupes de processus de premier plan ou d'arrière-plan) créés par celui-ci, puis quittez. La méthode de traitement par défaut pour un processus recevant un signal SIGHUP consiste à quitter le processus. Bien sûr, le processus peut également personnaliser le traitement du signal ou. ignorez-le.

2. De plus, lorsque le processus de contrôle se termine, le noyau enverra également un signal SIGHUP à tous les membres du groupe de processus de premier plan du terminal.


<?php
$callback = function($signo){
        $sigstr = &#39;unkown signal&#39;;
        switch($signo) {
        case SIGINT:
            $sigstr = &#39;SIGINT&#39;;
            break;
        case SIGHUP:
            $sigstr = &#39;SIGHUP&#39;;
            break;
        case SIGTSTP:
            $sigstr = &#39;SIGTSTP&#39;;
            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();
}

Utilisez php sighup.php pour exécuter le programme, puis fermez directement le terminal et reconnectez-vous au shell. Vous constaterez que le programme est toujours en cours d'exécution. , fichier daemon.txt Le signal SIGHUP capturé sera enregistré.


[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

En même temps, Linux fournit une commande nohup, qui permet au processus d'ignorer tous les signaux SIGHUP, tels que


[root@localhost php]# nohup php sighup.php 
nohup: 忽略输入并把输出追加到"nohup.out"


(2) Entrée standard, sortie standard, sortie d'erreur standard

Il existe trois descripteurs de fichiers ouverts par défaut dans php STDIN, STDOUT et STDERR correspondent respectivement aux trois descripteurs de fichiers ci-dessus Puisque l'entrée et la sortie standard sont liées au terminal, elles ne sont d'aucune utilité au processus démon. Elles peuvent être fermées directement, mais en les fermant. directement peut causer un problème. Veuillez consulter le code suivant


<?php
fclose(STDOUT);

$fp = fopen("stdout.log", "a");

echo "hello world\n";

Lors de l'exécution du code ci-dessus, l'écran n'affichera pas d'informations d'écho, mais sera écrit à l'air libre. Cela est dû à l'arrêt. Après le descripteur de fichier STDOUT, le descripteur de fichier correspondant est libéré et Linux utilise toujours le plus petit descripteur de fichier disponible pour ouvrir un fichier, donc ce descripteur de fichier pointe désormais vers le fichier ouvert par fopen, provoquant le les informations initialement écrites sur la sortie standard doivent maintenant être écrites. Arrivées dans le fichier. Afin d'éviter ce comportement étrange, nous pouvons immédiatement ouvrir le fichier trou noir /dev/null fourni par Linux après avoir fermé ces trois descripteurs de fichiers, tels que :


<?php
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

$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(&#39;/dev/null&#39;, &#39;r&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);
fopen(&#39;/dev/null&#39;, &#39;w&#39;);

//切换工作目录
chdir(&#39;/&#39;);

//清除文件掩码
umask(0);

//由于内核不会再为进程产生SIGHUP信号,我们可以使用该信号来实现热重启
pcntl_signal(SIGHUP, function($signo){
    //重新加载配置文件,重新打开日志文件等等
});

for(;;)
{
     pcntl_signal_dispatch();  //处理信号回调
    //实现业务逻辑
}



to be continue!

相关推荐:

PHP实现系统编程之网络Socket及IO多路复用

PHP实现系统编程之本地套接字(Unix Domain Socket)

PHP实现系统编程之 多进程编程介绍及孤儿进程、僵尸进程




Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn