Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

优化首章命名,使得排序更好些 #143

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/约定
File renamed without changes.
41 changes: 30 additions & 11 deletions 1.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,44 @@
那么两种init和两种shutdown各会执行多少次、各自的执行频率有多少呢?
这取决与PHP是用什么sapi与宿主通信的。最常见的四种方式如下所列:

* 直接以CLI/CGI模式调用
* 多进程模式
* 多线程模式
* Embedded(嵌入式,在自己的C程序中调用Zend Engine)
* 直接以CLI/CGI模式调用
* 多进程模式
* 多线程模式
* Embedded(嵌入式,在自己的C程序中调用Zend Engine)

## 1、CLI/CGI

CLI和CGI的SAPI是相当特殊的,因为这时PHP的生命周期完全在一个单独的请求中完成。虽然简单,不过我们以前提过的两种init和两种shutdown仍然都会被执行。图1.1展示了PHP在这种模式下是怎么工作的。

<p style="text-align:center"><img src="http://www.walu.cc/phpbook/image/01fig01.jpg" /></p>
![](http://www.walu.cc/phpbook/image/01fig01.jpg)



## 2、多进程模式

**[ps:书是2006年出版的,所以你应该理解作者说多进程是主流]**
PHP最常见的工作方式便是编译成为Apache2 的Pre-fork MPM或者Apache1 的APXS 模式,其它web服务器也大多用相同的方式工作,在本书后面,把这种方式统一叫做多进程方式。
给它起这个名字是有原因的,不是随便拍拍屁股拍拍脑袋定下来的。
当Apache启动的时候,会立即把自己fork出好几个子进程,每一个进程都有自己独立的内存空间,
也就代表了有自己独立的变量、函数等。在每个进程里的PHP的工作方式如下图所示:

<p style="text-align:center"><img src="http://www.walu.cc/phpbook/image/01fig02.jpg" /></p>


![](http://www.walu.cc/phpbook/image/01fig02.jpg)





因为是fork出来的,所以各个进程间的数据是彼此独立,不会受到外界的干扰**(ps:fork后可以用管道等方式实现进程间通信)**。
这是一片独立天地,它允许每个子进程做任何事情,玩七十码、躲猫猫都没人管,办公室拿砍刀玩自杀也没事,
下图展示了从apache的视角来看多进程工作模式下的PHP:

<p style="text-align:center"><img src="http://www.walu.cc/phpbook/image/01fig03.jpg" /></p>


![](http://www.walu.cc/phpbook/image/01fig03.jpg)



## 3、多线程模式
随着时代的进步,PHP越来越多的在多线程模式下工作,就像IIS的isapi和Apache MPM worker**(支持混合的多线程多进程的多路处理模块)**。
Expand All @@ -41,7 +54,13 @@ PHP最常见的工作方式便是编译成为Apache2 的Pre-fork MPM或者Apache
> 其实多线程与MINIT、MSHUTDOWN只执行一次并没有什么联系,多进程模式下一样可以实现。

下图展示了在这种模式下PHP的工作流程:
<p style="text-align:center"><img src="http://www.walu.cc/phpbook/image/01fig04.jpg" /></p>



![](http://www.walu.cc/phpbook/image/01fig04.jpg)




## 4、Embed
Embed SAPI是一种比较特殊的sapi,容许你在C/C++语言中调用PHP/ZE提供的函数。
Expand All @@ -53,11 +72,11 @@ Embed SAPI是一种比较特殊的sapi,容许你在C/C++语言中调用PHP/ZE
控制权在PHP和原程序间来回传递。关于嵌入式的PHP在第20章会有应用,到时我们再用实例介绍这个不经常使用的sapi。

## 关于Embed SAPI应用的文章
* [Laruence大哥的使用PHP Embed SAPI实现Opcodes查看器](http://www.laruence.com/2008/09/23/539.html) </li>
* [Laruence大哥的使用PHP Embed SAPI实现Opcodes查看器](http://www.laruence.com/2008/09/23/539.html)


## links
* [目录](<preface.md>)
* 上一节 [PHP的启动与终止](<1.2.md>)
* [目录](<preface.md>)
* 上一节 [PHP的启动与终止](<1.2.md>)
* 下一节 [线程安全](<1.4.md>)

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
22 changes: 15 additions & 7 deletions 3.1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 3.1 内存管理

在PHP里,我们可以定义字符串变量,比如&lt;?php $str="hello";?&gt;,$str这个字符串变量可以被自由的修改与复制等。这一切在C语言里看起来都是不可能的事情,我们用#char *p = "hello";#来定义一个字符串,但它是常量,是不能被修改的,如果你用p[1]='c';来修改这个字符串会引发段错误(Gcc,c99),为了修改C语言里的字符串常量,我们往往需要定义字符串数组。为了得到一个能够让我们自由修改的字符串,我们往往需要用strdup函数来复制一个字符串出来。
在PHP里,我们可以定义字符串变量,比如<?php $str="hello";?>,$str这个字符串变量可以被自由的修改与复制等。这一切在C语言里看起来都是不可能的事情,我们用 **char *p = "hello";** 来定义一个字符串,但它是常量,是不能被修改的,如果你用p[1]='c';来修改这个字符串会引发段错误(Gcc,c99),为了修改C语言里的字符串常量,我们往往需要定义字符串数组。为了得到一个能够让我们自由修改的字符串,我们往往需要用strdup函数来复制一个字符串出来。

````c
{
char *p = "hello world";
Expand All @@ -9,7 +10,6 @@
str = strdup(p);
str[0] = 'a'; //这时就能自由修改了。
}

````
在PHP内核中,大多数情况下都不应改直接使用C语言中自带着malloc、free、strdup、realloc、calloc等操作内存的函数,而应使用内核提供的操作内存的函数,这样可以由内核整体统一的来管理内存。
### Free the Mallocs
Expand Down Expand Up @@ -44,23 +44,31 @@ void call_function(const char *fname, int fname_len TSRMLS_DC)

````
当php_error_docref这个函数被调用的时候,便会触发内核中的错误处理机制,根据错误级别来决定是否调用longjmp来终止当前请求并退出call_function函数,从而efree函数便永远不会被执行了。
<div class="tip-common">
其实php_error_docref()函数就相当于php语言里的trigger_error()函数.它的第一个参数是一个将被添加到docref的可选的文档引用第三个参数可以是任何我们熟悉的E_*家族常量,用于指示错误的严重程度。后面的两个参数就像printf()风格的格式化和变量参数列表式样。</div>
其实php_error_docref()函数就相当于php语言里的trigger_error()函数.它的第一个参数是一个将被添加到docref的可选的文档引用第三个参数可以是任何我们熟悉的E_*家族常量,用于指示错误的严重程度。后面的两个参数就像printf()风格的格式化和变量参数列表式样。
### Zend内存管理器
在上面的"跳出"请求期间解决内存泄漏的方案之一是:使用Zend内存管理(Zend Memory Manager,简称ZendMM、ZMM)层。内核的这一部分非常类似于操作系统的内存管理功能——分配内存给调用程序。区别在于,它处于进程空间中非常低的位置而且是"请求感知"的;这样一来,当一个请求结束时,它能够执行与OS在一个进程终止时相同的行为。也就是说,它会隐式地释放所有的为该请求所占用的内存。图1展示了ZendMM与OS以及PHP进程之间的关系。
<p style="text-align:center;"><img src="http://www.walu.cc/phpbook/image/03fig01.jpg" />

![](http://www.walu.cc/phpbook/image/03fig01.jpg)



除了提供隐式的内存清除功能之外,ZendMM还能够根据php.ini中memory_limit设置来控制每一次内存请求行为,如果一个脚本试图请求比系统中可用内存更多的内存,或大于它每次应该请求的最大量,那么,ZendMM将自动地发出一个E_ERROR消息并且启动相应的终止进程。这种方法的一个额外优点在于,大多数内存分配调用的返回值并不需要检查,因为如果失败的话将会导致立即跳转到引擎的退出部分。
把PHP内核代码和OS的实际的内存管理层"钩"在一起的原理并不复杂:所有内部分配的内存都要使用一组特定的可选函数实现。例如,PHP内核代码不是使用malloc(16)来分配一个16字节内存块而是使用了emalloc(16)。除了实现实际的内存分配任务外,ZendMM还会使用相应的绑定请求类型来标志该内存块;这样以来,当一个请求"跳出"时,ZendMM可以隐式地释放它。
有些时候,某次申请的内存需要在一个请求结束后仍然存活一段时间,也就是持续性存在于各个请求之间。这种类型的分配(因其在一次请求结束之后仍然存在而被称为"永久性分配"),可以使用传统型内存分配器来实现,因为这些分配并不会添加ZendMM使用的那些额外的相应于每种请求的信息。然而有时,我们必须在程序运行时根据某个数据的具体值或者状态才能确定是否需要进行永久性分配,因此ZendMM定义了一组帮助宏,其行为类似于其它的内存分配函数,但是使用最后一个额外参数来指示是否为永久性分配。
如果你确实想实现一个永久性分配,那么这个参数应该被设置为1;在这种情况下,请求是通过传统型malloc()分配器家族进行传递的。然而,如果运行时刻逻辑认为这个块不需要永久性分配;那么,这个参数可以被设置为零,并且调用将会被调整到针对每种请求的内存分配器函数。
例如,pemalloc(buffer_len,1)将映射到malloc(buffer_len),而pemalloc(buffer_len,0)将被使用下列语句映射到emalloc(buffer_len):

````c
//define in Zend/zend_alloc.h:
#define pemalloc(size, persistent) ((persistent)?malloc(size): emalloc(size))

````
所有这些在ZendMM中提供的内存管理函数都能够从下表中找到其在C语言中的函数。


<table class="table-common">

<tr>
<td>C语言原生函数</td>
<td>PHP内核封装后的函数</td>
Expand Down Expand Up @@ -131,6 +139,6 @@ void *safe_pemalloc(size_t size, size_t count, size_t addtl, char persistent);


## links
* 3 [内存管理](<3.md>)
* 3.2 [引用计数](<3.2.md>)
* 3 [内存管理](<3.md>)
* 3.2 [引用计数](<3.2.md>)

File renamed without changes.
6 changes: 3 additions & 3 deletions 4.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ Orz...错误出在哪呢?我们遍历myext_samplefuc的所有实现代码也
p = strchr(str, ',');

````
现在所有的问题都水落石出了,字符串变量str没有以NULL结尾,而我们却把它当作一个参数传给了二进制不安全的字符串处理函数,str将会扫描str知道找到NULL为止,它的扫描肯定是越界了,然后引发了一个段错误。找到问题根源后,我们只要用memchr来替换strchr函数就能修复这个bug了。
现在所有的问题都水落石出了,字符串变量str没有以NULL结尾,而我们却把它当作一个参数传给了二进制不安全的字符串处理函数,str将会扫描str直道找到NULL为止,它的扫描肯定是越界了,然后引发了一个段错误。找到问题根源后,我们只要用memchr来替换strchr函数就能修复这个bug了。
### --enable-maintainer-zts
第二个重要的参数便是激活php的线程安全机制(Thread Safe Resource Manager(TSRM)/Zend Thread Safety(ZTS)),使我们开发出的程序是线程安全的。对于TSRM的介绍大家可以参考第一章的介绍,在平时的开发中,建议打开这个选项。
### --enable-embed
其实还有一个选项比较重要,那就是enable-embed,它主要用在你做php的嵌入式开发的场景中。平时我们把php作为apache的一个module进行编译,得到libphp5.so,而这个选项便使php编译后得到一个与我们设定的SAPI相对应的结果。


## links
* 4.1 [编译前的准备](<4.1.md>)
* 4.3 [Unix/Linux平台下的编译](<4.3.md>)
* 4.1 [编译前的准备](<4.1.md>)
* 4.3 [Unix/Linux平台下的编译](<4.3.md>)

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.