1. 引擎(Zend)+组件(ext)的模式降低内部耦合

  2. 中间层(sapi)隔绝web server和php

  3. Sapi主要有apache2handler,cgi,cli等接口

  4. PHP类型

    php的变量都保存在一个结构中

    其中真正的值都保存在zvalue_value中

    typedef struct _zval_struct {
        zvalue_value value;
        zend_uint refcount;
        zend_uchar type;
        zend_uchar is_ref;
      } zval;
    

    refcount为引用计数,zval.type为Is_Long或者Is_Bool时,则去取zval.value.lval或dval,字符串取str,资源也会去lval,像是对应资源链表的偏移值。zvalue_value的数据结构为union

    typedef union _zvalue_value {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
    } zvalue_value;
    
  5. PHP变量

    给变量赋值时,PHP会分配一个zval来存值,而zval的结构中是没有变量名的。

    然后变量名和指向这个zval的指针会填入一个数组hashtable中。

    不同的变量会保存在不同的定义域的活动符号表中(活动符号表和全局符号表)(symbol_table和active_symbol_table)(在函数中,我们可以通过显式申明global来使用全局变量。在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建) $var=”a”;

    var(全局符号表)=>*zval指(针)

    在函数b中的变量,则保存在b符号表中,而函数的参数$var也是保存在a符号表中,不过会把其指针指向一份全局变量$var的copy的zval

    $var1=$var;

    复制给另一个变量,其实只是添加了同一个指针,引用计数refcount+1

    $var=1;

    这样会修改一个变量,执行copy on write机制,如果refcount>1,则会复制一个新的zval,将原有refcount-1,并修改符号表指针。

    上面这个是复制,而当引用时:

    $a=”v”;  
    $b=&$a;  
    $b=1;  
    

    这时修改一个is_ref=1的zval时,会执行change on write,当is_ref=1是,不会复制新的zval

    因为is_ref是bool型,所以当其zval存在is_ref时,就表示所有指向其的符号表都是引用,复制的要分离出去。还有一种情况,同时又引用和赋值的情况,这个时候会分离出复制新的zval,来确保is_ref=1时不会同时存在非引用的情况。

    可以看这儿,(那如果是混合了引用(assign-by-reference)和普通赋值(assign-by-value)的脚本,又是什么情况呢?)

  6. unset只会清除符号表,而不会去管zval,zval被GC只看refcount是否为0.

  7. 引用传递

    function do_zval_test(&$s){  
        $s = "after";  
        return $s;  
    }  
    
    $a = "before";  
    $b = do_zval_test($a);
    

    在函数调用结束之后 $a的is_ref恢复成0

  8. 引用返回

    引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时

    function &find_node($key,&$tree){  
        $item = &$tree[$key];  
        return $item;  
    }   
    
    $tree = array(1=>'one',2=>'two',3=>'three');  
    $node =& find_node(3,$tree);  
    $node ='new';
    

    为了使用引用返回,必须在函数定义和函数调用的地方都显式的使用&符号

  9. global关键字

    1. 函数中声明global,会在函数内部生成一个局部的变量,并与全局的变量建立引用
    2. 函数中对global变量的任何更改操作都会间接更改全局变量的值。
    3. 函数unset局部变量不会影响global,而只是解除与全局变量的绑定。unset只是删除函数内部符号表中var符号,而不是删除全局的。同时,更新原zval的refcount值和is_ref引用标志(引用解绑)
  10. CGI和FastCGI两个协议

    CGI是webserver与后台语言交互的协议,有了这个协议,开发者可以使用任何语言处理webserve发来的请求。但是它处理每个请求的时候都会重新fork一个进程,请求结束后就结束进程,性能低下。

    所以有了FastCGI,在一个进程里处理多个请求。

    FPM是FastCGI的实现,任何实现了FastCGI协议的webserver都能与之通信。FPM是一个PHP 进程管理器,包含master和worker进程两种,master进程只有一个,负责监听端口,接受来自webserver的请求,而worker进程一般有多个,每个进程内部都有php解释器,可以执行PHP代码。

    master进程接受到请求。

    master根据配置指派worker进程处理请求,如果没有可用进程,返回502.

    worker进程处理请求,如果超时,返回504.

    请求处理结束,返回结果。

    Nginx是如何发送请求给fpm的master进程呢?
    Nginx不仅仅是web服务器,也是一个proxy服务器,除了http请求的代理,也可以进行其他协议的代理,包括fpm相关的fastcgi协议。
    Nginx提供了fastcgi模块来将http请求映射为对应的fastcgi请求。
    Nginx的fastcgi模块提供了fastcgi_param指令来处理这些映射关系,主要是将Nginx里面的变量翻译成PHP中能理解的变量。
    8A7CDB24-6A70-4982-8CE7-C6F85E124D41

    此外还有个fastcgi_pass指令,用于指定fpm进程监听的地址,Nginx会把所有的php请求翻译成fastcgi请求后再发送到这个地址。
    C153E975-C698-4655-AB32-8B3D94FB7FDE
    这儿的配置可以看到,我们新建了虚拟主机,通过location指令,将所有php结尾的请求都交给了fastcgi模块处理,从而把所有php请求都交给了fpm处理,从而完成Nginx到fpm的闭环。