linux - 如何让CentOS禁止ping(防PING)

经常遇到防止PING的服务器,试了一下

直接在终端下敲入

sysctl -w net.ipv4.icmp_echo_ignore_all=1
sysctl -p


 

扩展阅读:sysctl设置和显示在/proc/sys目录中的内核参数.能用sysctl来设置或重新设置连网功能,如IP转发、IP碎片去除及源路由检查等。用户只需要编辑/etc/sysctl.conf文件,即可手工或自动执行由sysctl控制的功能。

sysctl [-n] [-e] -w variable=value
sysctl [-n] [-e] -p  (default /etc/sysctl.conf)
sysctl [-n] [-e] -a

常用参数的意义:
-w   临时改动某个指定参数的值,如
sysctl -w net.ipv4.ip_forward=1
-a   显示所有的系统参数
-p   从指定的文件加载系统参数,如不指定即从/etc/sysctl.conf中加载
如果仅仅是想临时改动某个系统参数的值,能用两种方法来实现,例如想启用IP路由转发功能:
1) #echo 1 > /proc/sys/net/ipv4/ip_forward
2) #sysctl -w net.ipv4.ip_forward=1
以上两种方法都可能即时开启路由功能,但如果系统重启,或执行了
# service network restart
命令,所设置的值即会丢失,如果想永久保留设置,能修改/etc/sysctl.conf文件
将 net.ipv4.ip_forward=0改为net.ipv4.ip_forward=1

为博客添加Gravatar通用头像支持

Ps, 好久前就在Gravatar.co注册了chen@sjolzy.cn的头像,后来发现在好多支持Gravatar的网站头像都显示不出。。下午再研究才发现是图片级别定义错误了,我设了X
级别....重新传了一张很快就通过审核 ^_^

由此就想给博客加上Gravatar头像的支持功能。

给网站增加Gravatar头像支持其实也很简单,

Gravatar.com 是根据邮箱获取用户的头像,

如:

<img src="http://www.gravatar.com/avatar/81a1fc44eb28cffeb76021d88d8d00af?s=80&amp;d=&amp;r=G" alt="">

上面显示的那张图片就是通过这段代码获取到的。。81a1fc44eb28cffeb76021d88d8d00af
一次md5
加密用户邮箱【 我用 md5( 'chen@sjolzy.cn' ) 】,?s=
这个就是图像大小 r 参数是请求图片的级别,正常设为G级最好..

具体其他可以参考:http://www.oschina.net/bbs/thread/12685

弄完之后效果好多了... 整个网站多了一股莫名的气质.....

腾讯QQ微博开放API测试例子

12月份就尝鲜了一下QQ微博放出的API,

后来中断没继续,

今天收尾一下。

查看:sjolzy微博机器人

腾讯微博开放平台:http://open.t.qq.com/

第一次使用需要先通过QQ微博平台的授权认证,

认证后才能对受限资源进行操作。

这里的应用有登记了用户的认证

第一次认证后下次就可以直接使用了

简单的实现了微博发布和一键转播

主要还是冲着微博里的来源字段这个东东

qq微博开放平台字段来源

看着会有快感~~

在在腾讯微博开放平台创建的应用审核通过后,

来源字段就生效

以上瞎属娱乐。。哈哈

 

ps,下午腾讯的API服务器貌似在维修,到晚上还是不能应用。。

Unix信号知识恶补

工作范围离linux系统比较远,这方面弱弱的,赶紧恶补下。。

从不同地方摘的,有点散。


信号是传送给进程的一种事件通知,生成信号的事件有三大类:

1.       程序错误:除零,非法内存访问 …

2.       外部信号:终端 Ctrl-C 产生 SGINT 信号,定时器到期产生 SIGALRM…

3.       显示请求: kill 函数允许进程发送任何信号给其他进程或进程组。

信号生成既可以是同步的(信号与程序中的某个具体操作相关并在那个操作同时产生),也可以是异步的。通常程序错误生成信号为同步的,进程显式请求给自己的信号也是同步的。

外部事件总是异步的,来自其他进程的显示请求也是异步的。

信号发生时,我们可以告诉 unix 内核采取下面三种动作中的任一种:

1.       忽略信号:大部分信号可被忽略,除 SIGSTOP 和 SIGKILL 信号外(这是超级用户杀掉或停掉任意进程的手段)。

2.       捕获信号:注册信号处理函数,它对产生的特定信号做处理。

3.       让信号默认动作起作用: unix 内核定义的默认动作,有 5 种情况:

a)         流产 abort :终止进程并产生 core 文件。

b)        终止 stop :终止进程但不生成 core 文件。

c)         忽略:忽略信号。

d)        挂起 suspend :挂起进程。

