1、首先概念普及:

SAPI: Server abstraction API,它提供了一个接口,使得PHP可以和其他应用进行交互数据,具体点说是提供了一个和外部通信的接口。常见的:给apache的mod_php5,CGI,给IIS的ISAPI,还有Shell的CLI

首先我们看个从鸟哥那挪来的PHP架构图:

如果还感觉概念模糊的话 可以试着用wamp升级php版本来找下感觉

首先说下本篇以Apache sapi 为例来介绍,对sapi的功能实现比较全嘛, 还常用。

如果想看简单的可从cgi入手,鸟哥有对此介绍:http://www.laruence.com/2008/08/12/180.html

2、接下来我们来看具体的实现:

要定义个SAPI,首先要定义个sapi_module_struct,查看源码:php-5.5.12/sapi/apache2handler/sapi_apache2.c:

static sapi_module_struct apache2_sapi_module = {"apache2handler",                                                /* 输出给php_info()使用 */"Apache 2.0 Handler",                                            /* pretty name */php_apache2_startup,               /* startup */               /* 当SAPI初始化时,首先会调用该函数 */php_module_shutdown_wrapper,         /* shutdown */          /*  关闭函数包装器,它用来释放所有的SAPI的数据结构、内存等,调用php_module_shutdown */NULL,                       /* activate */                      /* 此函数会在每个请求开始时调用,它会做初始化,资源分配 */NULL,                     /* deactivate */                    /* 此函数会在每个请求结束时调用,它用来确保所有的数据都得到释放 */php_apache_sapi_ub_write,            /* unbuffered write */      /* 不缓存的写操作(unbuffered write),它是用来向SAPI外部输出数据 */php_apache_sapi_flush,                /* flush */                 /* 刷新输出,在CLI模式下通过使用C语言的库函数fflush实现 */php_apache_sapi_get_stat,           /* get uid */               /* */php_apache_sapi_getenv,                /* getenv */                /* 根据name查找环境变量 */php_error,                    /* error handler */                 /* 注册错误处理函数 */php_apache_sapi_header_handler,           /* header handler */    /* PHP调用header()时候被调用 */php_apache_sapi_send_headers,           /* send headers handler *//* 发送头部信息 */NULL,                     /* send header handler */           /* 发送一个单独的头部信息 */php_apache_sapi_read_post,         /* read POST data */        /* 当请求的方法是POST时,程序获取POST数据,写入$_POST数组 */php_apache_sapi_read_cookies,         /* read Cookies */      /* 获取Cookie值 */php_apache_sapi_register_variables,                              /* register server variables 给$_SERVER添加环境变量 */php_apache_sapi_log_message,         /* Log message */       /* 输出错误信息 */php_apache_sapi_get_request_time,       /* Request Time */      /* */NULL,                      /* Child Terminate */               /* */STANDARD_SAPI_MODULE_PROPERTIES
};

由上面代码,再结合SAPI.h和SAPI.c,其实可以看出 PHP的SAPI像是 面向对象中基类,SAPI.h和SAPI.c包含的函数是抽象基类的声明和定义,各个服务器用的SAPI模式,则是继承了这个基类,并重新定义基类方法的子类。

1,php_apache2_startup:当通过apache调用PHP时,这个函数会被调用。该函数定义如下,主要是对PHP进行初始化。

static int php_apache2_startup(sapi_module_struct *sapi_module)
{if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) {return FAILURE;}return SUCCESS;
}

其中主要做得工作:

启动

  • 初始化若干全局变量

这里的初始化全局变量大多数情况下是将气设置为NULL,有一些除外,比如设置zuf(zend_utility_functions),

  • 初始化若干常量

这里的常量是PHP自己的常量

......省略若干...../* Register constants */REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);REGISTER_MAIN_LONG_CONSTANT("PHP_MINOR_VERSION", PHP_MINOR_VERSION, CONST_PERSISTENT | CONST_CS);REGISTER_MAIN_LONG_CONSTANT("PHP_RELEASE_VERSION", PHP_RELEASE_VERSION, CONST_PERSISTENT | CONST_CS);REGISTER_MAIN_STRINGL_CONSTANT("PHP_EXTRA_VERSION", PHP_EXTRA_VERSION, sizeof(PHP_EXTRA_VERSION) - 1, CONST_PERSISTENT | CONST_CS);REGISTER_MAIN_LONG_CONSTANT("PHP_VERSION_ID", PHP_VERSION_ID, CONST_PERSISTENT | CONST_CS);
.....省略若干....
  • 初始化Zend引擎和核心组件

