2008-04-25

Apache中的Hook(挂钩)剖析(3)

Posted in Apache at 18:10 Author:仲远

标签:

5.5.7 可选挂钩

与标准挂钩相比,可选挂钩基本上没有太大的差异,唯一的区别就在于可选挂钩不一定需要被实现——这看起来令人迷惑的。不过你很快就会明白了。考虑一下,如果某个挂钩Hook_A是声明在一个可选模块中,那么正常情况下该模块没有被加载。如此此时某个模块想使用挂钩Hook_A,那么会发生什么情况呢。对于标准模块,Apache可能根本就无法进行编译。而可选挂钩则可以解决这种问题。对于可选挂钩,即使它没有被导入并运行,其余的模块也可以使用它。

可选挂钩的声明方法与标准挂钩声明没有任何区别,都是通过AP_DECLARE_HOOK进行的,比如下面的语句声明一个可选挂钩:

AP_DECLARE_HOOK(int , optional_hook , (request_rec *r , int n))

与标准挂钩相比,可选挂钩没有内部私有的数据结构。在标准挂钩中,为了保存各个模块对声明的挂钩的使用情况,通过声明AP_HOOK_STRUCT结构来实现。这种做法实际上是由挂钩实现者自行进行维护;而对于可选挂钩,模块编写者可以不需要维护该AP_HOOK_STRUCT结构了,该结构则转交内核维护。

在实现上,可选挂钩的声明从标准挂钩的AP_IMPLEMENT_HOOK_RUN_ALL形式转变为AP_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL

5.5.7.1可选挂钩数组

在Apache2.0中,任何模块对可选挂钩的调用信息都由Apache核心进行维护。在Apache内部,核心定义了两个全局哈希表s_phOptionalHooks和s_phOptionalFunctions分别保存所有的可选挂钩以及对应的挂钩处理句柄。S_phOptionalHooks哈希表中,可选挂钩名称用来作为哈希表Key键,而挂钩对应的挂钩数组则作为哈希表的Value值。其结构如上图所示。在Apr_hooks.c中,Apache提供了两个支持函数:apr_optional_hook_get和apr_optional_hook_add。

apr_optional_hook_get函数用来在哈希表s_phOptionalHooks中查找指定挂钩的挂钩数组,如果找到了则返回数组;否则返回NULL。

apr_optional_hook_add函数的原型声明如下:

APU_DECLARE(void) apr_optional_hook_add(const char *szName,void (*pfn)(void),

                    const char * const *aszPre,

                    const char * const *aszSucc,int nOrder)

该函数主要在可选挂钩szName数组中,增加一个登记项,登记的挂钩函数为pfn。同时aszPre、aszSucc以及nOrder与标准挂钩的含义相同。

在登记之前,函数必须能够在哈希表中找到挂钩szName对应的挂钩数组,这个可以通过apr_optional_hook_get来完成。由于可选挂钩可以没有任何挂钩函数,因此上图中挂钩数组为NULL也是可能的,此时必须为该挂钩首先生成挂钩数组,将该挂钩数组在哈希表中与键szName关联起来,同时进行排序。

如果szName挂钩数组已经存在,则直接调用apr_array_push相关信息压入数组并赋值。

可选挂钩数组中每个元素的结构都是apr_LINK__optional_t类型,其是宏APR_DECLARE_EXTERNAL_HOOK展开的结果,apr_LINK__optional_t结构实际如下所示:

typedef struct ap_LINK_optional_t

    {

         ap_HOOK_optional_t *pFunc;

         const char *szName;

         const char * const *aszPredecessors;

         const char * const *aszSuccessors;

         int nOrder;

    } ap_LINK_optional_t;

5.5.7.2 声明可选挂钩(APR_OPTIONAL_HOOK)

对于标准挂钩,注册使用挂钩通常使用ap_hook_name之类的函数,这些函数最终将使用信息登记到挂钩数组中去。而对于可选挂钩,由于不存在AP_HOOK_STRUCT宏,因此也就不存在挂钩数组了。在前面我们提到过,可选挂钩的保存是由Apache内核维护的,我们展开宏APR_OPTIONAL_HOOK就知道了。

APR_OPTIONAL_HOOK宏定义在ap_optional_hooks.h中:

#define APR_OPTIONAL_HOOK(ns,name,pfn,aszPre,aszSucc,nOrder) do { \

 ns##_HOOK_##name##_t *apu__hook = pfn; \

 apr_optional_hook_add(#name,(void (*)(void))apu__hook,aszPre, aszSucc, nOrder); \

} while (0)

