Maison >développement back-end >tutoriel php >Qu'est-ce qu'un démon ? Comment implémenter un démon en PHP ?

Qu'est-ce qu'un démon ? Comment implémenter un démon en PHP ?

青灯夜游
青灯夜游avant
2021-06-23 20:30:302842parcourir

Un démon est un processus spécial qui s'exécute en arrière-plan et est utilisé pour effectuer des tâches système spécifiques. Cet article vous expliquera comment implémenter un démon en PHP et vous présentera ce à quoi vous devez prêter attention en programmation.

Qu'est-ce qu'un démon ? Comment implémenter un démon en PHP ?

Le démon d'implémentation PHP peut être implémenté via les extensions pcntl et posix.

Les choses à noter dans la programmation sont :

  • Passez le deuxième pcntl_fork() et posix_setsid pour sortir le processus principal du terminal
  • Passez pcntl_signal() Ignorer ou gérer les signaux SIGHUP
  • Les programmes multi-processus doivent passer pcntl_fork() ou pcntl_signal() deux fois pour ignorer le signal SIGCHLD afin d'empêcher le processus enfant de devenir un processus zombie
  • via le masque d'autorisation du fichier de paramètres umask() pour empêcher les autorisations des autorisations de fichier héritées d'affecter la fonction
  • Rediriger le STDIN/STDOUT/STDERR du processus en cours d'exécution vers /dev/null ou d'autres flux

si vous voulez encore mieux, vous devez également faire attention à :

  • Si vous démarrez via root, passez à une identité d'utilisateur peu privilégié lors de l'exécution de
  • Timely chdir() Eviter les mauvais chemins d'opération
  • Les programmes de processus multiples doivent envisager de redémarrer régulièrement pour éviter les fuites de mémoire

Qu'est-ce que un démon

Le protagoniste de l'articleDémon , la définition sur Wikipédia est :

Dans un système d'exploitation informatique multitâche, démon ( Anglais : démon, /ˈdiːmən/ ou /ˈdeɪmən/) C'est un programme informatique qui s'exécute en arrière-plan. De tels programmes seront initialisés en tant que processus. Les noms des programmes démons se terminent généralement par la lettre « d » : par exemple, syslogd fait référence au démon qui gère les journaux système.
Habituellement, le processus démon n'a aucun processus parent existant (c'est-à-dire PPID=1) et est situé directement sous init dans la hiérarchie des processus du système UNIX. Un programme démon se transforme généralement en démon en exécutant fork sur un processus enfant, puis en mettant immédiatement fin à son processus parent afin que le processus enfant puisse s'exécuter sous init. Cette méthode est souvent appelée « bombardement ».

Programmation avancée dans un environnement UNIX (deuxième édition) (ci-après dénommé APUE) Le chapitre 13 dit :

Les processus démons sont également devenus le Le processus démon est un processus avec un long cycle de vie. Ils démarrent souvent au démarrage du système et ne se terminent que lorsque le système est arrêté. Parce qu'ils n'ont pas de terminal de contrôle, on dit qu'ils fonctionnent en arrière-plan.

On note ici que le démon présente les caractéristiques suivantes :

  • Aucun terminal
  • Fonctionne en arrière-plan
  • Le parent process pid est 1

Si vous souhaitez afficher le processus démon en cours d'exécution, vous pouvez le visualiser via ps -ax ou ps -ef, où -x signifie que les processus sans terminal de contrôle seront répertoriés .

Mise en œuvre des préoccupations

Deuxième fork et setsid

appel système fork

appel système fork

pcntlforkpcntl_fork() est utilisé pour copier un processus qui est presque identique au processus parent. La différence entre le processus enfant nouvellement généré et le processus parent est qu'il a un processus différent. pid et un différent Selon l'implémentation logique du code de l'espace mémoire, les processus parent et enfant peuvent effectuer le même travail ou des tâches différentes. Le processus enfant hérite des ressources telles que les descripteurs de fichiers du processus parent.