e)         继续 continue :若进程是挂起的,则 resume 进程,否则忽略此信号。

任意时刻,进程可以为信号指定动作。

信号处理涉及两个过程,生成与交付。

信号生成出现在事件发生时,此时内核检查接收进程的相关数据结构,此结构中记录了信号的布局,悬挂信号集和处理动作。如果信号是要被忽 略的,内核不做任何动作就返回。否则,将此信号加入悬挂信号集合中。(悬挂信号集合通常用位串表示,每位对应一个信号,内核无法记录同一信号的多个实 例)。

       如果进程处于可中断的睡眠状态,并且该信号非阻塞,内核唤醒进 程。被唤醒进程一旦运行则在返回用户态前优先处理悬挂信号,当有悬挂信号并且非阻塞时,内核查看是否有处理句柄,如果没有注册句柄,则采取默认动作(通常 为终止进程)。如果有句柄,则将此信号加入阻塞信号屏蔽中。

       最后内核安排进程返回到用户态并执行信号句柄,同时保证句柄执行完时,进程从被中断处代码执行。

       由异步事件产生的信号可能在任一条指令后发生,当信号句柄完成时,进程从中断之处起执行。如果信号是在进程处于系统调用期间到达的,内核通常 abort 此系统调用并返回错误码 EINTR 。

       进程可以有选择的阻塞信号交付,当一个被阻塞的信号生成时,如果进程指定的动作为默认或者捕获,则此信号一直悬挂于该进程直到对此信号的阻塞放开,或者信号动作改为忽略。 系统对阻塞信号的判定是在信号交付时而非生成时,这样可以允许进程在信号被交付前改变信号动作。

       每个进程有一个阻塞信号屏蔽,它定义当前被阻塞交付的那些信号。可认为它是一个位串,每位对应一个信号。如果某信号对应的位被设置,则该信号当前阻塞,进程可调用

       sigprocmask 函数来检查或设置屏蔽。

Unix信号列表

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL

5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE

9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS

13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGUSR1

17) SIGUSR2 18) SIGCHLD 19) SIGPWR 20) SIGWINCH

21) SIGURG 22) SIGIO 23) SIGSTOP 24) SIGTSTP

25) SIGCONT 26) SIGTTIN 27) SIGTTOU 28) SIGVTALRM

29) SIGPROF 30) SIGXCPU 31) SIGXFSZ 32) SIGWAITING

33) SIGLWP 34) SIGFREEZE 35) SIGTHAW 36) SIGCANCEL

37) SIGLOST 39) SIGRTMIN 40) SIGRTMIN+1 41) SIGRTMIN+2

42) SIGRTMIN+3 43) SIGRTMAX-3 44) SIGRTMAX-2 45) SIGRTMAX-1

46) SIGRTMAX

程序错误类信号
:默认动作使进程流产,产生core文件。

SIGABRT:   调用abort函数生成的信号。

SIGFPE:      浮点计算错误。

SIGILL:      非法指令错误。

SIGBUS/SIGSEGV: 硬件错误-非法地址访问。

SIGEMT:    硬件错误

SIGSYS:       非法系统调用。

SIGTRAP:   硬件错误(通常为断点指令)。

 

程序终止类信号
:默认动作使进程终止,我们通常要处理这类信号,做一些清理工作,句柄函数应在结束时为此信号指定默认动作,然后再次生成该信号,使得程序终止。

SIGHUP:终端断开连接时,生成此信号给控制进程。

SIGINT:Ctrl-C或Delete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。

SIGKILL:使程序立即终止,不能被捕获或忽略,也不能被阻塞。

SIGQUIT:Ctrl-\,如SIGINT,并且产生core。

SIGTERM:该信号使程序终止,但是可以阻塞、捕获、忽略。

 

闹钟类信号
:通知定时器到期,默认动作是终止程序,但通常会设置句柄。

SIGALRM:alarm/setitimer函数设置定时到期后,会产生此信号。

SIGPROF:

SIGVTALRM:

 

I/O类信号
:通知进程在描述字上发生了感兴趣事件,支持信号驱动IO。

SIGIO: fd准备执行输入输出时发送此信号。

SIGPOLL:异步I/O信号。

SIGURG:网络收到带外数据时可选择生成此信号。

 

作业控制类信号

SIGCHLD:   进程终止或停止时会向其父进程发送该信号,默认动作为忽略。

SIGCONT:     使停止的进程恢复运行。

SIGSTOP:      停止进程。

SIGTSTP/SIGTTIN/SIGTTOU:

 

操作错误类信号
:默认动作终止程序。

SIGPIPE:    管道破裂。

SIGXCPU/SIGXFSZ:

 

signal函数:

void (* signal(int sig, void (*func)(int)))(int);

sig指明是哪一种信号。

