虽然大部分php工程师都不需要知道php的C代码核心是如何运作的,有些人可能知道有个dl()函数.或者使用过一些第三方的类库,这些正是本文的重点之一.      
 
   希望对那些想把php带向更宽的边界的工程师有所帮助.

先来看看php的一个基本的运行流程:
   
   浏览器用户--->web服务器(apache,nginx)--->Zend引擎从文件系统读取php代码文件--->Zend解释器工作
   
   --->执行解释后的代码-->Zend引擎注册的函数接口-->内置模块或者各个需要的外部模块扩展-->数据库memcache等后端资源
   
   其中 
   Zend引擎注册的函数接口 就是php工程师经常接触的各种php函数.
   外部模块扩展 就是php编译的各个so文件(linux)或者dll文件(windwos).
   执行解释后的代码 浏览器的内容就是从这里返回的.
   内置模块 也就是php每次启动的时候会携带启动的模块. 
   从上面的流程图可以知道php可以从3个点进行扩展.1 外部模块扩展 2 Zend引擎 3 内置模块,下面我将一一讨论.
   
   外部模块扩展.
       如果你使用过dl()你就接触过这些外部的扩展模块.外部的扩展模块文件就放在你的硬盘里,他在php脚本运行时被加载到内存中,而且只有需要的时候才被加载.
   当此次的脚本运行完之后他就会被内存释放掉,总的来说它运行的慢但是不占资源.不需要你重新编译一个php.
   
   内置模块
       虽然也是Zend引擎之外的模块,但是与外部模块扩展有些不同,他已经在php里边了.他会使得你编译的php体积变大,如果有改变,必须重新编译php才行.内置模块会使得
   php内存变大,但是调用起来也会更加的快速.在我们的测试中一些模块运行在内置模式会有30%以上的速度提升.
   
   Zend引擎
      首先,我绝对不建议你去修改Zend引擎.一些php语言的特性只要在Zend引擎中才能够实现.比如你要修改数组关键字的名字,你可以在这里实现.
   在你下载的php源代码里,以zend开头的都是zend引擎的相关代码.
   
   一般php源码目录结构类似下面:
   main php的主要源代码,
   ext php的扩展 
   sapi 与不同服务器的api交互层代码
   zend zend引擎部分
   TSRM 线程安全相关模块代码
   
   下面以一个简单的模块为例子说明PHP如何扩展:
   
   首先php的代码有自己的一套标准,你需要遵守,不然可能会导致你的模块无法释放变量或者其他的问题,这些标准包括 宏定义,变量声明等.你可以到官方浏览详细的说明.

/* 扩展的标准头 */
#include "php.h"

/* 声明这个so被导出的函数 */
ZEND_FUNCTION(helloworld_module);

/* Zend引擎注册的函数接口 */
zend_function_entry helloworldmod_interfaces[] =
{
    ZEND_FE(helloworld_module, NULL)
    {NULL, NULL, NULL}
};

/* 这是这个模块的声明实体,它的值对模块编译的时候起实际作用 */
zend_module_entry helloworldmod_module_entry =
{
    STANDARD_MODULE_HEADER,
    "Hello world",
    helloworldmod_interfaces,
    NULL, 
    NULL, 
    NULL, 
    NULL, 
    NULL,
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};

/* 向zend引擎声明一个备案,可以说明 helloworldmod_module_entry属于helloworldmod.so这个动态库*/
#if COMPILE_DL_helloworld_module
ZEND_GET_MODULE(helloworldmod)
#endif

/* 这就是我们新增的函数的真正代码 */
ZEND_FUNCTION(helloworld_module)
{
    return "Hello,world";
}

我们可以根据其他扩展的config.m4文件来修改成我们的必要编译配置信息。这里这个模块几乎是一个空的config.m4文件就行,
   
   然后利用phpize来生成configure文件然后是 ./configure && make && make install执行就能编译一份我们的动态库
   
   test.php
<?php

echo helloworld_module();

?>      
   输出:
      "Hello,world"
      
      
   完成了PHP扩展,我们已经深入了php的c代码内部,但是在一些情况下,这还不够。我们需要深入到c语言调用c库的过程当中,在linux下面一个很给力的工具是LD_PRELOAD环境变量
   
   LD_PRELOAD环境变量是编译器找到程序中所引用的函数或全局变量所存在的位置的一个过滤器,比如在php的c代码里调用一个开始网络连接的方法connect,事实上就是通过动态链接
   
   去寻找linux的c库的函数connect,这些链接文件一般放在lib下面,这也就为我们影响php的代码执行提供了一个切入点。因为php程序在动态载入lib下面的函数connect之前会检查LD_PRELOAD
   
   提供的动态库里有没有这个connect函数,我们可以在这里对php的行为进行干涉。
   
   下面以一个简单的过滤网络访问的例子说明如何实现:
   
   先是一个准备作为LD_PRELOAD环境变量的值的so文件的代码。
   lp_demo.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <dlfcn.h>

//定义我们自己的connect函数
int  connect(int  sockfd,  const  struct sockaddr *serv_addr, socklen_t
             addrlen){
  static int (*connect_linuxc)(int, const  struct sockaddr*, socklen_t)=NULL;
  unsigned char *ip_char;
 
  //利用 lsym的RTLD_NEXT选项绕过LD_PRELOAD环境变量的connect方法找到c库的函数
  if (!connect_linuxc) connect_linuxc=dlsym(RTLD_NEXT,"connect");
 
    ip_char=serv_addr->sa_data;
    ip_char+=2;
 
    
     //192.168.2.3 找到了
    if ((*ip_char==192)&&(*(ip_char+1)==168)&&(*(ip_char+2)==2)&&(*(ip_char+3)==3)) {
    
         //简单返回一个权限错误的代码
            return EACCES;
    }

// 调用真正的connect方法
   return connect_linuxc(sockfd,serv_addr,addrlen);
  
}

编译成so文件

$ gcc -o lp_demo.so -shared lp_demo.c -ldl

测试文件 test.php

<?php

file_get_contents("http://192.168.2.3/");

?>

使用方法
LD_PRELOAD=lp_demo.so php test.php

这样他将不可能访问的到192.168.2.3这种我们内部的网址。起到一个很好的沙盒作用。

除此之外我们还可以利用fwrite fopen等函数将php对文件系统的读写操作转移到mencache,nosql之类的后端资源当中。

最后,即使我们已经深入了c库的内部,也不意味着我们走到了最底层,在c库下面,还有一堆sys_开头的函数,他们才是内核空间里的真正函数,在此就不在探讨了。

[置顶] PHP如何扩展和如何在linux底层对php扩展?相关推荐

  1. 横向扩展文件服务器,如何在 VMM 中创建横向扩展文件服务器

    适用于: System Center 2012 R2 Virtual Machine Manager 你可以使用下列过程,通过 Virtual Machine Manager (VMM) 控制台从物理 ...

  2. [置顶] 总结工作中常用到的linux命令

    常用解压命令 tar.bz2 命令: tar -jxvf  *.tar.bz2 tar.z   命令: tar -zxvf  *.tar.z tar.gz   命令: tar -Zxvf  *.tar ...

  3. Qt实用技巧:Qt窗口置顶

    若该文为原创文章,未经允许不得转载 原博主博客地址:长沙红胖子Qt C++ Linux Arm_长沙红胖子_CSDN博客-Qt开发,图形图像处理,OpenCV图像处理领域博主 原博主博客导航:红胖子( ...

  4. android列表实现置顶,Android利用RecyclerView实现全选、置顶和拖拽功能示例

    Android利用RecyclerView实现全选.置顶和拖拽功能示例 发布时间:2020-08-23 16:26:42 来源:脚本之家 阅读:159 作者:爱开发 前言 今天给大家分享是如何在Rec ...

  5. 阅读目录(置顶)(长期科技领域知识)

    第一期:适用于数据项目的7种强大的开源工具 第二期:四种正确的微服务部署方式 第三期:开源和赚钱何去何从 第四期:Web前端和Java开发哪个薪资更高,发展前景更好? 第五期:干货总结:中级前端工程师 ...

  6. android环信删除会话列表,关于会话列表的置顶聊天

    最近搞完了置顶聊天,来写篇文章分享下经验. 其实刚刚开始 ,我自己在想,我是不是要去做出类似于QQ那种的滑动,然后显示置顶和删除. 图1 我就开始写,写完了之后然后去置顶,取消置顶,其实是有用的,但是 ...

  7. Android集成环信IM,实现聊天置顶功能

    首先是,要实现置顶聊天,那么我们就要有两个List集合,一个是置顶的,一个是不是置顶的.这里,环信给出了EMConversation的一个方法,带大家看看技术文档. 看下这个文档里面说的非常清楚,也就 ...

  8. 玩安卓从 0 到 1 之列表一键置顶

    前言 系列文章 这篇文章是这个系列的第六篇文章了,下面是前五篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项目首页 3.玩安卓从 0 到 1 之首页框架搭建. 4.玩安卓 ...

  9. 仿QQ对话列表滑动删除与置顶的原理及实现

    接下来,我们将完成QQ聊天界面的ListView滑动效果,大家可能都用过ListView,知道ListView是上下滑动的,并不会产生左右滑动的效果,如果想让ListView变成左右滑动的效果,必须对 ...

最新文章

  1. 在Docker里使用(支持镜像继承的)supervisor管理进程(转)
  2. 强制类型转换规则 java_Java学习——第002天学习笔记整理
  3. android studio device功能,Android Studio 3.0找不到Android Device Monitor
  4. IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
  5. 2020美国纽约大学计算机科学排名,2020美国纽约大学排名第几
  6. 怎么ie取消要打开或保存来自_取消认证后,发票抵扣就这么简单!
  7. c 使用RTP协议发送视频数据
  8. 网络 一篇博文搞懂五种常见的IO模型
  9. mysql 从库升级为主库的步骤
  10. 蓝桥杯13年--18年Java组B组省赛题目以及题解汇总
  11. 一种电力线宽带载波系统采样频偏的估计方法
  12. sip服务器支持alg,SIP利用Tunnel與ALG方法穿越NAT之效能分析
  13. 巧用Q盘搭建SVN服务器
  14. java ljava/lang/string_([Ljava/lang/String;)V的含义
  15. Service Mesh-Conduit概览
  16. 计算机电缆价格范围,计算机电缆DJYPVPR价格,报价
  17. 解决wordpress部分博客文章页面无法显示的问题
  18. 百度关键词分析工具_【轰炸类】关键词百度首页分析【澳门XXX】【实战分析】...
  19. markdown插入本地图片小技巧
  20. 简述矩阵的秩和向量组的秩的定义 从定义出发分析两者之间的相互关系

热门文章

  1. pyrosim有Linux版本吗,烟气流动分析软件Pyrosim版本 2020.4.092
  2. 在python中排序元组
  3. LeetCode Factorial Trailing Zeroes(计算阶乘结果尾部有几个0)
  4. Makefile 里的 subst 函数
  5. 二分查找(递归和非递归)
  6. 29、剑指offer--最小的K个数
  7. Codeforces#371 Div2
  8. Drupal8 社区文档之在Drupal中,查看网页的编辑一个页面的几乎是相同的
  9. jquery实现tab切换加自动滚动切换
  10. C# Socket 入门5 UPD 结构体 与 C++ 通信