L'extension en PHP implémente la fonction , qui est utilisée pour créer un nouveau processus en PHP.

appel système setsid

会话setsid进程组 l'appel système est utilisé pour créer une nouvelle session et définir l'identifiant du groupe de processus.

Il y a plusieurs concepts ici :

,

. &SIGHUPSous Linux, la connexion utilisateur génère une session. Une session contient un ou plusieurs groupes de processus, et un groupe de processus contient plusieurs processus. Chaque groupe de processus a un chef de session et son pid est l'identifiant du groupe de processus. Une fois que le responsable du processus ouvre un terminal, ce terminal est appelé terminal de contrôle. Dès qu'une exception se produit dans le terminal de contrôle (déconnexion, erreur matérielle, etc.), un signal sera envoyé au chef du groupe de processus. SIGHUP

Les programmes exécutés en arrière-plan (tels que l'exécution d'instructions se terminant par dans le shell) seront également supprimés après la fermeture du terminal, car le signal envoyé lorsque le terminal de contrôle est déconnecté n'est pas bien géré. , et Le comportement par défaut d'un signal pour un processus est de quitter le processus.

Après avoir appelé l'appel système setsid, le processus actuel créera un nouveau groupe de processus. Si le terminal n'est pas ouvert dans le processus en cours, alors il n'y aura pas de terminal de contrôle dans ce groupe de processus, et il n'y en aura pas. terminal de contrôle en raison de l'arrêt. Le problème de l'arrêt du processus via le terminal.

L'extension posix en PHP implémente la fonction posix_setsid(), qui est utilisée pour définir un nouveau groupe de processus en PHP.

Processus orphelin

Le processus parent se termine avant le processus enfant, et le processus enfant deviendra un processus orphelin.

Le processus d'initialisation adoptera le processus orphelin, c'est-à-dire que le ppid du processus orphelin devient 1.

Le rôle du fork secondaire

Tout d'abord, l'appel système setsid ne peut pas être appelé par le chef du groupe de processus et renverra -1.

L'exemple de code pour la deuxième opération fork est le suivant :

$pid1 = pcntl_fork();

if ($pid1 > 0) {
    exit(0);
} else if ($pid1 < 0) {
    exit("Failed to fork 1\n");
}

if (-1 == posix_setsid()) {
    exit("Failed to setsid\n");
}

$pid2 = pcntl_fork();

if ($pid2 > 0) {
    exit(0);
} else if ($pid2 < 0) {
    exit("Failed to fork 2\n");
}

Supposons que nous exécutons l'application dans le terminal et que le processus est a. Le premier fork générera un processus enfant b. Si le fork réussit, le processus parent a se termine. b En tant que processus orphelin, il est hébergé par le processus init.

À ce stade, le processus b est dans le groupe de processus a et le processus b appelle posix_setsid pour demander la génération d'un nouveau groupe de processus. Une fois l'appel réussi, le groupe de processus actuel devient b.

A ce moment, le processus b a effectivement quitté n'importe quel terminal de contrôle. Routine :

<?php

cli_set_process_title('process_a');

$pidA = pcntl_fork();

if ($pidA > 0) {
    exit(0);
} else if ($pidA < 0) {
    exit(1);
}

cli_set_process_title('process_b');

if (-1 === posix_setsid()) {
    exit(2);
}

while(true) {
    sleep(1);
}

Après l'exécution du programme :

➜  ~ php56 2fork1.php
➜  ~ ps ax | grep -v grep | grep -E 'process_|PID'
  PID TTY      STAT   TIME COMMAND
28203 ?        Ss     0:00 process_b

D'après les résultats de ps, le TTY de process_b. C'est devenu , c'est-à-dire qu'il n'y a pas de terminal de contrôle correspondant.

Le code semble avoir terminé sa fonction à ce stade. process_b n'a pas été tué après la fermeture du terminal, mais pourquoi y a-t-il une deuxième opération fork ?