func指明动作:SIG_DFL, SIG_IGN,或者信号句柄地址。

当信号发生时,如果func指向信号句柄,系统在将控制转往句柄前,先将该信号动作置为DFL,或者阻塞该信号直到句柄完成。

Signal函数返回值指向前一次有效动作指针:SIG_DFL,SIG_IGN,或信号地址,这提供了恢复信号动作的机制。 如果signal调用出错,返回SIG_ERR并设置errno。

 

进程初启时的信号动作:

fork:继承父进程的动作

exec:所有信号动作要么是忽略要么是默认。

 

不可靠信号:

早期版本Unix中使用signal,每当信号交付时,其动作总是由系统重置为默认动作,因此为了使信号句柄执行期间,仍能对同一信号后续做反应,需要再次调用signal。

Catch_Signal(){

       // 如果第二次信号刚好在此时发生,将导致进程终止core掉。

       signal(SIGQUIT,Catch_Signal);

}

Main(){                   

       signal(SIGQUIT, Catch_Signal);

       …

}

使用signal的另一个问题是,对于信号,进程要么忽略,要么捕获,无法在一段时间内阻塞信号(推迟信号的交付),为了克服signal兼容性问题,现代unix均实现了POSIX定义的sigaction函数,该函数采用一个sigaction结构,除定义信号交付时要采取的动作外,还包含其他一些动作控制信息。

sigaction还允许调用进程检测或指定与特定信号相关的动作。

int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);

sig指定信号-除SIGKILL和SIGSTOP。

如果act为NULL,则不改变信号动作,只查询当前动作。

成功返回0,失败则不安装新信号动作,返回-1,设置errno。

struct sigaction{

       void (*sa_handler)(int);                        // 同signal的第二参数func。          

       void (*sa_sigaction)(int, siginfo_t*, void*); // 仅当flags设置SA_SIGINFO起作用。

       sigset_t sa_mask;    // 指明信号执行期间要阻塞的一组信号,除此之外导致信号句柄执

行的信号也自动阻塞,除非指定了SA_NODEFER。信号句柄正常返回时,屏蔽恢复到原先状态。          

       int sa_flags;           //

};

sa_flags是一个位串,可以通过或运算生成。可设置下列标志:

SA_NOCLDSTOP: 只对CHLD信号起作用,子进程暂停时不发信号给父进程。

SA_RESTART: 信号句柄返回时,自动恢复被该信号中断的系统调用。否则该系统调用将中断返回-1,并设置errno为EINTR。

SA_ONSTACK:

SA_RESETHAND:信号句柄入口,系统将重置信号动作为SIG_DFL.

SA_NODEFER:句柄执行期间,不自动阻塞该信号。

SA_NOCLDWAIT:只对CHLD起作用,调用进程的所有子进程在终止时不会成为Zombe。这种情况下,父进程无需要wait子进程,并且子进程终止也不向父进程发SIGCHLD信号。

如果父进程调用wait,将阻塞到所有子进程终止,并返回-1,errno设为ECHILD.

SA_SIGINFO:如果未设置此标志,则信号句柄原型为:

 void func(int signo);

如果设置此标志,则句柄原型为:

void func(int signo, singinfo_t* info, void* context);

Info- 解释信号生成的原因。

Context-信号被交付时所中断进程的上下文。

一旦用sigaction为特定信号建立了动作,该动作就一直保持,直到另一次调用sigaction,或者调用exec,或者因设置了SA_RESETHAND导致系统自动改变动作为默认为止。

 

除了外部中断产生信号外,程序可以显式的调用raise函数给他自己发送信号,或调用kill向自己或其他进程发送信号。

 

阻塞信号意味着保持该信号并推迟它的交付,可以防止程序中的关键代码被信号中断。

信号集操作:

int sigemptyset(sigset_t* set); // 清空信号集

int sigfillset(sigset_t* set);   // 包含所有信号集

sigaddset/sigdelset/sigismember;

sigprocmask用来检测或改变调用进程的信号屏蔽。

int sigprocmask(int how, const sigset_t* set, sigset_t* oset);

How: SIGBLOCK SIG_UNBLOCK   SIG_SETMASK

如果调用sigprocmask放开某个信号而导致任何悬挂信号被解除阻塞,则函数返回前,这些信号中至少有一个被交付。

检查悬挂信号:int sigpending(sigset_t* set);

等待信号:int pause(void);

悬挂调用进程直到有一个信号到达。仅当句柄执行并返回时,pause函数才返回:此时返回-1,并设置errno为EINTR。所有其他情况下pause不返回。

 

如果多个相同信号在信号句柄运行前发给了进程,则句柄只被运行一次。换句话说,默认情况下unix信号是非排队的,只有当实现支持实时信号并且sa_flags设置SA_SIGINFO时,由sigqueue生成的后续信号才排队。

 