5.5.7.3 APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL

对于标准挂钩,其实现分为VOID、FIRST和ALL三种,而对于可选挂钩,实现则归结只有一种ALL类型,即APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL,该宏定义如下:

#define APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,args_use,ok,decline) \

link##_DECLARE(ret) ns##_run_##name args_decl \

    { \

    ns##_LINK_##name##_t *pHook; \

    int n; \

    ret rv; \

    apr_array_header_t *pHookArray=apr_optional_hook_get(#name); \

    if(!pHookArray) \

     return ok; \

    pHook=(ns##_LINK_##name##_t *)pHookArray->elts; \

    for(n=0 ; n < pHookArray->nelts ; ++n) \

     { \

     rv=(pHook[n].pFunc)args_use; \

\

     if(rv != ok && rv != decline) \

         return rv; \

     } \

    return ok; \

    }

status模块mod_status.c中我们声明的挂钩status_hook就是一个可选挂钩,该挂钩实现如下:

APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(ap, STATUS, int, status_hook,

                                    (request_rec *r, int flags),

                                    (r, flags),

                                    OK, DECLINED)

     该宏展开后即实现了ap_run_status_hook函数,其实现过程,即展开结果如下所示:

     AP_DECLARE(int) ap_run_status_hook(request_rec* r,int flags)

     {

         ap_LINK_status_hook_t *pHook;

         int n;

         int rv;

    apr_array_header_t *pHookArray=apr_optional_hook_get(status_name);

    if(!pHookArray)

return ok;

    pHook=(ap_LINK_status_hook_t *)pHookArray->elts;

    for(n=0 ; n < pHookArray->nelts ; ++n)

{

rv=(pHook[n].pFunc)(r,flags);

if(rv != ok && rv != decline)

    return rv;

}

    return ok;

     }

5.5.8 可选函数

与挂钩存在类似的问题,函数也可能存在可选挂钩的问题。如果Apache在调用某一个函数的时候,该函数尚未被加载,会发生什么呢。你可能觉得DSO是个好的解决方法。如果指定的函数没有,则动态加载DSO模块进行查找。不过这种策略并不但是最完美的方案。首先,不是所有的平台都支持DSO;另一方面,

可选函数的特点正如其名,这就决定了它相对于Apache的动态性。正常的函数都是编写之后才会加入Apache中进行编译,而且一旦编译就无法更改。而可选函数则是在需要的时候动态产生的。在产生之前,没有人为之进行过专门的编写,因此链接代码中自然也就找不到该函数的实现。

可选函数的使用可以分为五大步骤:声明、实现、注册、获取以及函数调用。

5.5.8.1.可选函数的声明

声明一个可选函数通过宏APR_DECLARE_OPTIONAL_FN来实现,比如我们如果想声明一个optional_fun可选函数,其返回int类型,参数需要字符串类型,那么声明可以如下:

APR_DECLARE_OPTIONAL_FN(int,optional_fun,(const char* params))

APR_DECLARE_OPTIONAL_FN宏定义如下:

 #define APR_DECLARE_OPTIONAL_FN(ret,name,args) \

typedef ret (APR_OPTIONAL_FN_TYPE(name)) args

如果将上面的宏展开,则可以看出,该宏只是声明了一个apr_OFN_optional_fun_t类型的函数指针:

typedef int (apr_OFN_optional_fun_t)(const char* params)

一旦声明完毕,我们则将其进行实现如下,在实现中必须注意名称以及函数参数类型的匹配:

int optional_fun(const char* params)

{

       ……

       return 0;

}

5.5.8.2.可选函数的注册

可选函数由Apache核心统一维护。与可选挂钩类似,Apache核心维护了一个全局的哈希表s_phOptionalFunctions,该哈希表的键为可选函数的名称,而值则是对应的函数指针。为了便于Apache使用,我们必须在s_phOptionalFunctions中登记可选函数。函数的注册通过APR_REGISTER_OPTIONAL_FN进行,APR_REGISTER_OPTIONAL_FN定义如下:

#define APR_REGISTER_OPTIONAL_FN(name) do { \

 APR_OPTIONAL_FN_TYPE(name) *apu__opt = name; \

 apr_dynamic_fn_register(#name,(apr_opt_fn_t *)apu__opt); \

} while(0)

该宏内部实际调用了函数apr_dynamic_fn_register进行实际的注册。事实上,Apache中提供了相关函数来支持对s_phOptionalFunctions的操作,除了apr_dynamic_fn_register之外,还包括apr_register_optional_fnapr_dynamic_fn_retrieveapr_retrieve_optional_fn。不过其中apr_retrieve_optional_fnapr_register_optional_fnApache2.0中已经被废弃,因此不再多说。

apr_dynamic_fn_register的原型如下:

APU_DECLARE_NONSTD(void) apr_dynamic_fn_register(const char *szName,

                                                  apr_opt_fn_t *pfn)

参数szName是可选函数的名称,pfn则是实际可选函数的指针。如果s_phOptionalFunctions哈希表存在,函数只是简单的调用apr_hash_set将记录插入表中。

apr_dynamic_fn_retrieve函数原型为

APU_DECLARE(apr_opt_fn_t *) apr_dynamic_fn_retrieve(const char *szName)

该函数根据给定的函数名称获取实际的函数指针。

事实上,这两个函数都不允许直接调用,它们作为Apache的内部函数而存在,对外提供的则是宏APR_REGISTER_OPTIONAL_FNAPR_RETRIEVE_OPTIONAL_FN

对于optional_fun可选函数,注册语句如下:

APR_REGISTER_OPTIONAL_FN(optional_fun);

当用户想使用可选函数的时候,首先必须获得其函数指针,用法如下:

APR_OPTIONAL_FN_TYPE(some_fn) *pfn;

pfn=APR_RETRIEVE_OPTIONAL_FN(some_fn);

5.5.8.3.可选函数的使用

5.5.9智能挂钩

5.5.10挂钩工作机制

在前面的部分,我们对挂钩进行了详细的分析,但是还缺乏一个整体上的概念。从整体上来看挂钩的工作机制可以用下图来描述:

一个模块从挂钩的角度来看,其最重要的无非是两个方面:挂钩注册函数和挂钩处理函数。挂钩注册函数通常是模块结构中的register_hooks函数指针,该函数指针将调用实际的挂钩注册函数进行挂钩注册。挂钩注册的过程很简单,通过宏ap_hook_xxx实现。比如上图中声明了两个挂钩abc和xyz。

与此同时,模块中也将声明与挂钩对应的挂钩函数,该挂钩被触发的时候,该函数将被调用。正如前面描述,可以使用宏ap_run_xxx触发指定的挂钩。不过挂钩的触发通常是由核心模块在对客户端请求进行处理的过程中进行。

我们来看一个具体的例子,这是核心模块中关于挂钩的部分。

从模块的结构中可以看出,模块结构中包含一个指针register_hook,该指针通常指向模块中的实际的挂钩注册函数,比如,对于核心模块而言,其挂钩注册函数register_hooks,那么结构中的该指针也为register_hooks:

AP_DECLARE_DATA module core_module = {

    STANDARD20_MODULE_STUFF,

    create_core_dir_config,       /* create per-directory config structure */

    merge_core_dir_configs,       /* merge per-directory config structures */

    create_core_server_config,    /* create per-server config structure */

    merge_core_server_configs,    /* merge per-server config structures */

    core_cmds,                    /* command apr_table_t */

    register_hooks                /* register hooks */

};

而具体的register_hooks函数则如下:

static void register_hooks(apr_pool_t *p)

{

    ap_hook_create_connection(core_create_conn, NULL, NULL,

                              APR_HOOK_REALLY_LAST);

    ap_hook_pre_connection(core_pre_connection, NULL, NULL,

                           APR_HOOK_REALLY_LAST);

    ap_hook_post_config(core_post_config,NULL,NULL,APR_HOOK_REALLY_FIRST);

    ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);

    ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);

    ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);

    ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);

    ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);

    ap_hook_fixups(core_override_type,NULL,NULL,APR_HOOK_REALLY_FIRST);

    ap_hook_access_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);

    ap_hook_create_request(core_create_req, NULL, NULL, APR_HOOK_MIDDLE);

    APR_OPTIONAL_HOOK(proxy, create_req, core_create_proxy_req, NULL, NULL,

                      APR_HOOK_MIDDLE);

    ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE);

……

}

该函数的任务非常的简单,即声明该模块需要实现的挂钩,以及对应的处理函数,供主程序调用。

本文可以自由转载,转载时请保留全文并注明出处:
转载自仲子说 [ http://www.wangzhongyuan.com/ ]
原文链接:

Leave a Comment

*
To prove you're a person (not a spam script), type the security text shown in the picture. Click here to regenerate some new text.
Click to hear an audio file of the anti-spam word