Apache 2.3/2.4系の新機能を見てみよう 番外編 〜イベントフックの実装〜
さて、今回は番外編ということで、2.3/2.4系に限らず現行のバージョンも含めてApacheのイベントフックの実装を見ていきたいと思います。該当箇所を抜き出しながらのソースレビューとなるため文章よりもコード中心の内容となります。では、前回"mpm"イベントフックの定義ということでAP_DECLARE_HOOKマクロが出ましたが、これをもう一度見ましょう。
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マクロは更に以下のように展開されます。
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)
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といったフィールドを持ちます。
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.c83 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.h187 #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段階です。
- まずソートすべきハンドラリストpHooksをprepare()関数に渡す。(196行)
- prepare()ではまずnOrderの値(APR_HOOK_FIRSTとかAPR_HOOK_MIDDLEとか)によってソートされ、
- 次にハンドラ情報構造体(ap_LINK_mpm)のaszPredecessors、aszSuccessorsに対してポインタ参照を行う構造体を作り替え(TSort構造体の配列)てコピーする。
- aszPredecessors、aszSuccessorsを反映したポインタ参照も設定しておく。
- tsort()に移り、依存関係を満たすようにTSort構造体の配列をソートする。(197行)
- TSort構造体の配列から、再度ap_LINK_mpm構造体の配列にコピーして戻す。(198-208行)
以上のようにして、イベントハンドラは設定され、然るべき依存関係を保って実行されるのです。今後Apacheのソースを見ていくにあたってap_hook_xxx()/ap_run_xxx()各関数を各所で見つけることになりますが、これらのマクロ展開のイメージを持っておけば、関数定義が見つからないと慌てることは無くなることでしょう。