I/O执行期间,有可能到达信号,此时有两种情况:重新开始系统调用还是返回失败.

早期unix特征为,进程执行慢系统调用期间捕获信号时,该调用被中断并设置errno为EINTR。现代unix增加了sa_flags选项SA_RESTART可对单个信号要求自动恢复被中断的系统调用。

原则如下:如果进程阻塞于慢系统调用,并且进程捕获信号且该信号句柄返回,系统调用可能返回EINTR。 虽然有些unix能自动恢复系统调用,但是为了兼容性,我们必须准备慢系统调用返回EINTR,当检测到EINTR,要么重新开始系统调用,要么做其他处理。

ThinkPHP使用Paypal支付接口的模块开发

<?php
/**
 * 
 * PaypalAction.class.php
 * 
 * ThinkPHP Paypal支付接口模块
 * 
**/


class PaymentAction extends Action { 

/** 
* 自己的paypal账号 
*/ 
private $account = 'admin@gmail.com';

/** 
* paypal支付网关地址 
*/ 
private $gateway = 'https://www.paypal.com/cgi-bin/webscr?'; 

public function index() { 
	echo '<a href="'.U('Payment/order').'">Payment this order.</a>'; 
} 

/** 
  * 生成订单并跳转到Paypal进行支付 
  */ 
public function order() { 
	/** 
	* 自己的逻辑代码 
	* 判断是否登录、购买的哪个商品、购物车等等逻辑 
	* 当然可以调用Model更简单点 
	* 这里不在赘述 
	*/ 
	/** ...... **/ 

	$order_info	= array();	// 初始化订单数据 
	$order_id	= uniqid();	// 按照自己的规则生成订单号入库 

	/** 
	* 订单包含哪几种商品、谁买的、什么时间、几件 
	*/ 
	$order_info['order_id']		= $order_id; 
	$order_info['member_id']	= Session::get('member_id'); 
	/** .... **/ 

	$order = D('Order'); 

	if( $order->createOrder($order_info) ) { 
		unset($order,$order_info); 
		// 初始化准备提交到Paypal的数据 
		$pp_info = array();
		// 告诉Paypal,我的网站是用的我自己的购物车系统 
		$pp_info['cmd']			= '_xclick';
		// 告诉paypal,我的(商城的商户)Paypal账号,就是这钱是付给谁的 
		$pp_info['business']	= $this->account;
		// 用户将会在Paypal的支付页面看到购买的是什么东西,只做显示,没有什么特殊用途,
		// 如果是多件商品,则直接告诉用户,只支付某个订单就可以了 
		$pp_info['item_name']	= "支付订单:{$order_id}";
		$pp_info['amount']		= '13'; // 告诉Paypal,我要收多少钱 
		// 告诉Paypal,我要用什么货币。这里需要注意的是,由于汇率问题,
		// 如果网站提供了更改货币的功能,那么上面的amount也要做适当更改,
		// paypal是不会智能的根据汇率更改总额的 
		$pp_info['currency_code']	= 'USD';
		// 当用户成功付款后paypal会将用户自动引导到此页面。
		// 如果为空或不传递该参数,则不会跳转 
		$pp_info['return']		= 'http#//www.sjolzy.cn/index.php/payment/finish';	
		$pp_info['invoice']	= $order_id; 
		$pp_info['charset']	= 'utf-8'; 
		$pp_info['no_shipping']	= '1'; 
		$pp_info['no_note']		= '1'; 
		// 当跳转到paypal付款页面时,用户又突然不想买了。则会跳转到此页面 
		$pp_info['cancel_return']	= 'http#//www.sjolzy.com/index.php/product/view/pid/3';	
		// Paypal会将指定 invoice 的订单的状态定时发送到此URL 
		// (Paypal的此操作,是paypal的服务器和我方商城的服务器点对点的通信,用户感觉不到)
		$pp_info['notify_url']	= 'http#//www.sjolzy.com/index.php/payment/notify/orderid/'.$order_id; 
		$pp_info['rm']	= '2'; 
		$paypal_payment_url = $this->gateway.http_build_query($pp_info); 
		echo "<a href='{$paypal_payment_url}'>Go Paypal!</a>"; 
		unset($pp_info); 
	} else { 
		$this->error(L('order_create_fail')); 
	} 
} 

public function finish() { 
	$this->success('购买成功'); 
} 

public function notify() { 
	// 由于这个文件只有被Paypal的服务器访问,所以无需考虑做什么页面什么的,
	// 这个页面不是给人看的,是给机器看的 
	$order_id = (int) $_GET['orderid']; 
	$order_info = D('Order')->getOrderDetail($order_id); 

	// 由于该URL不仅仅只有Paypal的服务器能访问,其他任何服务器都可以向该方法发起请求。
	// 所以要判断请求发起的合法性,也就是要判断请求是否是paypal官方服务器发起的 

	// 拼凑 post 请求数据 
	$req = 'cmd=_notify-validate';// 验证请求 
	foreach ($_POST as $k=>$v){ 
		$v = urlencode(stripslashes($v)); 
		$req .= "&{$k}={$v}"; 
	} 

	$ch = curl_init(); 
	curl_setopt($ch,CURLOPT_URL,'http://www.paypal.com/cgi-bin/webscr'); 
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); 
	curl_setopt($ch,CURLOPT_POST,1); 
	curl_setopt($ch,CURLOPT_POSTFIELDS,$req); 
	$res = curl_exec($ch); 
	curl_close($ch); 

