2014年11月25日火曜日

apr_bucket と apr_bucket_brigade

今度は CORE_IN入力フィルタを見ようとしたところだが、入出力フィルタではbucketとbucket brigadeの処理が重要だ。
そこで、先に少し見ておくことにした。

bucket brigade(apr_bucket_brigade)は内部にbucket(apr_bucket)の列のリング構造を持っている。
bucket情報は前後へのポインタを持っており、bucket brigade情報自体もbucket情報へのポインタを持っている。
bucket brigadeの持っている次へのポインタが、bucket列の先頭に繋がり、bucket列を順に辿って、bucketの末尾の次が再び、bucket brigadeに繋がってリング構造となる。
逆方向も同じで、bucket brigadeの前にbucket列の末尾が繋がり、前に辿っていくと、bucket列の先頭に繋がり、その前に再びbucket brigadeが位置する。
このbucket brigade自身(内部)のアドレスは有効なbucket情報ではなく、センチネル(番兵)として機能する。



brigadeが軍隊がらみの用語らしく、bucket brigadeが何を意味するのか分からなかったが、単純に "bucket brigade" で辞書を引けば載っていた。バケツリレーの列のことだった。
バケツリレーか。なるほどね。



bucketタイプ情報


bucketにはデータバケットとメタデータバケットがある。
apr-utilライブラリで定義されているものが多いが、httpd側で定義されているものもある。
バケットタイプごとに異なるapr_bucket_type_t情報が定義されている。

以下、コメントを訳してみる。
(apr-util-1.5.4/include/apr_buckets.h)

    131 struct apr_bucket_type_t {

バケットタイプ名

    135     const char *name;
     :

このバケットタイプで定義されている関数の数。5以上。

    140     int num_func;
     :

バケットタイプがメタデータかどうかの識別

  • APR_BUCKET_DATA(0) データバケット
  • APR_BUCKET_METADATA(1) メタデータバケット

メタデータはapr_bucket_read()でデータを返さない。
lengthも有効ではない。
空のバケットはメタデータバケットでない場合にのみ安全に、自由に削除できる。

    151     enum {
    152         /** This bucket type represents actual data to send to the client. */
    153         APR_BUCKET_DATA = 0,
    154         /** This bucket type represents metadata. */
    155         APR_BUCKET_METADATA = 1
    156     } is_metadata;
     :

destroy関数
バケットの内部データや使用しているリソースを解放する。
全てのバケットにおいて実装されている必要がある。
ただし、一部では何もしない実装のものもある。

通常、apr_bucket_destroy(data) というマクロを使って実行される。

    164     void (*destroy)(void *data);
     :

read関数
バケットからデータを読み込む。
全てのバケットにおいて実装される必要がある。
b: 読込対象のバケット
str:読み込んだデータの格納先
len:読み込んだデータ長
block: ブロックモードAPR_BLOCK_READ(0)で読むか、非ブロックモードAPR_NONBLOCK_READ(1)で読むか

通常、apr_bucket_read(b,str,len,block) というマクロを使って実行される。

    176     apr_status_t (*read)(apr_bucket *b, const char **str, apr_size_t *len,
    177                          apr_read_type_e block);
    178
     :

setaside関数
バケットを指定したプールに退避する。
これにより、バケットは少なくともそのプールが存在している間は存続できる。
ただし、バケットに含まれるデータは、このプールより先に失われる可能性がある(例えば
データはスタック上に蓄えられていたかもしれない、。あるいは与えられてプールの子プールにあったかもしれない)
そのため、適切な生存期間を確保するために、何らかの方法でコピーしたり、データを変換したりする必要がある。

通常、apr_bucket_setaside(e,pool)というマクロを使って実行される。

    192     apr_status_t (*setaside)(apr_bucket *e, apr_pool_t *pool);
    193
     :

基本的には、バケットタイプごとのデータ(data)を指定のプール上に複製しapr_bucketの情報を置き換え、元のdataは必要に応じて破棄の処理が行われている(apr_bucket *eの中身が入れ替わる)。


split関数
ひとつのバケットを、指定した位置で二つに分割する。
分割はバケットを複製し、データのstart/end/offset情報を変更することで行われる(データの複製は行わない)。
この操作が行えない場合(データの長さが決定できないPIPEバケットやSOCKETバケットが該当)、APR_ENOTIMPLが返される。
e: 分割するバケット
point : 新しく作成されるバケットの先頭データの位置
分割後のeでは、pointの前までのデータが参照される。

通常、apr_bucket_split(e,point)というマクロを使って実行される。

    203     apr_status_t (*split)(apr_bucket *e, apr_size_t point);
    204
     :

copy関数
バケット情報をコピーする(データはコピーしない)
実装されていない場合、APR_ENOTIMPLが返る。
e: コピーするバケット
c: コピー先の新しいバケットへのアドレス

通常、apr_bucket_copy(e,c) というマクロを使って実行される。

    211     apr_status_t (*copy)(apr_bucket *e, apr_bucket **c);
    212
    213 };

バケットタイプ情報の例として、SOCKETデータバケットの場合は次の通り。

(apr-util-1.5.4/buckets/apr_buckets_socket.c)

    107 APU_DECLARE_DATA const apr_bucket_type_t apr_bucket_type_socket = {
    108     "SOCKET", 5, APR_BUCKET_DATA,
    109     apr_bucket_destroy_noop,
    110     socket_bucket_read,
    111     apr_bucket_setaside_notimpl,
    112     apr_bucket_split_notimpl,
    113     apr_bucket_copy_notimpl
    114 };

関数名に _notimpl がつくものはAPR_ENOTIMPLを返すだけの関数。
関数名の _noopがつくものは、return APR_SUCCESSなど、成功で復帰するだけの関数。
具体的な処理を含む関数は、この例ではread関数だけとなる。

何もしないことが正しいという場合は、noopで、何かしてほしいが、何もできていないのがnotimになっている。
フィルタのコードを見ると、setaside()を実行しようとして、APR_ENOTIMPLが返った場合にはread()を実行し、
その後、再度、setasid()を実行する例が見られる。
例えば、apr_bucket_socketの場合、read()関数で、バケットタイプがHEAPデータバケットに変換される。
HEAPバケットタイプにはsetaside関数の実装が用意されているので、実行できる。

どのようなバケットタイプがあるか、grepしてみると以下のものが見つかった。

(1)メタデータバケット


変数名 名前 定義ファイル
apr_bucket_type_flush FLUSHメタデータバケット apr-util-1.5.4/buckets/apr_buckets_flush.c
apr_bucket_type_eos EOSメタデータバケット apr-util-1.5.4/buckets/apr_buckets_eos.c
ap_rl_bucket_type_end RL_ENDメタデータバケット httpd-2.4.9/modules/filters/mod_ratelimit.c
ap_rl_bucket_type_start RL_STARTメタデータバケット httpd-2.4.9/modules/filters/mod_ratelimit.c
ap_bucket_type_eoc EOCメタデータバケット httpd-2.4.9/server/eoc_bucket.c
ap_bucket_type_eor EORメタデータバケット httpd-2.4.9/server/eor_bucket.c
ap_bucket_type_error ERRORメタデータバケット httpd-2.4.9/server/error_bucket.c


(2)データバケット


変数名 名前 定義ファイル
apr_bucket_type_pool POOLデータバケット apr-util-1.5.4/buckets/apr_buckets_pool.c
apr_bucket_type_socket SOCKETデータバケット apr-util-1.5.4/buckets/apr_buckets_socket.c
apr_bucket_type_file FILEデータバケット apr-util-1.5.4/buckets/apr_buckets_file.c
apr_bucket_type_immortal IMMORTALデータバケット apr-util-1.5.4/buckets/apr_buckets_simple.c
apr_bucket_type_transient TRANSIENTデータバケット apr-util-1.5.4/buckets/apr_buckets_simple.c
apr_bucket_type_mmap MMAPデータバケット apr-util-1.5.4l/buckets/apr_buckets_mmap.c
apr_bucket_type_heap HEAPデータバケット apr-util-1.5.4/buckets/apr_buckets_heap.c
apr_bucket_type_pipe PIPEデータバケット apr-util-1.5.4/buckets/apr_buckets_pipe.c
apr_bucket_type_lob LOBデータバケット apr-util-1.5.4/dbd/apr_dbd_oracle.c
apr_bucket_type_lob LOBデータバケット apr-util-1.5.4/dbd/apr_dbd_mysql.c
odbc_bucket_type ODBC_LOBデータバケット apr-util-1.5.4/dbd/apr_dbd_odbc.c
bucket_type_cgi CGIデータバケット httpd-2.4.9/modules/generators/mod_cgi.c
bucket_type_socket_ex SOCKET_EXデータバケット httpd-2.4.9/modules/proxy/mod_proxy_scgi.c


bucket情報


bucket情報には、このbucketタイプを含む。
bucketの生成は、通常バケットタイプごとに用意されている生成関数を使う。
例えば、apr_bucket_eos_create(list) はEOSメタデータバケットを生成する。

    224 struct apr_bucket {
    225     /** Links to the rest of the brigade */
    226     APR_RING_ENTRY(apr_bucket) link;




bucketのリング構造のための前後のapr_bucketへのリンクアドレス

(apr-1.5.1/include/apr_ring.h)
     70 #define APR_RING_ENTRY(elem)                           \
     71     struct {                                      \
     72      struct elem * volatile next;                      \
     73      struct elem * volatile prev;                      \
     74     }

これは、こうなる

 struct {
  struct apr_bucket * volatile next;
  struct apr_bucket * volatile prev;
 } link;

apr_bucket_type_t情報
バケットタイプごとに適切な情報をセットする。

     :
    228     const apr_bucket_type_t *type;
     :

バケット内のデータ長。
本来なら、これも関数として実装されるべきかもしれない。
もっとも一般的になされるのは長さを知ることだと思われるので、最適化した。
もし、データ長が未知なら、この変数の値は-1をとる。

    234     apr_size_t length;
     :

バケットの内部に持つbaseポインタからの相対位置を示す、データの開始位置。
ほとんどのバケットタイプは複数のバケットから参照されるようなデータの固定ブロックを許容している。
各バケットは、そのデータブロックの異なる断片を参照する。各断片は、固定ブロックの開始base + startから
始まり、 base+start+length までを参照する。
length==-1の場合、start==-1とする。

    242     apr_off_t start;
     :

バケットタイプに依存する情報のアドレス。

    244     void *data;
     :

free関数
バケットを解放する関数。
この関数は常に定義され、バケットをアロケートしているメモリ関数と整合しているべきである。
例えば、malloc()がバケットのアロケートに使われるなら、このfree関数ポインタはfree()関数を指すべきだ。

通常、apr_bucket_destroy(data) というマクロを使って実行される。
このマクロでは、バケットタイプで定義されるdestroy()関数と、このfree()関数が実行されている。
だいたい、apr_bucket_free()が指定されている。

    252     void (*free)(void *e);
     :

このバケットのアロケートに使われたフリーリスト

    254     apr_bucket_alloc_t *list;
    255 };




bucket brigade情報

そして、bucket brigade情報に、bucket列が格納される



    258 struct apr_bucket_brigade {
     :

このbrigadeに関係するプール。
プールの外部にアロケートされるデータはないが、cleanup関数がこのプールには登録されている。
もしbrigadeがプールの破棄機能以外の何らかの関数によって破棄された場合には、その破棄関数は
cleanup関数を解除する責任を負う。


    264     apr_pool_t *p;
     :

bucket brigade内の bucket列

    273     APR_RING_HEAD(apr_bucket_list, apr_bucket) list;
     :

(apr-1.5.1/include/apr_ring.h)
     91 #define APR_RING_HEAD(head, elem)                      \
     92     struct head {                                 \
     93      struct elem * volatile next;                      \
     94      struct elem * volatile prev;                      \
     95     }

これは、こうなる

 struct apr_bucket_list {
  struct apr_bucket * volatile next;
  struct apr_bucket * volatile prev;
 } list;

このバケットのアロケートに使われたフリーリスト

    275     apr_bucket_alloc_t *bucket_alloc;
    276 };

bucket brigadeの生成には、
 apr_bucket_brigade *apr_brigade_create(apr_pool_t *p, apr_bucket_alloc_t *list)
が使われる。生成されたbucket brigadeは初期状態ではbucketは含まない(空の状態)。

ここで、bucket領域は、引数で渡されたプールp上に確保され、
引数で渡される listが、 apr_bucket_brigadeの bucket_allocにセットされる。
上記のapr_bucket_brigadeの説明にあるcleanup関数は、brigade_cleanup関数が、プールpのcleanup関数に登録される。


マクロと関数の概要


ここでは、CORE出力フィルタやCORE_IN入力フィルタで使用されているものを見ておく。


  • APR_BRIGADE_CONCAT(a,b)
    • apr-util-1.5.4/include/apr_buckets.h
    • APR_BRIGADE_CONCAT(a,b)はbucket brigade aの末尾にbucket brigade bを繋ぐ処理を行うマクロ。
      その後、bucket brigade b は空にする。
  • APR_BRIGADE_EMPTY(b)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket brigadeに 有効なbucket情報がない(センチネルしかない)場合に真を返すマクロ。
  • APR_BRIGADE_FIRST(b)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket brigade b のbucketリングの先頭(センチネルの次)の bucketのアドレスを返すマクロ。
  • APR_BRIGADE_INSERT_TAIL(b, e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket brigade bのbucket列の末尾(センチネルの前)に bucket eを追加するマクロ。
  • APR_BRIGADE_LAST(b)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket brigade b のbucket列の末尾(センチネルの前)のbucketを取得するマクロ。
  • APR_BRIGADE_PREPEND(a,b)
    • apr-util-1.5.4/include/apr_buckets.h
    • brigade bのbucket列を brigade aのbucket列の前に追加するマクロ
      brigade bは空になる。
  • APR_BRIGADE_SENTINEL(b)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket brigade bのbucketリングの端点(センチネル)のアドレスを返すマクロ。
      これ自身は有効なbucketではない。
  • APR_BUCKET_INSERT_AFTER(a,b)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket aの後ろにbucket bを挿入するマクロ
  • APR_BUCKET_IS_FILE(e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket eのバケットタイプがFILEの場合に真を返すマクロ
  • APR_BUCKET_IS_FLUSH(e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket e のバケットタイプがFLUSHメタデータの場合に真を返すマクロ
  • APR_BUCKET_IS_METADATA(e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket eがメタデータバケットの場合に1(APR_BUCKET_METADATA)を返すマクロ。
  • APR_BUCKET_NEXT(e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket eの次のbucketを返すマクロ
      返ってきたアドレスがセンチネルと一致した場合には、bucketリングを一周したことになる。
  • APR_BUCKET_REMOVE(e)
    • apr-util-1.5.4/include/apr_buckets.h
    • bucket eを 含まれている bucket brigadeの bucketリングから外すマクロ
      (eの次のバケットの前を、eの前のバケットにし、eの前のバケットの次を、eの次のバケットにする)
  • AP_BUCKET_IS_EOR(e)
    • httpd-2.4.9/include/http_request.h
    • bucket eのバケットタイプがEORメタデータの場合に真を返すマクロ
  • BRIGADE_NORMALIZE(b)
    • httpd-2.4.9/server/core_filters.c
    • bucket brigade bのbucketリングの先頭から辿って、length==0のデータバケットの削除処理(apr_bucket_delete)を行うマクロ。
    • apr_status_t ap_save_brigade(ap_filter_t *f, apr_bucket_brigade **saveto, apr_bucket_brigade **b, apr_pool_t *p)
      • httpd--2.4.9/server/util_filter.c
      • bucket brigade *b のバケットを全て setaside()でプール pに退避する。
        退避した*bのbucket 列を bucket brigade *saveto の末尾に繋ぐ(APR_BRIGADE_CONCAT(*saveto, *b)。
        結果として bucket brigade *bは空になる。
        なお、 *savetoがNULLだった場合には新しいbucket brigadeを生成する。
    • apr_status_t apr_brigade_cleanup(void *data)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • bucket brigade dataに保持されるbucket列のbucketをすべて破棄(apr_bucket_delete)する。
    • apr_bucket_brigade *apr_brigade_create(apr_pool_t *p, apr_bucket_alloc_t *list)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • 新しいbucket brigadeを生成する。初期状態ではbucketは含まない(空)。
    • apr_status_t apr_brigade_partition(apr_bucket_brigade *b, apr_off_t point, apr_bucket **after_point)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • bucket brigade b の先頭から指定の長さpoint 分進めて、その次のbucketを*after_pointに返す。
        指定の長さが、ひとつのデータbucketの途中だった場合、そのデータbucketを分割し、分割された後ろのbucketが*after_pointに返される
    • apr_bucket_brigade *apr_brigade_split_ex(apr_bucket_brigade *b, apr_bucket *e, apr_bucket_brigade *a)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • apr_brigade_split_exは、bucket brigade b を 指定したbucket eの前で分割する。
        分割された eから後ろのbucket列は bucket brigade aに追加される。
        追加された bucket brigade(a)を返す。
        注:aが空でない場合、追加する前に空にされる。aがNULLの場合は、新しいbucket brigadeを生成する。
    • apr_status_t apr_brigade_split_line(apr_bucket_brigade *bbOut, apr_bucket_brigade *bbIn, apr_read_type_e block, apr_off_t maxbytes)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • bucket brigade bbInを読み込み、LFがあった場合、そこまでのbucket列を作成し、bbOutに追加する。
        • bbIn読み込み時にブロックモードとするかどうかを blockに指定。
        • 読込の最大長指定が、maxbytesで、それだけ読んでもLFがなければ、そこまでで分割する。
          (注: LFがない場合は、読み込んだデータがmaxbytes以上になった時点で処理を終えるので、maxbytesを越えることもある)
    • apr_status_t apr_brigade_write(apr_bucket_brigade *b, apr_brigade_flush flush, void *ctx, const char *str, apr_size_t nbyte)
      • apr-util-1.5.4/buckets/apr_brigade.c
      • データ列 str(長さnbytes)を、bucket brigade *bのbucket列に追加する。
      • ただし、bucket列の末尾がHEAPデータバケットだった場合で、そのHEAPのデータがほかのHEAPバケットが参照していない場合に、
        内部で確保されているメモリのうち、実際に使用済みの領域以降に残っている未使用のメモリサイズが、
        ここで要求されているサイズnbytes以上だった場合、
        • strを未使用領域にコピーし、末尾のHEAPデータバケットのlengthを+nbytesで更新する。
      • HEAPバケットはあったが 未使用領域が不足していた場合、
        flush(フラッシュ関数)が指定されていた場合
        • str/nbytes のTRANSIENTデータバケットを作成し、bの末尾に追加し、flush(b,ctx)を実行する。
      • flushが指定されていない場合は、
        • str/nbytes のHEAPデータバケットを作成し、bの末尾に追加しSUCCESSでreturnする。
      • 末尾のHEAPデータバケットが使えない/存在しない場合、
        • APR_BUCKET_BUFF_SIZE(8000)バイトのメモリを確保し、それを含むHEAPデータバッファを作成し、
          str/nbytes をそのバッファにコピーし、lengthをnbytesで更新する。
    • apr_bucket_copy(e,c)
      • apr-util-1.5.4/include/apr_buckets.h
      • bucketのタイプ別copy関数を実行するマクロ
    • apr_bucket_destroy(e)
      • apr-util-1.5.4/include/apr_buckets.h
      • bucketのタイプ別destroy関数を実行し、
        bucketのfree()関数を実行するマクロ
    • apr_bucket_delete(e)
      • apr-util-1.5.4/include/apr_buckets.h
      • bucket e をAPR_BUCKET_REMOVE(e)でbucket brigadeのbucketリングから外し、apr_bucket_destroy(e)で破棄するマクロ
    • apr_bucket_eos_create
      • apr-util-1.5.4/buckets/apr_buckets_eos.c
      • EOSメタデータバケットの生成
    • apr_bucket_read
      • apr-util-1.5.4/include/apr_buckets.h
      • bucketのタイプ別read関数を実行するマクロ
    • apr_bucket_setaside
      • apr-util-1.5.4/include/apr_buckets.h
      • bucketの対応別setaside関数を実行するマクロ
    • apr_bucket_split(e,point)
      • apr-util-1.5.4/include/apr_buckets.h
      • bucketのタイプ別split関数を実行するマクロ
        ひとつのbucketを2つに分割する。



    今回はここまでで終わり。

    2014/12/10 誤字訂正。setaside関数説明補足。

    0 件のコメント:

    コメントを投稿