Apache Hook机制解析(中)——细节讨论
本文在上文《Apache Hook机制解析(上)——钩子机制的实现》的基础上,对钩子机制的细节了补充分析。1. 静态变量_hooks
这个变量由宏APR_HOOK_STRUCT所定义,因为是静态的,所以对这个变量的操作只能在同一源文件中实现——也对应的AP_IMPLEMENT_HOOK_RUN_ALL宏必须在APR_HOOK_STRUCT所在的源文件中被定义。
2. AP__开头的宏和APR__开头的宏
APR指Apache Portable Runtime,它是一个基本的库,可以用于Apache HTTP Server,也可用于其他产品;而AP实际上指Apache HTTP Server。
因此AP__开头的宏很多都是对APR__宏的进一层封装,将其封装到Apache HTTP Server的命名空间,如AP_IMPLEMENT_HOOK_RUN_ALL实际上是APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL宏的封装:
#define AP_IMPLEMENT_HOOK_RUN_ALL(ret,name,args_decl,args_use,ok,decline) /
APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap,AP,ret,name,args_decl, /
args_use,ok,decline)
如果是要在自己写的代码中调用APR库,则必须使用APR__开头的宏。
3. AP_IMPLEMENT_HOOK_RUN_ALL的变种
在apr_hooks.h中的代码中可以看到,除了AP_IMPLEMENT_HOOK_RUN_ALL宏之外,还有两个用于定义钩子的宏:
APR_IMPLEMENT_EXTERNAL_HOOK_VOID
AP_IMPLEMENT_HOOK_RUN_FIRST
第一个宏是为了无参数的钩子函数准备的,对应前面log_transaction的例子,该宏展开后的触发函数代码如下:
AP_DECALRE(void) ap_run_log_transaction (request_rec *r)
{
ap_AP_log_transaction_t *pHook;
int n;
if(!_hooks.link_log_transaction)
return ok;
pHook=(ap_AP_log_transaction_t *)_hooks.link_log_transaction->elts;
for(n=0 ; n < _hooks.link_log_transaction->nelts ; ++n)
pHook.pFunc (r);
}
第二个宏用于执行条件触发——如果某些钩子实现返回了DECLINE(拒绝),则停止执行后续的钩子函数。对应前面log_transaction的例子,该宏展开后的触发函数代码如下:
AP_DECALRE(int) ap_run_log_transaction (request_rec *r)
{
ap_AP_log_transaction_t *pHook;
int n;
ret rv;
if(!_hooks.link_log_transaction)
return decline;
pHook=(ap_AP_log_transaction_t *)_hooks.link_log_transaction->elts;
for(n=0 ; n < _hooks.link_log_transaction->nelts ; ++n)
{
rv=pHook.pFunc (r);
if(rv != decline)
return rv;
}
return decline;
}
这里的实现和AP_IMPLEMENT_HOOK_RUN_ALL 不同主要在于:
1) 默认的返回是decline,而不是ok
2) 只要钩子函数返回decline,则终止循环
4. ap_hook_get_log_transaction
进行钩子的声明和定义时,除了ap_hook_ log_transaction和ap_run_ log_transaction,还有一个函数——ap_hook_get_log_transaction。
这个函数的用处只是提供给外界一个访问log_transaction相关的数据结构的方法。在整个Apache代码中,ap_hook_get_log_transaction只被引用到了一次——在mod_info中,用于遍历log_transaction的钩子函数数组,进行查找。
5. ap_hook_log_transaction的其他参数
AP_DECLARE(void) ap_hook_log_transaction(ap_HOOK_log_transaction_t *pf,
const char *const *aszPre,
const char * const *aszSucc, int nOrder)
以上函数原型中,pf为实际的钩子函数的指针,而其他三个参数主要用于钩子的排序(见下一小节),其意义如下:
aszPre此钩子的前驱节点,也即此钩子必须排在哪个钩子之后
aszSucc 此钩子的后驱节点,也即此钩子必须排在哪个钩子之前
nOrder此钩子顺序值(也可以视作排序的优先级)
6. 钩子的排序
不同的模块可能对同一钩子挂载多个钩子函数,如何决定这些钩子函数被调用的先后顺序呢。
通过对apr_hooks.c中排序相关的代码分析,钩子函数的排序主要由以下规则决定:
1) Order——钩子函数排序时,首先会用快速排序法对钩子函数的数组进行一次排序,qsort使用的比较函数是crude_order:
static int crude_order(const void *a_,const void *b_)
{
const TSortData *a=a_;
const TSortData *b=b_;
return a->nOrder-b->nOrder;
}
也即Order越小的钩子函数排在越前面。系统定义的Order有这几种:
/** run this hook first, before ANYTHING */
#define APR_HOOK_REALLY_FIRST (-10)
/** run this hook first */
#define APR_HOOK_FIRST0
/** run this hook somewhere */
#define APR_HOOK_MIDDLE10
/** run this hook after every other hook which is defined*/
#define APR_HOOK_LAST20
/** run this hook last, after EVERYTHING */
#define APR_HOOK_REALLY_LAST 30
2) 前驱和后驱——完成基于Order的排序后,还会根据钩子函数相关的前驱(Predecessors)和后驱(Successors)进行调整。此算法的流程如下:
a) 通过遍历钩子数组,计算每个钩子有多少个前驱(见apr_hooks.c中的prepare函数)
b) 初始化一个链表
c) 根据每个钩子的前驱数量,确定其在链表中的位置(见apr_hooks.c中的tsort函数)
d) 将钩子插入链表
e) 遍历链表,生成一个新的钩子数组
此算法的流程稍显复杂,在Apache中也使用很少。大多数地方都只使用Order来决定钩子的顺序。
此外,APR提供了一个单独的函数apr_hook_sort_all来对系统内所有的Hook进行排序。这带来了一个问题:_hooks是静态函数,那apr_hook_sort_all是怎么获得系统内的所有Hooks呢?
解答就在钩子的挂载函数中——注意ap_hook_log_transaction中的这几句代码:
if(!_hooks.link_log_transaction)
{
_hooks.link_log_transaction=apr_array_make(apr_hook_global_pool,
1,
sizeof(ap_AP_log_transaction_t));
apr_hook_sort_register(log_transaction,&_hooks.link_log_transaction);
}
在创建一个新的钩子数组之后,调用了apr_hook_sort_register函数,此函数将新建的钩子数组注册到一个全局表中去。这样apr_hook_sort_all就可以对所有的钩子数组进行排序了。
页:
[1]