	if( $res && !emptyempty($order_info) ) { 
		// 本次请求是否由Paypal官方的服务器发出的请求 
		if(strcmp($res, 'VERIFIED') == 0) { 
			/** 
			* 判断订单的状态 
			* 判断订单的收款人 
			* 判断订单金额 
			* 判断货币类型 
			*/ 
			if(($_POST['payment_status'] != 'Completed' && $_POST['payment_status'] != 'Pending')
			 OR ($_POST['receiver_email'] != $this->account)
			  OR ($_POST['mc_gross'] != 13)
			   OR ('USD' != $_POST['mc_currency'])) { 
			// 如果有任意一项成立,则终止执行。由于是给机器看的,所以不用考虑什么页面。直接输出即可 
				exit('fail'); 
			} else {// 如果验证通过,则证明本次请求是合法的 
				D('Order')->finishOrder($order_id);// 更改订单状态 
				exit('success'); 
			} 
		} else { 
			exit('fail'); 
		} 
	} 
} 
} 

?>

更多Paypal参数的使用参考:PayPal支付接口的PHP开发方式

[转]PHP函数的实现原理及性能分析

 

前言

 

在任何语言中,函数都是最基本的组成单元。对于php的函数,它具有哪些特点?函数调用是怎么实现的?php函数的性能如何,有什么使用建议?本文将从原理出发进行分析结合实际的性能测试尝试对这些问题进行回答,在了解实现的同时更好的编写php程序。同时也会对一些常见的php函数进行介绍。

php函数的分类

 

在php中,横向划分的话,函数分为两大类: user function(内置函数) 和internal function(内置函数)。前者就是用户在程序中自定义的一些函数和方法,后者则是php本身提供的各类库函数(比如sprintf、array_push等)。用户也可以通过扩展的方法来编写库函数,这个将在后面介绍。对于user function,又可以细分为function(函数)和method(类方法),本文中将就这三种函数分别进行分析和测试。

php函数的实现

 

一个php函数最终是如何执行,这个流程是怎么样的呢?

要回答这个问题,我们先来看看php代码的执行所经过的流程。

从图1可以看到,php实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。Php本身是用c实现的,因此最终调用的也都是c的函数,实际上,我们可以把php看做是一个c开发的软件。
通过上面描述不难看出,php中函数的执行也是被翻译成了opcodes来调用,每次函数调用实际上是执行了一条或多条指令。

对于每一个函数,zend都通过以下的数据结构来描述

typedef union _zend_function {
zend_uchar type; /* MUST be the first element of this struct! */
struct {
zend_uchar type; /* never used */
char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
} common;

zend_op_array op_array;
zend_internal_function internal_function;
} zend_function;