这里的初始化主要有

内存管理初始化,

全局使用的函数指针初始化,

对PHP源文件进行词法分析、语法分析、中间代码执行的函数指针的赋值,

初始化若干HashTable,

为ini文件解析做准备,

为PHP源文件解析做准备,

注册内置函数,

注册变准常量,

注册GLOBALS全局变量等

  • 解析php.ini
  • 全局操作函数的初始化
  • 启动静态构建的模块
  • 启动php.ini中需要加载的 共享模块(MINIT)
  • 禁用函数和类

2,php_module_shutdown_wrapper :PHP的关闭函数。

3,PHP会在每个request的时候,处理一些初始化,资源分配的事务。这部分就是activate字段要定义的。

4,deactiveate,它会提供一个handler, 用来处理收尾工作。

5,php_apache_sapi_ub_write:提供一个向Response数据写的接口。

static int
php_apache_sapi_ub_write(const char *str, uint str_length TSRMLS_DC)
{request_rec *r;php_struct *ctx;ctx = SG(server_context);r = ctx->r;if (ap_rwrite(str, str_length, r) < 0) {php_handle_aborted_connection();}return str_length; /* we always consume all the data passed to us. */
}

6,php_apache_sapi_flush:提供给zend刷新缓存的句柄。

static void
php_apache_sapi_flush(void *server_context)
{php_struct *ctx;request_rec *r;TSRMLS_FETCH();ctx = server_context;/* If we haven't registered a server_context yet,* then don't bother flushing. */if (!server_context) {return;}r = ctx->r;sapi_send_headers(TSRMLS_C);r->status = SG(sapi_headers).http_response_code;SG(headers_sent) = 1;if (ap_rflush(r) < 0 || r->connection->aborted) {php_handle_aborted_connection();}
}

7,php_apache_sapi_get_stat:这部分用来让Zend可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等。

static struct stat*
php_apache_sapi_get_stat(TSRMLS_D)
{php_struct *ctx = SG(server_context);ctx->finfo.st_uid = ctx->r->finfo.user;ctx->finfo.st_gid = ctx->r->finfo.group;ctx->finfo.st_dev = ctx->r->finfo.device;ctx->finfo.st_ino = ctx->r->finfo.inode;
#if defined(NETWARE) && defined(CLIB_STAT_PATCH)ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime);ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime);ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime);
#elsectx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime);ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime);ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime);
#endifctx->finfo.st_size = ctx->r->finfo.size;ctx->finfo.st_nlink = ctx->r->finfo.nlink;return &ctx->finfo;
}

8,php_apache_sapi_getenv:为Zend提供了一个根据name来查找环境变量的接口,当我们在脚本中调用getenv的时候,就会间接的调用这个句柄。

static char *
php_apache_sapi_getenv(char *name, size_t name_len TSRMLS_DC)
{php_struct *ctx = SG(server_context);const char *env_var;if (ctx == NULL) {return NULL;}env_var = apr_table_get(ctx->r->subprocess_env, name);return (char *) env_var;
}

9,php_error:错误处理函数,直接调用PHP错误处理函数。

10,php_apache_sapi_header_handler:在调用PHP的header()函数时,会调用这个函数。