Une réponse sur StackOverflow est bien écrite :

Le deuxième fork(2) est là pour garantir que le nouveau processus n'est pas un leader de session, donc il ne pourra pas (accidentellement) attribuer un terminal de contrôle, puisque les démons ne sont pas censés avoir un terminal de contrôle.

Cela permet d'éviter que le processus de travail réel ne s'associe activement ou que le contrôle Le terminal est accidentellement associé. Le nouveau processus généré après le fork ne peut pas postuler à l'association avec le terminal de contrôle car il n'est pas le chef du groupe de processus.

Pour résumer, la fonction du fork secondaire et du setsid est de générer un nouveau groupe de processus et d'empêcher le processus de travail d'être associé au terminal de contrôle.

Gestion du signal SIGHUP

L'action par défaut d'un processus recevant le signal SIGHUP est de terminer le processus.

Et SIGHUP sera émis dans les situations suivantes :

  • Le terminal de contrôle est déconnecté, SIGHUP est envoyé au chef du groupe processus
  • Le groupe processus leader quitte, SIGHUP sera envoyé au processus de premier plan dans le groupe de processus
  • SIGHUP est souvent utilisé pour notifier au processus de recharger le fichier de configuration (mentionné dans APUE, le démon est considéré comme peu susceptible de recevoir ce signal car il n'a pas de terminal de contrôle. Alors choisissez la réutilisation)

Étant donné que le processus de travail réel n'est pas dans le groupe de processus de premier plan et que le chef du groupe de processus a quitté et ne contrôle pas le terminal, il n'est bien sûr pas de problème s'il n'est pas traité normalement. Cependant, afin d'éviter que le processus ne se termine en raison de la réception accidentelle de SIGHUP, et pour suivre les conventions de programmation du démon, ce signal doit quand même être traité.

Traitement du processus Zombie

Qu'est-ce que le processus Zombie

Simplement parlant, le processus enfant first Lorsque le processus parent se termine, le processus parent n'appelle pas le traitement de l'appel système wait et le processus devient un processus zombie.

Lorsque le processus enfant se termine avant le processus parent, il enverra le signal SIGCHLD au processus parent. Si le processus parent ne le gère pas, le processus enfant deviendra également un processus zombie.

Les processus zombies occuperont le nombre de processus pouvant être dupliqués. Trop de processus zombies entraîneront l'incapacité de créer de nouveaux processus.

De plus, dans le système Linux, un processus dont le ppid est le processus d'initialisation sera recyclé et géré par le processus d'initialisation après qu'il soit devenu un Zombie.

Gestion du processus Zombie

D'après les caractéristiques du processus Zombie, pour les démons multi-processus, ce problème peut être résolu de deux manières :

  • Le processus parent gère leSIGCHLD signal
  • Laissez le processus enfant être pris en charge par init

Le processus parent gère le signal. fonction de rappel de traitement du signal et appelez la méthode de recyclage.

Pour que le processus enfant soit repris par init, vous pouvez utiliser la méthode de fork deux fois, afin que le processus enfant a du premier fork puisse ensuite débourser le processus de travail réel b et laisser une sortie Tout d'abord, faire en sorte que b devienne un processus orphelin afin qu'il puisse être hébergé par le processus d'initialisation.

umask

umask sera hérité du processus parent, affectant les autorisations des fichiers créés.

PHP Manuel mentionné :

umask() définit l'umask de PHP sur le masque & 0777 et renvoie l'umask d'origine. Lorsque PHP est utilisé comme module serveur, l'umask est restauré après chaque requête.

Si l'umask du processus parent n'est pas défini correctement, des effets inattendus se produiront lors de l'exécution de certaines opérations sur les fichiers :

➜  ~ cat test_umask.php
<?php
        chdir('/tmp');
        umask(0066);
        mkdir('test_umask', 0777);
➜  ~ php test_umask.php
➜  ~ ll /tmp | grep umask
drwx--x--x 2 root root 4.0K 8月  22 17:35 test_umask

所以,为了保证每一次都能按照预期的权限操作文件,需要置0 umask 值。

重定向0/1/2

这里的0/1/2分别指的是 STDIN/STDOUT/STDERR,即标准输入/输出/错误三个流。

样例

首先来看一个样例:

上述代码几乎完成了文章最开始部分提及的各个方面,唯一不同的是没有对标准流做处理。通过 php not_redirect_std_stream_daemon.php 指令也能让程序在后台进行。

sleep 的间隙,关闭终端,会发现进程退出。

通过 strace 观察系统调用的情况:

➜  ~ strace -p 6723
Process 6723 attached - interrupt to quit
restart_syscall(<... resuming interrupted call ...>) = 0
write(1, "1503417004\n", 11)            = 11
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({10, 0}, 0x7fff71a30ec0)      = 0
write(1, "1503417014\n", 11)            = -1 EIO (Input/output error)
close(2)                                = 0
close(1)                                = 0
munmap(0x7f35abf59000, 4096)            = 0
close(0)                                = 0

发现发生了 EIO 错误,导致进程退出。

原因很简单,即我们编写的 daemon 程序使用了当时启动时终端提供的标准流,当终端关闭时,标准流变得不可读不可写,一旦尝试读写,会导致进程退出。

信海龙的博文《一个echo引起的进程崩溃》中也提到过类似的问题。

解决方案

APUE 样例

APUE 13.3中提到过一条编程规则(第6条):

某些守护进程打开 /dev/null 时期具有文件描述符0、1和2,这样,任何一个视图读标准输入、写标准输出或者标准错误的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以不能在终端设备上显示器输出,也无从从交互式用户那里接受输入。及时守护进程是从交互式会话启动的,但因为守护进程是在后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们也不会在该终端上见到守护进程的输出,用户也不可期望他们在终端上的输入会由守护进程读取。

简单来说:

  • daemon 不应使用标准流
  • 0/1/2 要设定成 /dev/null

例程中使用:

for (i = 0; i < rl.rlim_max; i++)
	close(i);

fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);

