2010年03月03日

Apache 2.3/2.4系の新機能を見てみよう 番外編 〜イベントフックの実装〜

はてなブックマークに登録

さて、今回は番外編ということで、2.3/2.4系に限らず現行のバージョンも含めてApacheのイベントフックの実装を見ていきたいと思います。該当箇所を抜き出しながらのソースレビューとなるため文章よりもコード中心の内容となります。では、前回"mpm"イベントフックの定義ということでAP_DECLARE_HOOKマクロが出ましたが、これをもう一度見ましょう。

include/ap_mpm.h
    82  /**
    83   * Pass control to the MPM for steady-state processing.  It is responsible
    84   * for controlling the parent and child processes.  It will run until a
    85   * restart/shutdown is indicated.
    86   * @param pconf the configuration pool, reset before the config file is read
    87   * @param plog the log pool, reset after the config file is read
    88   * @param server_conf the global server config.
    89   * @return DONE for shutdown OK otherwise.
    90   */
    91  AP_DECLARE_HOOK(int, mpm, (apr_pool_t *pconf, apr_pool_t *plog, server_rec *server_conf))

あとはひたすらこのマクロを一つずつ展開して行くこととします。AP_DECLARE_HOOKマクロは更に以下のように展開されます。

include/ap_config.h
    138 /**
    139  * Declare a hook function
    140  * @param ret The return type of the hook
    141  * @param name The hook's name (as a literal)
    142  * @param args The arguments the hook function takes, in brackets.
    143  */
    144 #define AP_DECLARE_HOOK(ret,name,args) \
    145         APR_DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)
srclib/apr-util/include/apr_hooks.h
     41 /** macro to declare the hook correctly */    
     42 #define APR_DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \
     43 typedef ret ns##_HOOK_##name##_t args; \
     44 link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, \
     45                                       const char * const *aszPre, \
     46                                       const char * const *aszSucc, int nOrder); \
     47 link##_DECLARE(ret) ns##_run_##name args; \
     48 APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); \
     49 typedef struct ns##_LINK_##name##_t \
     50     { \
     51     ns##_HOOK_##name##_t *pFunc; \
     52     const char *szName; \
     53     const char * const *aszPredecessors; \
     54     const char * const *aszSuccessors; \
     55     int nOrder; \
     56     } ns##_LINK_##name##_t;
     57 

最終的にAPR_DECLARE_EXTERNAL_HOOKマクロに到達し、下のように展開されます。

typedef int ap_HOOK_mpm_t(apr_pool_t *pconf, apr_pool_t *plog, server_rec *server_conf);

void ap_hook_mpm(ap_HOOK_mpm_t *pf,
                 const char * const *aszPre,
                 const char * const *aszSucc, int nOrder);

int ap_run_mpm(apr_pool_t *pconf, apr_pool_t *plog, server_rec *server_conf);

apr_array_header_t * ap_hook_get_mpm(void);

typedef struct ap_LINK_mpm_t
{
    ap_HOOK_mpm_t      *pFunc;
    const char         *szName;
    const char * const *aszPredecessors;
    const char * const *aszSuccessors;
    int                 nOrder;
} ap_LINK_mpm_t;

前回の説明のように、マクロの展開によってap_hook_mpm()とap_run_mpm()が同時に定義されました。またイベントハンドラの型としてap_HOOK_mpm_t型が定義され、ap_hook_mpm()の第1引数として与えられてイベントハンドラがどのような引数、返値を持つべきかを規定します。

さらにイベントハンドラの登録情報構造体としてap_LINK_mpm_t型も定義されました。当該ハンドラの実行よりも先に完了していてほしい、逆に自らが完了した後に起動してほしいそれぞれモジュール名をaszPredecessors、aszSuccessorsとして持ち、またハンドラの起動優先順をREALLY_FIRST、FIRST、MIDDLE、LAST、REALLY_LASTの順でグループ分けするnOrderといったフィールドを持ちます。

srclib/apr-util/include/apr_hooks.h
    195     /* Hook orderings */
    196 /** run this hook first, before ANYTHING */
    197 #define APR_HOOK_REALLY_FIRST   (-10)
    198 /** run this hook first */
    199 #define APR_HOOK_FIRST          0
    200 /** run this hook somewhere */
    201 #define APR_HOOK_MIDDLE         10
    202 /** run this hook after every other hook which is defined*/
    203 #define APR_HOOK_LAST           20
    204 /** run this hook last, after EVERYTHING */
    205 #define APR_HOOK_REALLY_LAST    30

引き続き、こんどは関数の定義マクロを見ていきましょう。

server/mpm_common.c
    83  AP_IMPLEMENT_HOOK_RUN_ALL(int, monitor,
    84                            (apr_pool_t *p, server_rec *s), (p, s), OK, DECLINED)
    85  AP_IMPLEMENT_HOOK_RUN_ALL(int, drop_privileges,
    86                            (apr_pool_t * pchild, server_rec * s),
    87                            (pchild, s), OK, DECLINED)
    88  AP_IMPLEMENT_HOOK_RUN_FIRST(int, mpm,
    89                              (apr_pool_t *pconf, apr_pool_t *plog, server_rec *s),
    90                              (pconf, plog, s), DECLINED)

