Maison  >  Article  >  développement back-end  >  Explorez le cycle de vie PHP

Explorez le cycle de vie PHP

coldplay.xixi
coldplay.xixiavant
2020-07-28 16:38:082127parcourir

Explorez le cycle de vie PHP

Apprendre le cycle de vie de PHP

Le cycle de vie de PHP est un processus très complexe, et son cycle de vie doit être maîtrisé par ceux qui souhaitent utilisez-le. Le contenu principal est le suivant :

Démarrage PHP. Si vous exécutez la CLI ou le FPM, il exécutera C main(). S'il est exécuté en tant que module sur un serveur réseau, comme avec le SAPI apxs2 (Apache 2), PHP démarre peu de temps après le démarrage d'Apache et commence à exécuter la séquence de démarrage de ses modules, dont PHP fait partie. Le démarrage est appelé en interne l'étape de démarrage du module. Nous l'abrégeons également en étape MINIT.

Une fois démarré, PHP attendra de traiter une/plusieurs requêtes. Quand on parle de PHP CLI, il n'y aura qu'une seule requête : le script en cours à exécuter. Mais lorsque nous parlons d'environnement Web - il devrait s'agir de PHP-FPM ou d'un module de serveur Web - PHP peut gérer plusieurs requêtes les unes après les autres. Tout dépend de la façon dont vous configurez votre serveur Web : vous pouvez lui dire de traiter un nombre illimité de requêtes, ou un nombre spécifique de requêtes avant d'arrêter et de recycler le processus. Chaque fois qu'une nouvelle requête est traitée dans un thread, PHP exécute l'étape d'initiation de la requête. Nous l'appelons RINIT.

Recommandations d'apprentissage associées : Programmation PHP du débutant à compétent

La demande est traitée et (peut-être) du contenu est généré, OK. Il est temps de clôturer la demande et de se préparer à traiter une autre demande. La demande de clôture appelle l' étape de clôture de la demande . Nous l'appelons RSHUTDOWN. ·

Lorsque X requêtes (une, des dizaines, des milliers, etc.) auront été traitées, PHP va finalement s'arrêter et se terminer. L'arrêt du processus PHP est appelé étape d'arrêt du module. L'abréviation est MSHUTDOWN.

Si nous pouvions dessiner ces étapes, nous pourrions obtenir ce qui suit :

Explorez le cycle de vie PHP

Modèle parallèle

Dans l'environnement CLI, tout est facile : un processus gère une requête : il démarre un script PHP distinct puis se termine. L'environnement CLI est une spécialisation de l'environnement Web et est plus complexe.

Afin de traiter plusieurs requêtes simultanément, vous devez exécuter un modèle parallèle. Il en existe deux types en PHP :

  • Le modèle basé sur les processus Le modèle basé sur les processus
  • Le modèle basé sur les threads Le modèle basé sur les threads

Utilisation Basé sur le modèle de processus, le système d'exploitation isole chaque interpréteur PHP dans son propre processus. Ce modèle est très courant sous Unix. Chaque demande suit son propre processus. PHP-CLI, PHP-FPM et PHP-CGI utilisent ce modèle.

Dans le modèle basé sur les threads, chaque interpréteur PHP est isolé en threads à l'aide d'une bibliothèque de threads. Ce modèle est principalement utilisé dans les systèmes d'exploitation Windows, mais peut également être utilisé dans la plupart des Unix. Nécessite que PHP et ses extensions soient construits en mode ZTS.

Voici le modèle basé sur les processus :

Explorez le cycle de vie PHP

Voici le modèle basé sur les threads :

Explorez le cycle de vie PHP

Remarque

En tant que développeur d'extensions, le module multi-processus de PHP n'est pas une option pour vous. Vous devrez le soutenir. Vous devez permettre à votre extension de s'exécuter dans un environnement threadé, en particulier sous Windows, et devez être programmé pour cela.

Hooks d'extension PHP

Comme vous l'avez peut-être deviné, le moteur PHP déclenchera votre extension à plusieurs points du cycle de vie. Nous les appelons fonctions de hook. Votre extension peut déclarer son intérêt pour des points spécifiques du cycle de vie en déclarant des hooks de fonction lors de son inscription auprès du moteur.
Ces hooks sont clairement visibles lorsque vous analysez la structure de l'extension PHP (zend_module_entry structure) :

struct _zend_module_entry {
        unsigned short size;
        unsigned int zend_api;
        unsigned char zend_debug;
        unsigned char zts;
        const struct _zend_ini_entry *ini_entry;
        const struct _zend_module_dep *deps;
        const char *name;
        const struct _zend_function_entry *functions;
        int (*module_startup_func)(INIT_FUNC_ARGS);        /* MINIT() */
        int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);   /* MSHUTDOWN() */
        int (*request_startup_func)(INIT_FUNC_ARGS);       /* RINIT() */
        int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  /* RSHUTDOWN() */
        void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);     /* PHPINFO() */
        const char *version;
        size_t globals_size;
#ifdef ZTS
        ts_rsrc_id* globals_id_ptr;
#else
        void* globals_ptr;
#endif
        void (*globals_ctor)(void *global);                /* GINIT() */
        void (*globals_dtor)(void *global);                /* GSHUTDOWN */
        int (*post_deactivate_func)(void);                 /* PRSHUTDOWN() */
        int module_started;
        unsigned char type;
        void *handle;
        int module_number;
        const char *build_id;
};

Voyons maintenant quel type de code vous devez écrire dans ces hooks.

Initialisation du module : MINIT()

Il s'agit de l'étape de démarrage du processus PHP. Dans le MINIT() développé, vous chargerez et allouerez tous les objets persistants ou informations nécessaires pour chaque demande ultérieure. La plupart d'entre eux seront alloués en tant qu'objets en lecture seule.

Dans MINIT() aucun thread ou processus n'est encore apparu, vous avez donc un accès complet aux variables globales sans aucune protection. De plus, vous ne pouvez pas allouer de mémoire liée à la requête puisque la requête n'a pas encore démarré. Vous n'utilisez jamais les allocations de gestion de mémoire Zend dans l'étape MINIT(), mais les allocations permanentes sont utilisées. Pas emalloc(), mais pemalloc(). Sinon, cela provoquera un crash.

Dans MINIT(), le moteur d'exécution n'est toujours pas démarré, il ne faut donc pas tenter d'accéder à aucune de ses structures sans attention particulière.

Si vous devez enregistrer une entrée INI pour votre extension, MINIT() est la bonne approche.

Si vous souhaitez enregistrer des zend_strings en lecture seule pour une utilisation ultérieure, utilisez l'allocation persistante.

Si les objets que vous devez allouer seront écrits lors du traitement d'une requête, vous devez alors copier leur allocation de mémoire dans le pool spécifique au thread pour cette requête. N'oubliez pas que vous ne pouvez écrire en toute sécurité que dans l'espace global situé à l'intérieur de MINIT().

Remarque

La gestion, l'allocation et le débogage de la mémoire font partie des chapitres sur la gestion de la mémoire.

Dans la fonction php_module_startup(), déclenchez zend_startup_modules() via MINIT().

Terminaison du module : MSHUTDOWN()

Il s'agit de l'étape de terminaison du processus PHP. C'est facile, en gros, vous exécutez le contraire de ce que vous avez utilisé dans MINIT() ici. Vous libérez des ressources, désenregistrez les paramètres INI, etc.

Remarque : le moteur d'exécution est désactivé, vous ne devez donc accéder à aucune de ses variables ici.

Puisque vous n'avez pas besoin de requêtes ici, vous ne devez pas utiliser efree() de Zend Memory Management ou des fonctions similaires pour libérer des ressources, mais pour libérer des allocations persistantes, utilisez pefree().

Dans la fonction php_module_shutdown(), zend_shutdown() est déclenché par zend_destroy_modules() dans MSHUTDOWN().

Initialisation de la requête : RINIT()

La requête que vous venez de regarder, PHP la gérera ici. Dans RINIT(), vous dirigez les ressources nécessaires pour traiter cette demande précise. PHP est une architecture sans partage qui offre des capacités de gestion de la mémoire.

Dans RINIT(), si vous devez allouer de la mémoire dynamique, vous utiliserez le gestionnaire de mémoire Zend. Vous appellerez emalloc(). Le gestionnaire de mémoire Zend garde une trace de la mémoire que vous allouez via lui, et lorsqu'une requête est fermée, il essaiera de libérer la mémoire liée à la requête si vous oubliez de le faire (ce que vous ne devriez pas faire).

Ici, vous ne devez pas demander de mémoire dynamique persistante, c'est-à-dire celle de la libc malloc() ou celle de Zend pemalloc(). Si vous demandez de la mémoire persistante ici et oubliez de la libérer, vous créerez une fuite qui s'accumulera à mesure que PHP traitera de plus en plus de requêtes, provoquant éventuellement le crash du processus (Kernel MOO) et le manque de mémoire de la machine.

Veillez également à ne pas écrire dans l'espace global ici. Si PHP est exécuté dans des threads en tant que modèle parallèle sélectionné, alors vous modifierez le contexte dans chaque pool de threads (toutes les requêtes traitées en parallèle avec la vôtre), et si vous ne verrouillez pas la mémoire, une condition de concurrence critique peut également être déclenchée. Si vous voulez avoir une vue d’ensemble, vous devez les protéger.

Note

La gestion du périmètre global est expliquée dans un chapitre dédié.

Dans la fonction php_request_startup(), déclenchez zend_activate_module() via RINIT().

Résiliation de la demande : RSHUTDOWN()

Il s'agit de l'étape de terminaison de la requête PHP. PHP vient de terminer le traitement de ses requêtes et il est désormais temps de nettoyer sa part de mémoire en tant qu'architecture sans partage. Les requêtes ultérieures ne doivent rien retenir de la requête en cours. C'est simple, en gros, vous faites le contraire de ce que RINIT() utilise ici. Vous libérez la ressource liée par la demande.

Puisque vous utilisez des requêtes ici, vous devez libérer la ressource en utilisant le efree() du gestionnaire de mémoire Zend ou similaire. Si vous oubliez de libérer et provoquez une fuite, sous les versions de débogage, le gestionnaire de mémoire enregistrera les pointeurs divulgués sur le processusstderr et les libérera pour vous.

Pour vous donner une idée, RSHUTDOWN() sera appelé :

  • Après avoir exécuté la fonction de fermeture de la zone utilisateur (register_shutdown_function())
  • après avoir appelé chaque objet analyse Après le constructeur
  • Après avoir vidé le tampon de sortie PHP
  • Après avoir désactivé max_execution_time

Dans la fonction php_request_shutdown(), déclenchez zend_deactivate_modules() via RSHUTDOWN() .

Fin de la demande : PRSHUTDOWN()

Ce hook est rarement utilisé. Il porte le nom de RSHUTDOWN(), mais un code moteur supplémentaire est exécuté entre les deux.
Surtout après RSHUTDOWN :

  • Le tampon de sortie PHP a été fermé et son gestionnaire a été vidé
  • PHP superglobal a été détruit
  • Le moteur d'exécution a été détruit. été arrêté

Ce crochet est rarement utilisé. Dans la fonction php_request_shutdown(), elle est déclenchée après zend_post_deactivate_modules() jusqu'à RSHUTDOWN().

Initialisation globale : GINIT()

La bibliothèque de threads appellera ce hook à chaque fois qu'un thread est sauté. Si vous utilisez plusieurs processus, au démarrage de PHP, appelez cette fonction uniquement avant le déclenchement de MINIT().

Je n'entrerai pas dans trop de détails ici, initialisez simplement ici les variables globales, généralement initialisées à 0. La gestion globale sera expliquée en détail dans un chapitre dédié.

N'oubliez pas que les variables globales ne sont pas nettoyées après chaque requête. Si vous devez les réinitialiser pour chaque nouvelle demande (éventuellement), alors vous devez mettre ces processus dans RINIT().

Note

La gestion du périmètre global est détaillée dans le chapitre dédié.

Terminaison globale : GSHUTDOWN()

Dans la bibliothèque de threads, ce hook est appelé chaque fois qu'un thread se termine. Si vous utilisez le multithreading, cette fonction sera appelée une fois lors de la terminaison de PHP (à MSHUTDOWN()).

Sans donner trop de détails ici, vous pouvez simplement désinitialiser vos variables globales ici, généralement vous n'avez rien à faire, mais si des ressources sont allouées lors de la construction du global (GINIT()), voici les mesures à prendre pour les libérer.

La gestion globale sera présentée en détail dans un chapitre dédié.

N'oubliez pas que les variables globales ne sont pas effacées après chaque requête. Autrement dit, GSHUTDOWN() ne sera pas appelé dans le cadre de RSHUTDOWN().

Note

La gestion du périmètre global est présentée en détail dans le chapitre dédié.

Collecte d'informations : MINFO()

Ce hook est particulier, il ne sera jamais déclenché automatiquement par le moteur, il ne se déclenchera que lorsque vous lui demanderez des informations sur l'extension . Un exemple typique est l’appel de phpinfo(). Cette fonction est ensuite exécutée et des informations spéciales sur l'extension en cours sont imprimées dans le flux.

En bref, phpinfo() affiche des informations.

Cette fonction peut également être appelée via la CLI à l'aide de l'un des commutateurs de réflexion, tels que php --ri pib ou via l'espace utilisateur ini_get_all().

Vous pouvez laisser ce champ vide, auquel cas seul le nom de l'extension sera affiché et rien d'autre (les paramètres INI peuvent ne pas être affichés, car cela fait partie de MINFO()).

Réflexions sur le cycle de vie de PHP

Explorez le cycle de vie PHP

Vous avez peut-être découvert que RINIT() et RSHUTDOWN() sont particulièrement importants car ils sont déclenchés dans les extensions des milliers de fois . Si l'étape PHP est destinée au web (pas à la CLI) et a été configurée pour gérer un nombre infini de requêtes, votre groupe RINIT()/RSHUTDOWN() sera appelé un nombre infini de fois.

Nous souhaitons une nouvelle fois attirer votre attention sur la gestion de la mémoire. Lors du traitement des requêtes (entre RINIT() et RSHUTDOWN()), vous finissez par perdre de petits octets qui auront un impact sérieux sur un serveur entièrement chargé. C'est pourquoi il est recommandé d'utiliser Zend Memory Manager pour de telles allocations et d'être prêt à déboguer la disposition de la mémoire. Dans le cadre de son architecture sans partage, PHP oublie et libère la mémoire demandée à la fin de chaque requête. Ceci est dû à la conception interne de PHP.

De plus, si votre signal de crash est SIGSEGV (mauvais accès à la mémoire), l'ensemble du processus plantera. Si PHP utilise des threads comme moteur multi-processus, alors tous vos autres threads planteront également, provoquant peut-être même le crash du serveur.

Remarque

Le langage C n'est pas le langage PHP. Avec C, les erreurs dans le programme sont susceptibles de provoquer le blocage et l'arrêt du programme.

Accrochage en remplaçant les pointeurs de fonction

Maintenant que vous savez quand le moteur déclenchera le code, il existe également des pointeurs de fonction à noter que vous pouvez remplacer pour vous accrocher au moteur. Parce que ces pointeurs sont des variables globales, vous pouvez les remplacer par MINIT() étapes et les remettre dans MSHUTDOWN().

D'intérêt :

  • AST, Zend/zend_ast.h :

    • void (zend_ast_process_t) (zend_ast ast)
  • Compilateur, Zend/zend_compile.h:

    • zend_op_array ( zend_compile_file)(zend_file_handle file_handle, type int)*
    • zend_op_array (zend_compile_string)(zval source_string, char filename)
  • Exécuteur, Zend/zend_execute.h:

    • void (zend_execute_ex)(zend_execute_data execute_data)
    • void (zend_execute_internal)(zend_execute_data execute_data, zval return_value)*
  • GC, Zend/ zend_gc.h:

    • int (gc_collect_cycles)(void)*
  • TSRM, TSRM/TSRM h :

    • void (tsrm_thread_begin_func_t)(THREAD_T thread_id)*
    • void (tsrm_thread_end_func_t)(THREAD_T thread_id)*
  • Erreur, Zend/zend.h :

    • void (zend_error_cb)(int type, const char error_filename, const uint error_lineno, const char format, va_list args)*
  • Exceptions, Zend/zend_exceptions.h :

    • void (zend_throw_exception_hook)(zval ex)
  • Durée de vie, Zend/zend.h :

    • void (zend_on_timeout)(int secondes)*
    • void (zend_interrupt_function)(zend_execute_data execute_data)
    • void (zend_ticks_function)(int ticks)*

Il y en a d'autres, mais ceux ci-dessus sont les plus importants lorsque vous concevez Extensions PHP dont vous pourriez avoir besoin. Leurs noms étant faciles à lire, ils ne seront pas expliqués en détail.

Si vous avez besoin de plus d'informations, vous pouvez consulter le code source PHP et découvrir quand et comment les déclencher.

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