[置顶] PHP如何扩展和如何在linux底层对php扩展?
虽然大部分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扩展?的更多相关文章
- [置顶] Oracle 11g ASM:如何在 ASMCMD 命令行工具中创建 Oracle ACFS 文件系统
实验环境:Oracle 11g R2 RAC (11.2.0.3.5) Oracle Enterprise Linux 5.6 x86 1.创建 ASM 磁盘组 在两节点 ...
- [置顶] C#扩展方法 扩你所需
通过前面的学习,了解到:使用扩展方法,可以向现有类型“添加”方法.本文将使用扩展方法来对系统类型,自定义类型及接口进行方法扩展,一睹扩展方法的风采. 1.使用扩展方法来扩展系统类型 String是c# ...
- Javascript笔记----实现Page页面右下角置顶按钮.
从用博客开始,发现博客园中很多博友的博客中在Page右下角都有个图标,不论屏幕怎么拉伸,都始终停留在右下角.点击后页面置顶.后面想想写一个Demo来实现这种效果吧. 一. 图标右下角固定. 1.SS ...
- QT窗口置顶/真透明/背景模糊/非矩形/跳过任务栏分页器/无边框/无焦点点击/焦点穿透
qt 窗口置顶/真透明/背景模糊/非矩形/跳过任务栏分页器/无边框/无焦点点击/焦点穿透 窗口置顶qt 里是 setWindowFlags(Qt::WindowStaysOnTopHint)kde 里 ...
- VC++ 判断你的窗口是否置顶TopMost
大家可能已经知道,使你的窗口置顶(TopMost)或者总是最前(Always on Top)的方法: C++ Code 12345 // Make topmost , SWP_NOMOVE | ...
- UITableView设置Cell左滑多个按钮(编辑,删除,置顶等)
一.iOS7不支持cell多个按钮这个时候可以使用一个三方库JZTableViewRowAction,引用类扩展文件并实现其代理方法 JZTableViewRowAction下载地址:http://d ...
- 在UWP中页面滑动导航栏置顶
最近在研究掌上英雄联盟,主要是用来给自己看新闻,顺便copy个界面改一下段位装装逼,可是在我copy的时候发现这个东西 当你滑动到一定距离的时候导航栏会置顶不动,这个特性在微博和淘宝都有,我看了@ms ...
- WinFrom窗体始终置顶
调用WindowsAPI使窗体始终保持置顶效果,不被其他窗体遮盖: [DllImport("user32.dll", CharSet = CharSet.Auto)] privat ...
- winform窗体置顶
winform窗体置顶 金刚 winform 置顶 今天做了一个winform小工具.需要设置置顶功能. 网上找了下,发现百度真的很垃圾... 还是必应靠谱些. 找到一个可以链接. https://s ...
随机推荐
- 《小C QQ空间转帖、分享工具》之QQ空间数据传递的g_tk算法(C#)
原文地址:http://user.qzone.qq.com/419067339/2 public string GET_HTTP(string url, string referer_post, st ...
- XCode工程中ARC模式与非ARC模式共用(转)
Xcode 项目中经常会融合一些老的代码,它们可能采用非ARC的模式.混合编译时,就会碰到编译出错的情况. 如何共用ARC模式和非ARC模式呢? XCode除了提供整个项目是否使用ARC模式的选择外, ...
- poj2503
有关字符串的hash,用黑书上推荐的传说中的ELFhash函数 输入的话,用sscanf处理比較简洁 #include<iostream> #include<cstring> ...
- android VoiceRecognition 语音识别并打印到列表上
package com.example.wenandroid; import java.util.ArrayList; import java.util.List; import android.ap ...
- n!的近似值 (stirling approximation)与 大O记法(big -O- notation)
參考wiki: 1.n!的近似值 (stirling approximation) (中文) http://zh.wikipedia.org/wiki/%E6%96%AF%E7%89%B9%E9%9D ...
- ContextMenuStrip 添加在窗体。点击右键不能显示问题解答
ContextMenuStrip控件,系统默认为点击菜单,使用时.需要与你的窗体绑定.如图所示,,form窗体上包括一个contextMenustrip信息.默认状态是无.所以在使用contextMe ...
- PHP自学4——通过mail函数将feedback界面用户填写表单信息发送至指定邮箱
这一讲的内容依旧简单(谁叫PO主水平菜,依旧是个弱鸡ORZ),通过PHP的内置mail函数将一个反馈界面的信息发送到指定邮箱.在Windows平台不能直接需要使用该函数,需要下载一个sendmail并 ...
- node.js(四)path优化(路径优化)
1.normalize函数的基本用法 normalize函数将不符合规范的路径经过格式化转换为标准路径,解析路径中的.与..外,还能去掉多余的斜杠. 如下示例: var path = require( ...
- CGAffineTransformMake(a,b,c,d,tx,ty) 矩阵运算的原理 (转载)
简记: CGAffineTransformMake(a,b,c,d,tx,ty) ad缩放bc旋转tx,ty位移,基础的2D矩阵 公式 x=ax+cy+tx y=bx+dy+ty 1.矩阵的基 ...
- Java提高学习之Object(5)
字符串形式的表现 Q1:toString() 方法实现了什么功能?A1:toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug. Q2:当 toString() 方法没 ...