typedef struct _zend_function_state {
HashTable *function_symbol_table;
zend_function *function;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_function_state;
其中type标明了函数的类型:用户函数、内置函数、重载函数。Common中包含函数的基本信息,包括函数名,参数信息,函数标志(普通函数、静态方法、抽象方法)等内容。另外,对于用户函数,还有一个函数符号表,记录了内部变量等,这个将在后面详述。 Zend维护了一个全局function_table,这是一个大的hahs表。函数调用的时候会首先根据函数名从表中找到对应的zend_function。当进行函数调用时候,虚拟机会根据type的不同决定调用方法, 不同类型的函数,其执行原理是不相同的




内置函数

内置函数,其本质上就是真正的c函数,每一个内置函数,php在最终编译后都会展开成为一个名叫zif_xxxx的function,比如我们常见的sprintf,对应到底层就是zif_sprintf。Zend在执行的时候,如果发现是内置函数,则只是简单的做一个转发操作。

Zend提供了一系列的api供调用,包括参数获取、数组操作、内存分配等。内置函数的参数获取,通过zend_parse_parameters方法来实现,对于数组、字符串等参数,zend实现的是浅拷贝,因此这个效率是很高的。可以这样说,对于php内置函数,其效率和相应c函数几乎相同,唯一多了一次转发调用。

内置函数在php中都是通过so的方式进行动态加载,用户也可以根据需要自己编写相应的so,也就是我们常说的扩展。ZEND提供了一系列的api供扩展使用

用户函数

和内置函数相比,用户通过php实现的自定义函数具有完全不同的执行过程和实现原理。如前文所述,我们知道php代码是被翻译成为了一条条opcode来执行的,用户函数也不例外,实际中每个函数对应到一组opcode,这组指令被保存在zend_function中。于是,用户函数的调用最终就是对应到一组opcodes的执行。

  • 局部变量的保存及递归的实现 
    我们知道,函数递归是通过堆栈来完成的。在php中,也是利用类似的方法来实现。Zend为每个php函数分配了一个活动符号表(active_sym_table),记录当前函数中所有局部变量的状态。所有的符号表通过堆栈的形式来维护,每当有函数调用的时候,分配一个新的符号表并入栈。当调用结束后当前符号表出栈。由此实现了状态的保存和递归。

对于栈的维护,zend在这里做了优化。预先分配一个长度为N的静态数组来模拟堆栈,这种通过静态数组来模拟动态数据结构的手法在我们自己的程序中也经常有使用,这种方式避免了每次调用带来的内存分配、销毁。ZEND只是在函数调用结束时将当前栈顶的符号表数据clean掉即可。
因为静态数组长度为N,一旦函数调用层次超过N,程序不会出现栈溢出,这种情况下zend就会进行符号表的分配、销毁,因此会导致性能下降很多。在zend里面,N目前取值是32。因此,我们编写php程序的时候,函数调用层次最好不要超过32。当然,如果是web应用,本身可以函数调用层次的深度。

  • 参数的传递 
    和内置函数调用zend_parse_params来获取参数不同,用户函数中参数的获取是通过指令来完成的。函数有几个参数就对应几条指令。具体到实现上就是普通的变量赋值。
    通过上面的分析可以看出,和内置函数相比,由于是自己维护堆栈表,而且每条指令的执行也是一个c函数,用户函数的性能相对会差很多,后面会有具体的对比分析。因此,如果一个功能有对应php内置函数实现的尽量不要自己重新写函数去实现。

类方法

类方法其执行原理和用户函数是相同的,也是翻译成opcodes顺次调用。类的实现,zend用一个数据结构zend_class_entry来实现,里面保存了类相关的一些基本信息。这个entry是在php编译的时候就已经处理完成。

在zend_function的common中,有一个成员叫做scope,其指向的就是当前方法对应类的zend_class_entry。关于php中面向对象的实现,这里就不在做更详细的介绍,今后将专门写一篇文章来详述php中面向对象的实现原理。就函数这一块来说,method实现原理和function完全相同,理论上其性能也差不多,后面我们将做详细的性能对比。

 

性能对比

 

函数名长度对性能的影响

  • 测试方法 
    对名字长度为1、2、4、8、16的函数进行比较,测试比较它们每秒可执行次数,确定函数名长度对性能的影响
  • 测试结果如下图

  • 结果分析 
    从图上可以看出,函数名的长度对性能还是会有一定的影响。一个长度为1的函数和长度为16的 空函数调用
     ,其性能差了1倍。分析一下源码不难找到原因,如前面叙述所说,函数调用的时候zend会先在一个全局的funtion_table中通过函数名查询相关信息,function_table是一个哈希表。必然的,名字越长查询所需要的时间就越多。 因此,在实际编写程序的时候,对多次调用的函数,名字建议不要太长

虽然函数名长度对性能有一定影响,但具体有多大呢?这个问题应该还是需要结合实际情况来考虑,如果一个函数本身比较复杂的话,那么对整体的性能影响并不大。
一个建议是对于那些会调用很多次,本身功能又比较简单的函数,可以适当取一些言简意赅的名字。

函数个数对性能的影响

  • 测试方法 
    在以下三种环境下进行函数调用测试,分析结果:1.程序仅包含1个函数 2.程序包含100个函数 3.程序包含1000个函数。
    测试这三种情况下每秒所能调用的函数次数
  • 测试结果如下图

  • 结果分析 
    从测试结果可以看出,这三种情况下性能几乎相同,函数个数增加时性能下降微乎其微,可以忽略。
    从实现原理分析,几种实现下唯一的区别在于函数获取的部分。如前文所述,所有的函数都放在一个hash表中,在不同个数下查找效率都应该还是接近于O(1),所以性能差距不大。

不同类型函数调用消耗

  • 测试方法 
    选取用户函数、类方法、静态方法、内置函数各一种,函数本身不做任何事情,直接返回,主要测试空函数调用的消耗。测试结果为每秒可执行次数 
    测试中为去除其他影响,所有函数名字长度相同
  • 测试结果如下图

  • 结果分析 
    通过测试结果可以看到,对于用户自己编写的php函数,不管是哪种类型,其效率是差不多的,均在280w/s左右。如我们预期,即使是空调,内置函数其效率也要高很多,达到780w/s,是前者是3倍。可见,内置函数调用的开销还是远低于用户函数。从前面原理分析可知主要差距在于用户函数调用时初始化符号表、接收参数等操作。

内置函数和用户函数性能对比

  • 测试方法 
    内置函数和用户函数的性能对比,这里我们选取几个常用的函数,然后用php实现相同功能的函数进行一下性能对比。
    测试中,我们选取字符串、数学、数组中各一个典型进行对比,这几个函数分别是字符串截取(substr)、10进制转2进制(decbin)、求最小值(min)和返回数组中的所以key(array_keys)。
  • 测试结果如下图 
     

 

  • 结果分析 
    从测试结果可以看出,如我们预期,内置函数在总体性能上远高于普通用户函数。尤其对于涉及到字符串类操作的函数,差距达到了1个数量级。因此,函数使用的一个原则就是如果某功能有相应的内置函数,尽量使用它而不是自己编写php函数。
    对于一些涉及到大量字符串操作的功能,为提高性能,可以考虑用扩展来实现。比如常见的富文本过滤等。

和C函数性能对比

  • 测试方法 
    我们选取字符串操作和算术运算各3种函数进行比对,php用扩展实现。三种函数是简单的一次算法运算、字符串比较和多次的算法运算。
    除了本身的两类函数外,还会测试将函数空调开销去掉后的性能,一方面比对一下两种函数(c和php内置)本身的性能差异,另外就是侧面印证空调函数的消耗 
    测试点为执行10w次操作的时间消耗
  • 测试结果如下图

  • 结果分析 
    内置函数和C函数的开销在去掉php函数空调用的影响后差距较小,随着函数功能越来越复杂,双方性能趋近于相同。这个从之前的函数实现分析中也容易得到论证,毕竟内置函数就是C实现的。
    函数功能越复杂,c和php的性能差距越小 
    相对c来说,php函数调用的开销大很多,对于简单函数来说性能还是有一定影响。因此php中函数不宜嵌套封装太深。

伪函数及其性能

 

在php中,有这样一些函数,它们在使用上是标准的函数用法,但底层实现却和真正函数调用完全不同,这些函数不属于前文提到的三种function中的任何一类,其实质是一条单独的opcode,这里估且叫做伪函数或者指令函数。

如上所说,伪函数使用起来和标准的函数并无二致,看起来具有相同的特征。但是他们最终执行的时候是被zend反映成了一条对应的指令(opcode)来调用,因此其实现更接近于if、for、算术运算等操作。

  • php中的伪函数 
    isset 
    empty 
    unset 
    eval

通过上面的介绍可以看出,伪函数由于被直接翻译成指令来执行,和普通函数相比少了一次函数调用所带来的开销,因此性能会更好一些。我们通过如下测试来做一个对比。 Array_key_exists和isset两者都可以判断数组中某个key是否存在,看一下他们的性能

从图上可以看出,和array_key_exists相比,isset性能要高出很多,基本是前者的4倍左右,而即使是和空函数调用相比,其性能也要高出1倍左右。由此也侧面印证再次说明了php函数调用的开销还是比较大的。

常用php函数实现及介绍

 

count

count是我们经常用到的一个函数,其功能是返回一个数组的长度。

count这个函数,其复杂度是多少呢? 
一种常见的说法是count函数会遍历整个数组然后求出元素个数,因此复杂度是O(n)。那实际情况是不是这样呢?
我们回到count的实现来看一下,通过源码可以发现,对于数组的count操作,函数最终的路径是zif_count-> php_count_recursive-> zend_hash_num_elements,而zend_hash_num_elements的行为是 return ht->nNumOfElements,可见,这是一个O(1)而不是O(n)的操作。实际上,数组在php底层就是一个hash_table,对于hash表,zend中专门有一个元素nNumOfElements记录了当前元素的个数,因此对于一般的count实际上直接就返回了这个值。由此,我们得出结论: count是O(1)的复杂度,和具体数组的大小无关。

非数组类型的变量,count的行为时怎样?
对于未设置变量返回0,而像int、double、string等则会返回1


strlen

Strlen用于返回一个字符串的长度。那么,他的实现原理是如何的呢?
我们都知道在c中strlen是一个o(n)的函数,会顺序遍历字符串直到遇到\0,然后出长度。Php中是否也这样呢?答案是否定的,php里字符串是用一个复合结构来描述,包括指向具体数据的指针和字符串长度(和c++中string类似),因此strlen就直接返回字符串长度了,是常数级别的操作。
另外,对于非字符串类型的变量调用strlen,它会首先将变量强制转换为字符串再求长度,这点需要注意。


isset和array_key_exists

这两个函数最常见的用法都是判断一个key是否在数组中存在。但是前者还可以用于判断一个变量是否被设置过。
如前文所述,isset并非真正的函数,因此它的效率会比后者高很多。推荐用它代替array_key_exists。


array_push和array[]

两者都是往数组尾部追加一个元素。不同的是前者可以一次push多个。他们最大的区别在于一个是函数一个是语言结构,因此后者效率要更高。因此如果只是普通的追加元素,建议使用array []。


rand和mt_rand

两者都是提供产生随机数的功能,前者使用libc标准的rand。后者用了 Mersenne Twister 中已知的特性作为随机数发生器,它可以产生随机数值的平均速度比 libc 提供的 rand() 快四倍。因此如果对性能要求较高,可以考虑用mt_rand代替前者。
我们都知道,rand产生的是伪随机数,在C中需要用srand显示指定种子。但是在php中,rand会自己帮你默认调用一次srand,一般情况下不需要自己再显示的调用。
需要注意的是,如果特殊情况下需要调用srand时,一定要配套调用。就是说srand对于rand,mt_srand对应srand,切不可混合使用,否则是无效的。


sort和usort

两者都是用于排序,不同的是前者可以指定排序策略,类似我们C里面的qsort和C++的sort。
在排序上两者都是采用标准的快排来实现,对于有排序需求的,如非特殊情况调用php提供的这些方法就可以了,不用自己重新实现一遍,效率会低很多。原因见前文对于用户函数和内置函数的分析比对。


urlencode和rawurlencode

这两个都是用于url编码, 字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。两者唯一的区别在于对于空格,urlencode会编码为+,而rawurlencode会编码为%20。
一般情况下除了搜索引擎,我们的策略都是空格编码为%20。因此采用后者的居多。
注意的是encode和decode系列一定要配套使用。


strcmp系列函数

这一系列的函数包括strcmp、strncmp、strcasecmp、strncasecmp,实现功能和C函数相同。但也有不同,由于php的字符串是允许\0出现,因此在判断的时候底层使用的是memcmp系列而非strcmp,理论上来说更快。
另外由于php直接能获取到字符串长度,因此会首先这方面的检查,很多情况下效率就会高很多了。


is_int和is_numeric

这两个函数功能相似又不完全相同,使用的时候一定需要注意他们的区别。
Is_int:判断一个变量类型是否是整数型,php变量中专门有一个字段表征类型,因此直接判断这个类型即可,是一个绝对O(1)的操作 
Is_numeric:判断一个变量是否是整数或数字字符串,也就是说除了整数型变量会返回true之外,对于字符串变量,如果形如”1234”,”1e4”等也会被判为true。这个时候会遍历字符串进行判断。


总结及建议

 

通过对函数实现的原理分析和性能测试,我们总结出以下一些结论

1. Php的函数调用开销相对较大。

2. 函数相关信息保存在一个大的hash_table中,每次调用时通过函数名在hash表中查找,因此函数名长度对性能也有一定影响。

3. 函数返回引用没有实际意义

4. 内置php函数性能比用户函数高很多,尤其对于字符串类操作。

5. 类方法、普通函数、静态方法效率几乎相同,没有太大差异

6. 除去空函数调用的影响,内置函数和同样功能的C函数性能基本差不多。

7. 所有的参数传递都是采用引用计数的浅拷贝,代价很小。

8. 函数个数对性能影响几乎可以忽略

因此,对于php函数的使用,有如下一些建议

1. 一个功能可以用内置函数完成,尽量使用它而不是自己编写php函数。

2. 如果某个功能对性能要求很高,可以考虑用扩展来实现。

3. Php函数调用开销较大,因此不要过分封装。有些功能,如果需要调用的次数很多本身又只用1、2行代码就行实现的,建议就不要封装调用了。

4. 不要过分迷恋各种设计模式,如上一条描述,过分的封装会带来性能的下降。需要考虑两者的权衡。Php有自己的特点,切不可东施效颦,过分效仿java的模式。

5. 函数不宜嵌套过深,递归使用要谨慎。

6. 伪函数性能很高,同等功能实现下优先考虑。比如用isset代替array_key_exists

7. 函数返回引用没有太大意义,也起不到实际作用,建议不予考虑。

8. 类成员方法效率不比普通函数低,因此不用担心性能损耗。建议多考虑静态方法,可读性及安全性都更好。

9. 如不是特殊需要,参数传递都建议使用传值而不是传引用。当然,如果参数是很大的数组且需要修改时可以考虑引用传递。