88行目にあるのが、今回注目点のmpmイベントフック関連の各関数実装用のマクロです。83、85行目にも似たようなマクロが見えますが、これはそれぞれどう違うのでしょうか。

include/ap_config.h
    187 #define AP_IMPLEMENT_HOOK_RUN_ALL(ret,name,args_decl,args_use,ok,decline) \
    188         APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap,AP,ret,name,args_decl, \
    189                                             args_use,ok,decline)
    ...
    207 #define AP_IMPLEMENT_HOOK_RUN_FIRST(ret,name,args_decl,args_use,decline) \
    208         APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap,AP,ret,name,args_decl, \
    209                                               args_use,decline)
srclib/apr-util/include/apr_hooks.h
     67 #define APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \
     68 link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf,const char * const *aszPre, \
     69                                       const char * const *aszSucc,int nOrder) \
     70     { \
     71     ns##_LINK_##name##_t *pHook; \
     72     if(!_hooks.link_##name) \
     73         { \
     74         _hooks.link_##name=apr_array_make(apr_hook_global_pool,1,sizeof(ns##_LINK_##name##_t)); \
     75         apr_hook_sort_register(#name,&_hooks.link_##name); \
     76         } \
     77     pHook=apr_array_push(_hooks.link_##name); \
     78     pHook->pFunc=pf; \
     79     pHook->aszPredecessors=aszPre; \
     80     pHook->aszSuccessors=aszSucc; \
     81     pHook->nOrder=nOrder; \
     82     pHook->szName=apr_hook_debug_current; \
     83     if(apr_hook_debug_enabled) \
     84         apr_hook_debug_show(#name,aszPre,aszSucc); \
     85     } \
     86     APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name) \
     87     { \
     88         return _hooks.link_##name; \
     89     }
    ...
    136 #define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,args_use,ok,decline) \
    137 APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \
    138 link##_DECLARE(ret) ns##_run_##name args_decl \
    139     { \
    140     ns##_LINK_##name##_t *pHook; \
    141     int n; \
    142     ret rv; \
    143 \
    144     if(!_hooks.link_##name) \
    145         return ok; \
    146 \
    147     pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; \
    148     for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \
    149         { \
    150         rv=pHook[n].pFunc args_use; \
    151 \
    152         if(rv != ok && rv != decline) \
    153             return rv; \
    154         } \
    155     return ok; \
    156     }
    157 
    ...
    173 #define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ns,link,ret,name,args_decl,args_use,decline) \
    174 APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \
    175 link##_DECLARE(ret) ns##_run_##name args_decl \
    176     { \
    177     ns##_LINK_##name##_t *pHook; \
    178     int n; \
    179     ret rv; \
    180 \
    181     if(!_hooks.link_##name) \
    182         return decline; \
    183 \
    184     pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; \
    185     for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \
    186         { \
    187         rv=pHook[n].pFunc args_use; \
    188 \
    189         if(rv != decline) \
    190             return rv; \
    191         } \
    192     return decline; \
    193     }

なるほど、今回で言えば ns##_run_##name = ap_run_mpm なので、イベントフックの起動関数の処理を若干違えているのみで、あとはAPR_IMPLEMENT_EXTERNAL_HOOK_BASEマクロによって共通ということですね。

というわけでまずはこの共通部分=APR_IMPLEMENT_EXTERNAL_HOOK_BASEマクロ部分を展開して実際のコードとした場合です。

void
ap_hook_mpm(ap_HOOK_mpm_t *pf,const char * const *aszPre,
            const char * const *aszSucc,int nOrder)
{
    ap_LINK_mpm_t *pHook;
    if (!_hooks.link_mpm) {
        _hooks.link_mpm = apr_array_make(apr_hook_global_pool, 1, sizeof(ap_LINK_mpm_t));
        apr_hook_sort_register("mpm", &_hooks.link_mpm);
    }
    pHook = apr_array_push(_hooks.link_mpm);
    pHook->pFunc           = pf;
    pHook->aszPredecessors = aszPre;
    pHook->aszSuccessors   = aszSucc;
    pHook->nOrder          = nOrder;
    pHook->szName          = apr_hook_debug_current;
    if (apr_hook_debug_enabled)
        apr_hook_debug_show("mpm", aszPre, aszSucc);
}

apr_array_header_t *
ap_hook_get_mpm(void)
{
    return _hooks.link_mpm;
}

ap_hook_mpm()関数がどのようにハンドラを登録するか、ということなわけですが、apr_array_push()を使ってひたすら追加して行くだけということになります。この時点では、ハンドラ間の優先順位設定(aszPredecessorsやaszSuccessors)は構造体に渡されるだけであとは何もありません。

次に、RUN_FIRST版のap_run_mpm()です。

int
ap_run_mpm(apr_pool_t *pconf, apr_pool_t *plog, server_rec *s)
{
    ap_LINK_mpm_t *pHook;
    int n;
    int rv;

    if (!_hooks.link_mpm)
        return DECLINED;

    pHook = (ap_LINK_mpm_t *)_hooks.link_mpm->elts;
    for (n = 0; n < _hooks.link_mpm->nelts; ++n) {
        rv = pHook[n].pFunc(pconf, plog, s);

        if (rv != DECLINED)
            return rv;
    }
    return DECLINED;
}

登録されたイベントハンドラを先頭からループしてなめていって、最初にDECLINED以外を返したハンドラがいた時点でループを止めて関数から帰ります。一方仮にRUN_ALLだった場合はどうなるのでしょうか。

int
ap_run_mpm(apr_pool_t *pconf, apr_pool_t *plog, server_rec *s)
{
    ap_LINK_mpm_t *pHook;
    int n;
    ret rv;
    
    if (!_hooks.link_mpm)
        return OK;
    
    pHook = (ap_LINK_mpm_t *)_hooks.link_mpm->elts;
    for (n = 0; n < _hooks.link_mpm->nelts; ++n) {
        rv = pHook[n].pFunc(pconf, plog, s);

        if (rv != OK && rv != DECLINED)
            return rv;
    }
    return OK;
}

この場合は、登録されたイベントハンドラを先頭から順に起動し、OKかDECLINEDが返される限りはループを継続します。

イベントフックの実装は以上のようになっています。さて、前の方でも少し出ましたが、この時点ではイベントハンドラのリストは登録順に先頭から並ぶのみで、指定したaszPredecessorsやaszSuccessors、nOrderなどが全く反映されていません。これをどこで調整するのでしょうか。

その答えはserver/main.c上で見ることができます。main()中に、apr_hook_sort_all()という関数が呼ばれる箇所があります。これは、その時点で登録されているあらゆるイベントフックを確認し、指定の依存関係をもとにイベントハンドラの順番を並べ直す役割を持ちます。

    239 APU_DECLARE(void) apr_hook_sort_all(void)
    240 {
    241 #ifdef NETWARE
    242     get_apd
    243 #endif
    244     int n;
    245 
    246     for(n=0 ; n < s_aHooksToSort->nelts ; ++n) {
    247         HookSortEntry *pEntry=&((HookSortEntry *)s_aHooksToSort->elts)[n];
    248         *pEntry->paHooks=sort_hook(*pEntry->paHooks,pEntry->szHookName);
    249     }
    250 }

"pre_config"フックや"fixups"フック、もちろん今回"mpm"フックも含め、イベントフックは一つ一つ名前を持って定義され、それぞれ個別の構造体配列となって保持されています。246行目から249行目までで、これらのフック一つ一つをループしてソートを進めて行きます。ソートの処理は、248行のsort_hook()中で行います。

    187 static apr_array_header_t *sort_hook(apr_array_header_t *pHooks,
    188                                      const char *szName)
    189 {
    190     apr_pool_t *p;
    191     TSort *pSort;
    192     apr_array_header_t *pNew;
    193     int n;
    194 
    195     apr_pool_create(&p, apr_hook_global_pool);
    196     pSort=prepare(p,(TSortData *)pHooks->elts,pHooks->nelts);
    197     pSort=tsort(pSort,pHooks->nelts);
    198     pNew=apr_array_make(apr_hook_global_pool,pHooks->nelts,sizeof(TSortData));
    199     if(apr_hook_debug_enabled)
    200         printf("Sorting %s:",szName);
    201     for(n=0 ; pSort ; pSort=pSort->pNext,++n) {
    202         TSortData *pHook;
    203         assert(n < pHooks->nelts);
    204         pHook=apr_array_push(pNew);
    205         memcpy(pHook,pSort->pData,sizeof *pHook);
    206         if(apr_hook_debug_enabled)
    207             printf(" %s",pHook->szName);
    208     }
    209     if(apr_hook_debug_enabled)
    210         fputc('\n',stdout);
    211     return pNew;
    212 }

sort_hook()関数中で行っているのは次の3段階です。

  1. まずソートすべきハンドラリストpHooksをprepare()関数に渡す。(196行)
    1. prepare()ではまずnOrderの値(APR_HOOK_FIRSTとかAPR_HOOK_MIDDLEとか)によってソートされ、
    2. 次にハンドラ情報構造体(ap_LINK_mpm)のaszPredecessors、aszSuccessorsに対してポインタ参照を行う構造体を作り替え(TSort構造体の配列)てコピーする。
    3. aszPredecessors、aszSuccessorsを反映したポインタ参照も設定しておく。
  2. tsort()に移り、依存関係を満たすようにTSort構造体の配列をソートする。(197行)
  3. TSort構造体の配列から、再度ap_LINK_mpm構造体の配列にコピーして戻す。(198-208行)

以上のようにして、イベントハンドラは設定され、然るべき依存関係を保って実行されるのです。今後Apacheのソースを見ていくにあたってap_hook_xxx()/ap_run_xxx()各関数を各所で見つけることになりますが、これらのマクロ展開のイメージを持っておけば、関数定義が見つからないと慌てることは無くなることでしょう。

klab_gijutsu2 at 05:44│Comments(0)TrackBack(0)

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント