Home  >  Article  >  Backend Development  >  Explore the PHP life cycle

Explore the PHP life cycle

coldplay.xixi
coldplay.xixiforward
2020-07-28 16:38:082043browse

Explore the PHP life cycle

Learn PHP life cycle

The life cycle of PHP is a very complex process, and its life cycle should be mastered by those who are keen to use it. The main content is as follows:

PHP startup. If running the CLI or FPM, it will run C main(). If run as a module to a network server, like with the apxs2 SAPI (Apache 2), PHP starts shortly after Apache starts and starts running the startup sequence of its modules, of which PHP is one. Startup is called internally Module Startup Step. We also abbreviate it as MINIT step.

Once started, PHP will wait to process one/several requests. When we talk about PHP CLI, there will be only one request: the current script to run. But when we talk about web environment - it should be PHP-FPM or web server module - PHP can handle multiple requests one after another. It all depends on how you configure your web server: you can tell it to handle an unlimited number of requests, or a specific number of requests before shutting down and recycling the process. PHP runs the request initiation step every time a new request is to be processed in a thread. We call it RINIT.

Related learning recommendations: PHP programming from entry to proficiency

The request was processed and (maybe) some content was generated, OK. It's time to close the request and get ready to handle another request. Close request calls request close step. We call it RSHUTDOWN. ·

After processing X requests (one, dozens, thousands, etc.), PHP will finally shut down itself and end. Shutting down the PHP process is called the Module Shutdown Step. The abbreviation is MSHUTDOWN.

If we could draw these steps, we might get the following:

Explore the PHP life cycle

Parallel Model

In the CLI environment, anything It's easy: a process handles a request: it starts a separate PHP script and then ends. The CLI environment is a specialization of the web environment and is more complex.

In order to handle multiple requests at the same time, you must run the parallel model. There are two types in PHP:

  • The process-based model The process-based model
  • The thread-based model The thread-based model

Use Based on the process model, the operating system isolates each PHP interpreter into its own process. This model is very common in Unix. Each request goes to its own process. PHP-CLI, PHP-FPM, and PHP-CGI use this model.

In the thread-based model, each PHP interpreter is isolated into threads using a thread library. This model is mainly used in Windows operating systems, but can also be used in most Unix. Requires PHP and its extensions to be built in ZTS mode.

This is the process-based model:

Explore the PHP life cycle

This is the thread-based model:

Explore the PHP life cycle

Note

As an extension developer, PHP's multi-process module is not an option for you. You will need to support it. You must enable your extension to run in a threaded environment, especially on Windows, and must be programmed for it.

PHP Extension Hooks

As you may have guessed, the PHP engine will trigger your extension at multiple lifecycle points. We call them hook functions. Your extension can declare interest in specific lifecycle points by declaring function hooks when registering with the engine.
These hooks can be clearly seen when you analyze the PHP extension structure (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;
};

Now let's see which kind of hooks you should write in code.

Module initialization: MINIT()

This is the PHP process startup step. In the extended MINIT(), you will load and allocate any persistent objects or information needed for each subsequent request. Most of them will be allocated as read-only objects.

In MINIT(), no thread or process has popped up yet, so you have full access to global variables without any protection. Also, you can't allocate request-bound memory since the request hasn't started yet. You never use Zend memory management allocations in the MINIT() step, but permanent allocations are used. Not emalloc(), but pemalloc(). Otherwise it will cause a crash.

In MINIT(), the execution engine has not been started yet, so do not try to access any of its structures without special attention.

If you need to register an INI entry for your extension, MINIT() is the correct approach.

If you want to register read-only zend_strings for later use, please use persistent allocation.

If the objects you need to allocate will be written to while processing a request, then you must copy their memory allocation to the thread-specific pool for that request. Remember, you can only safely write to global space in MINIT().

Note

Memory management, allocation, and debugging are part of the memory management chapter.

In the php_module_startup() function, MINIT() is triggered through zend_startup_modules().

Module termination: MSHUTDOWN()

This is the PHP process termination step. It's easy, basically, you're running the opposite of what you used in MINIT() here. You free resources, unregister INI settings, etc.

Note again: the execution engine is closed, so you should not access any of its variables here.

Since you don't need requests here, you should not use Zend Memory Management's efree() or similar functions to release resources, but for releasing persistent allocations, use pefree ().

In the php_module_shutdown() function, MSHUTDOWN() is triggered by zend_shutdown() in zend_destroy_modules().

Request initialization: RINIT()

The request you just looked at, PHP will handle it here. In RINIT(), you direct the resources needed to handle that precise request. PHP is a shared-nothing architecture that provides memory management capabilities.

In RINIT(), if you need to allocate dynamic memory, you will use the Zend memory manager. You will call emalloc(). The Zend memory manager keeps track of the memory you allocate through it, and when a request is closed, it will try to free the request-bound memory if you forget to do this (which you shouldn't do).

Here you should not request persistent dynamic memory, i.e. libc's malloc() or Zend's pemalloc(). If you request persistent memory here and forget to release it, you will create a leak that will pile up as PHP handles more and more requests, eventually causing the process to crash (Kernel OOM) and the machine to run out of memory.

Also, be careful not to write to the global space here. If PHP is run into threads as the selected parallel model, then you will modify the context in each thread pool (all requests processed in parallel with yours), and if you do not lock the memory, a race condition may also be triggered. If you want the big picture, you have to protect them.

Note

Global scope management is explained in a dedicated chapter.

In the php_request_startup() function, RINIT() is triggered by zend_activate_module().

Request termination: RSHUTDOWN()

This is the PHP request termination step. PHP has just finished processing its requests and now it's time to clean up its portion of memory as a shared-nothing architecture. Subsequent requests should not remember anything from the current request. It's easy, basically you're doing the opposite of what RINIT() is using here. You release the resource bound by the request.

Since you are using requests here, you should use the Zend memory manager's efree() or similar to free the resource. If you forget to free and cause a leak, under debug builds the memory manager will log the leaked pointers on process stderr and free them for you.

To give you an idea, RSHUTDOWN() will be called:

  • After executing the user area shutdown function (register_shutdown_function())
  • After calling each object destructor
  • After PHP output buffer flush
  • After disabling max_execution_time

In php_request_shutdown() function , trigger RSHUTDOWN() through zend_deactivate_modules().

Post request termination: PRSHUTDOWN()

This hook is rarely used. It is called after RSHUTDOWN(), but some additional engine code will be run in the middle.
Especially in Post-RSHUTDOWN:

  • The PHP output buffer has been closed and its handler has been flushed
  • The PHP superglobal has been destroyed
  • Execution engine has been shut down

This hook is rarely used. In the php_request_shutdown() function, through zend_post_deactivate_modules(), it is triggered after RSHUTDOWN().

Global initialization: GINIT()

The thread library will call this hook every time a thread pops up. If you are using multiple processes, when PHP starts, this function is called only before MINIT() is triggered.

I won’t go into too many details here, just simply initialize the global variables here, usually initialized to 0. Global management will be explained in detail in a dedicated chapter.

Remember that global variables are not cleaned up after each request. If you need to reset them for each new request (possibly), then you must put such a process in RINIT().

Note

Global scope management is introduced in detail in the dedicated chapter.

Global termination: GSHUTDOWN()

In the thread library, this hook is called whenever a thread terminates. If you use multi-threading, this function will be called once during PHP termination (at MSHUTDOWN()).

Without giving too many details here, you can simply uninitialize your global variables here, normally you don't have to do anything, but if you are building a global (GINIT()) When resources are allocated, in this step you should release them.

Global management will be introduced in detail in a dedicated chapter.

Remember that global variables are not cleared after each request. That is, GSHUTDOWN() will not be called as part of RSHUTDOWN().

Note

Global scope management is introduced in detail in the dedicated chapter.

Information collection: MINFO()

This hook is very special, it will never be automatically triggered by the engine, it will only be triggered when you ask it for information about the extension. A typical example is calling phpinfo(). This function is then run and special information about the current expansion is printed to the stream.

In short, phpinfo() displays information.

This function can also be called via the CLI using one of the reflection switches, such as php --ri pib or via the userland call ini_get_all().

You can leave this blank, in which case only the name of the extension will be shown and nothing else (the INI settings may not be shown, since this is part of MINFO()).

Thinking about the PHP life cycle

Explore the PHP life cycle

You may have discovered, RINIT() and RSHUTDOWN() are especially important since they are triggered thousands of times in extensions. If the PHP step is for the web (not CLI) and has been configured to handle an unlimited number of requests, then your RINIT()/RSHUTDOWN() groups will be called an unlimited number of times.

We would like to once again draw your attention to memory management. While handling the request (between RINIT() and RSHUTDOWN()) you end up leaking small bytes that will have a serious impact on a fully loaded server. That's why it's recommended that you use the Zend Memory Manager for such allocations, and be prepared to debug the memory layout. As part of its shared-nothing architecture, PHP forgets and releases requested memory at the end of each request. This is by PHP's internal design.

Also, if your crash signal is SIGSEGV (bad memory access), the entire process will crash. If PHP is using threads as a multi-process engine, then all your other threads will also crash, possibly even causing the server to crash.

Note

C language is not PHP language. With C, errors in the program are likely to cause the program to crash and terminate.

Hooking by overriding function pointers

Now that you know when the engine will fire your code, there are also function pointers worth noting that you can replace to hook into the engine. Because those pointers are global variables, you can replace them with the MINIT() step and put them back into the MSHUTDOWN() step.

Interested ones are:

  • ##AST, Zend/zend_ast.h:

      ##void (
    • zend_ast_process_t) (zend_ast ast)
    ##Compiler, Zend/zend_compile.h:
  • zend_op_array
      (
    • zend_compile_file)(zend_file_handle file_handle, int type)*zend_op_array
    • (
    • zend_compile_string)(zval source_string, char filename)
    Executor, 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)*
    • Error, 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)
  • ##Lifetime, Zend/zend.h:

    • void (zend_on_timeout)(int seconds)*
    • void (zend_interrupt_function)(zend_execute_data execute_data)
    • void (zend_ticks_function)(int ticks)*
There are others, but the above are the most important when you When designing PHP extensions, you may need. Since their names are easy to read, they will not be explained in detail.

If you need more information, you can look in the PHP source code and discover when and how they are triggered.

The above is the detailed content of Explore the PHP life cycle. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:learnku.com. If there is any infringement, please contact admin@php.cn delete