static int
php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC)
{php_struct *ctx;char *val, *ptr;ctx = SG(server_context);switch (op) {case SAPI_HEADER_DELETE:apr_table_unset(ctx->r->headers_out, sapi_header->header);return 0;case SAPI_HEADER_DELETE_ALL:apr_table_clear(ctx->r->headers_out);return 0;case SAPI_HEADER_ADD:case SAPI_HEADER_REPLACE:val = strchr(sapi_header->header, ':');if (!val) {return 0;}ptr = val;*val = '\0';do {val++;} while (*val == ' ');if (!strcasecmp(sapi_header->header, "content-type")) {if (ctx->content_type) {efree(ctx->content_type);}ctx->content_type = estrdup(val);} else if (!strcasecmp(sapi_header->header, "content-length")) {
#ifdef PHP_WIN32
# ifdef APR_HAS_LARGE_FILESap_set_content_length(ctx->r, (apr_off_t) _strtoui64(val, (char **)NULL, 10));
# elseap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10));
# endif
#elseap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10));
#endif} else if (op == SAPI_HEADER_REPLACE) {apr_table_set(ctx->r->headers_out, sapi_header->header, val);} else {apr_table_add(ctx->r->headers_out, sapi_header->header, val);}*ptr = ':';return SAPI_HEADER_ADD;default:return 0;}
}

11,php_apache_sapi_send_headers:当要真正发送header的时候,这个函数会被调用。

static int
php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{php_struct *ctx = SG(server_context);const char *sline = SG(sapi_headers).http_status_line;ctx->r->status = SG(sapi_headers).http_response_code;/* httpd requires that r->status_line is set to the first digit of* the status-code: */if (sline && strlen(sline) > 12 && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') {ctx->r->status_line = apr_pstrdup(ctx->r->pool, sline + 9);ctx->r->proto_num = 1000 + (sline[7]-'0');if ((sline[7]-'0') == 0) {apr_table_set(ctx->r->subprocess_env, "force-response-1.0", "true");}}/*  call ap_set_content_type only once, else each time we call it,configured output filters for that content type will be added */if (!ctx->content_type) {ctx->content_type = sapi_get_default_content_type(TSRMLS_C);}ap_set_content_type(ctx->r, apr_pstrdup(ctx->r->pool, ctx->content_type));efree(ctx->content_type);ctx->content_type = NULL;return SAPI_HEADER_SENT_SUCCESSFULLY;
}

12,在php_apache_sapi_send_headers指针下面有一个域,用来指明发送每一个单独的header时调用。

13,php_apache_sapi_read_post:表示如何读取POST数据。

static int
php_apache_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC)
{apr_size_t len, tlen=0;php_struct *ctx = SG(server_context);request_rec *r;apr_bucket_brigade *brigade;r = ctx->r;brigade = ctx->brigade;len = count_bytes;/** This loop is needed because ap_get_brigade() can return us partial data* which would cause premature termination of request read. Therefor we* need to make sure that if data is available we fill the buffer completely.*/while (ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES, APR_BLOCK_READ, len) == APR_SUCCESS) {apr_brigade_flatten(brigade, buf, &len);apr_brigade_cleanup(brigade);tlen += len;if (tlen == count_bytes || !len) {break;}buf += len;len = count_bytes - tlen;}return tlen;
}

14,php_apache_sapi_read_cookie:如何读取cookie。

static char *
php_apache_sapi_read_cookies(TSRMLS_D)
{php_struct *ctx = SG(server_context);const char *http_cookie;http_cookie = apr_table_get(ctx->r->headers_in, "cookie");/* The SAPI interface should use 'const char *' */return (char *) http_cookie;
}

15,php_apache_sapi_register_variables:提供接口,用于给$_SERVER[]数组提供变量。

static void
php_apache_sapi_register_variables(zval *track_vars_array TSRMLS_DC)
{php_struct *ctx = SG(server_context);const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env);char *key, *val;int new_val_len;APR_ARRAY_FOREACH_OPEN(arr, key, val)if (!val) {val = "";}if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len TSRMLS_CC)) {php_register_variable_safe(key, val, new_val_len, track_vars_array TSRMLS_CC);}APR_ARRAY_FOREACH_CLOSE()if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len TSRMLS_CC)) {php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array TSRMLS_CC);}
}

16,php_apache_sapi_log_message:输出错误信息。

static void php_apache_sapi_log_message(char *msg TSRMLS_DC)
{php_struct *ctx;ctx = SG(server_context);if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg);} else {ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "%s", msg);}
}

17,php_apache_sapi_get_request_time:获取请求时间。

static double php_apache_sapi_get_request_time(TSRMLS_D)
{php_struct *ctx = SG(server_context);return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0;
}

这就完成了apache的SAPI定义【通过这个SAPI的分析 我也可以想象其他SAPI的实现机制】

之后当用户用URL请求apache服务,这些函数指针就会在适当的时候,发挥作用了(被调用)。

PHP内核之SAPI:Apache2 SAPI分析相关推荐

  1. 制造内核崩溃并使用crash分析内核崩溃产生的vmcore文件

    制造内核崩溃并使用crash分析内核崩溃产生的vmcore文件 1,安装kernel-debuginfo$(uname -r).rpm和kernel-debuginfo-common-$(uname ...

  2. (75)内核APC执行过程,分析 KiDeliverApc 函数

    一.内核APC执行过程 通过分析 SwapContext ,KiSwapContexgt , KiSwapThread ,我们得出一个结论:切换线程后会执行内核APC,调用的函数是 KiDeliver ...

  3. 鸿蒙轻内核M核源码分析:中断Hwi

    摘要:本文带领大家一起剖析了鸿蒙轻内核的中断模块的源代码,掌握中断相关的概念,中断初始化操作,中断创建.删除,开关中断操作等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列五 中断Hwi&g ...

  4. 鸿蒙轻内核M核源码分析:数据结构之任务就绪队列

    摘要:本文会给读者介绍鸿蒙轻内核M核源码中重要的数据结构,任务基于优先级的就绪队列Priority Queue. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列三 数据结构-任务就绪队列> ...

  5. CentOS 7 Linux实时内核下的epoll性能分析后续 | 火焰图分析

    在<CentOS 7 Linux实时内核下的epoll性能分析>从源码角度分析了epoll在实时内核和非实时内核之间的差异,为了更好的展示问题所在,这里给出epoll和select在实时内 ...

  6. CentOS 7 Linux实时内核下的epoll性能分析

    CentOS 7 Linux实时内核下的epoll性能分析 rtoax 2021年3月4日 1. 问题引入 一些参考链接见文末. 1.1. 测试调试环境 非实时环境: 3.10.0-1062.el7. ...

  7. Linux内核IP Queue机制的分析(一)

    将会通过包括本文在内的三篇文章,对IP Queue机制从用户态的应用到内核态的模块程序设计进行分析.三篇文章的题目分别是: Linux内核IP Queue机制的分析(一)--用户态接收数据包 Linu ...

  8. 内核之旅:简单分析getsockopt系统调用

    内核之旅:简单分析getsockopt系统调用 引出问题 今天看了个sockopt操作,本想着了解下流程,看看进程是如何获取opt的.结果前一半顺利,后一半坎坷,但是还是坚持了下来. 问题出在哪里? ...

  9. linux收发包内核进程名称,Linux内核IP Queue机制的分析(一)——用户态接收数据包...

    序 笔者将会通过包括本文在内的三篇文章,对IP Queue机制从用户态的应用到内核态的模块程序设计进行分析.三篇文章的题目分别是: Linux内核IP Queue机制的分析(一)­--用户态接收数据包 ...

最新文章

  1. linux双机脚本pkg如何生效,linux里命令pkg config工具的使用
  2. 9 计算机组成原理第五章 中央处理器 指令流水线
  3. CLOUD TOOLKIT打包SPRINGCLOUD项目,多模块
  4. Linux---进程的基本概念
  5. Django - 日期、时间字段
  6. Android之TextView练习
  7. 成功是需要付出代价的: 32个成功观念分享
  8. ubuntu安装labelme
  9. 简述prototype, _proto_, constructor三者的关系
  10. SpringBoot整合Redis
  11. c语言实数的输出和占位_C语言输出格式总结
  12. c语言均衡器,拿什么拯救你的音色——初识均衡器
  13. ESP8266串口WiFi模块基本使用方法和配置教程
  14. iOS开发-Xcode8兼容iOS7手记
  15. Mugeda(木疙瘩)H5案例课—快闪制作-岑远科-专题视频课程
  16. centos 7.6编译安装nginx
  17. 牛客 打气球的最大分数
  18. java架构图软件,如何画好IT项目中的各种架构图
  19. Busiest Computing Nodes
  20. 个人学习(解决)练习ssm框架遇到的问题No qualifying bean of type ‘service.BookTypeService‘ available:

热门文章

  1. 微信小程序中使用画布canvas实现动态心电图绘制
  2. 线程池系列三:动态修改线程池队列大小
  3. 「今天14:30」AI+经济学 Debate 现场,记得来啊 | AI TIME 27
  4. MTK平台双击Power打开Camera的简单流程
  5. 小牛电动Q2财报:国外不乐观,国内狂下沉
  6. 《重构-改善既有代码的设计》第三章(上)
  7. 【DockerCE】RHEL 7.9完整安装DockerCE 20.10.5的包集合
  8. 【Unity3D】拖尾TrailRenderer
  9. html项目符号正方形,html – 列表项下的项目符号
  10. 面试官:什么是静态代理?什么是动态代理?注解、反射你会吗?