实现了这一个功能。dup() (参考手册)系统调用会复制输入参数中的文件描述符,并复制到最小的未分配文件描述符上。所以上述例程可以理解为:

关闭所有可以打开的文件描述符,包括标准输入输出错误;
打开/dev/null并赋值给变量fd0,因为标准输入已经关闭了,所以/dev/null会绑定到0,即标准输入;
因为最小未分配文件描述符为1,复制文件描述符0到文件描述符1,即标准输出也绑定到/dev/null;
因为最小未分配文件描述符为2,复制文件描述符0到文件描述符2,即标准错误也绑定到/dev/null;复制代码

开源项目实现:Workerman

Workerman 中的 Worker.php 中的 resetStd() 方法实现了类似的操作。

/**
* Redirect standard input and output.
*
* @throws Exception
*/
public static function resetStd()
{
   if (!self::$daemonize) {
       return;
   }
   global $STDOUT, $STDERR;
   $handle = fopen(self::$stdoutFile, "a");
   if ($handle) {
       unset($handle);
       @fclose(STDOUT);
       @fclose(STDERR);
       $STDOUT = fopen(self::$stdoutFile, "a");
       $STDERR = fopen(self::$stdoutFile, "a");
   } else {
       throw new Exception('can not open stdoutFile ' . self::$stdoutFile);
   }
}

Workerman 中如此实现,结合博文,可能与 PHP 的 GC 机制有关,对于 fd 0 1 2来说,PHP 会维持对这三个资源的引用计数,在直接 fclose 之后,会使得这几个 fd 对应的资源类型的变量引用计数为0,导致触发回收。所需要做的就是将这些变量变为全局变量,保证引用的存在。

推荐学习:《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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer