PHP内核 - SAPI - Cli

PHP   PHP内核  

一、Cli 基本常识

  Cli(Command Line Interface),即命令行接口,用于在命令行下执行PHP脚本,它是执行PHP脚本最简便的一种方式。

  Cli模式通过执行编译的PHP二进制程序即可启动,它定义了很多命令行参数,不同的参数对应不同的处理,比如:
(1)执行PHP脚本文件、直接执行PHP代码(-r参数)
(2)输出PHP版本(-v参数)
(3)输出已安装的扩展(-m参数)
(4)指定php.ini配置(-c参数)

 

二、Cli 执行流程

  Cli 是单进程模式,处理完请求后就直接关闭了,生命周期先后经历module startup、request startup、request shutdown、module shutdown,其执行流程比较简单,关键的处理过程如下:

main() -> php_cli_startup() -> do_cli() -> php_module_shutdown()

  Cli SAPI的main函数位于 /sapi/cli/php_cli.c中,执行时首先解析命令行参数,然后初始化sapi_module_struct,它是记录SAPI信息的主要结构,这个结构中有几个函数指针,它们是内核定义的操作接口的具体实现,用来告诉内核如何读取、输出数据。

  1. static sapi_module_struct cli_sapi_module = {
  2. "cli", /* name */
  3. "Command Line Interface", /* pretty name */
  4. php_cli_startup, /* startup */
  5. php_module_shutdown_wrapper, /* shutdown */
  6. // 请求初始化函数
  7. NULL, /* activate */
  8. // 请求收尾处理函数,Cli中:fflush(stdout)
  9. sapi_cli_deactivate, /* deactivate */
  10. sapi_cli_ub_write, /* unbuffered write */
  11. sapi_cli_flush, /* flush */
  12. NULL, /* get uid */
  13. NULL, /* getenv */
  14. php_error, /* error handler */
  15. // 调用header()函数的处理handle
  16. sapi_cli_header_handler, /* header handler */
  17. // 发送header时的函数
  18. sapi_cli_send_headers, /* send headers handler */
  19. sapi_cli_send_header, /* send header handler */
  20. // 获取POST数据的函数
  21. NULL, /* read POST data */
  22. sapi_cli_read_cookies, /* read Cookies */
  23. // 向$_SERVER中注册变量的函数
  24. sapi_cli_register_variables, /* register server variables */
  25. sapi_cli_log_message, /* Log message */
  26. NULL, /* Get request time */
  27. NULL, /* Child terminate */
  28. STANDARD_SAPI_MODULE_PROPERTIES
  29. };

 
(1)在完成参数的解析及sapi_module_struct的基本初始化后,进入module_startup阶段。

  1. if (sapi_module->startup(sapi_module) == FAILURE) {
  2. exit_status = 1;
  3. goto out;
  4. }

 
(2)前面定义的startup函数为php_cli_startup(),这个函数直接调用了php_module_startup()

  1. static int php_cli_startup(sapi_module_struct *sapi_module)
  2. {
  3. if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
  4. return FAILURE;
  5. }
  6. return SUCCESS;
  7. }

 
(3)在module_startup阶段处理完后,接下来进入请求初始化阶段

  1. zend_first_try {
  2. if (sapi_module == &cli_sapi_module) {
  3. exit_status = do_cli(argc, argv);
  4. } else {
  5. exit_status = do_cli_server(argc, argv);
  6. }
  7. } zend_end_try();

 
(4)do_cli()将完成请求的处理,此函数一开始对使用到的命令行参数进行解析,如果是一些查询系统信息之类的请求(如-v,-m,-i),则不需要经历PHP请求的生命周期,这里会单独处理,下面看一下执行PHP脚本请求时的处理。

  1. zend_file_handle file_handle;
  2. ...
  3. if (script_file) {
  4. // fopen请求的脚本文件
  5. if (cli_seek_file_begin(&file_handle, script_file, &lineno) != SUCCESS) {
  6. goto err;
  7. } else {
  8. char real_path[MAXPATHLEN];
  9. if (VCWD_REALPATH(script_file, real_path)) {
  10. translated_path = strdup(real_path);
  11. }
  12. script_filename = script_file;
  13. }
  14. } else {
  15. file_handle.filename = "Standard input code";
  16. file_handle.handle.fp = stdin;
  17. }
  18. // 输入类型为ZEND_HANDLE_FP,也就是FILE*
  19. file_handle.type = ZEND_HANDLE_FP;

 
  PHP 脚本执行时的输入形式有很多种,比如文件路径(filepath)、文件句柄(FILE)、文件描述符(fd)等,zend_file_handle结构就是用来定义不同输入形式的,这样可以统一PHP执行函数的输入参数。

  Cli 中此处使用的是文件句柄,在Linux环境下也就是调用fopen()打开一个文件,这样内核就可以直接读取PHP脚本代码了,当然也可以直接把文件路径提供给内核。定义好请求的输入结构后,将进行请求初始化操作,即request startup阶段,然后开始PHP脚本的执行操作。

  1. if (php_request_startup()==FAILURE) {
  2. *arg_excp = arg_free;
  3. fclose(file_handle.handle.fp);
  4. PUTS("Could not startup.\n");
  5. goto err;
  6. }
  7. ...
  8. switch (behavior) {
  9. case PHP_MODE_STANDARD:
  10. ...
  11. // 执行
  12. php_execute_script(&file_handle);
  13. ...
  14. break;
  15. case ... // 其他执行模式
  16. }

 
(5)完成脚本的处理后进入request shutdown阶段:

  1. out:
  2. if (request_started) {
  3. php_request_shutdown((void *) 0);
  4. }

 
(6)do_cli() 完成后回到 main() 函数中,进入module shutdown阶段,最后进程退出,这就是 Cli 下执行一个脚本的生命周期。

  1. out:
  2. if (module_started) {
  3. php_module_shutdown();
  4. }
  5. if (sapi_started) {
  6. sapi_shutdown();
  7. }

 

 



Top