2014年12月22日月曜日

apr_file_t 情報と FILEデータバケット

ここでは apr_bucketとapr_bucket_brigade で調べたデータバケットのひとつであるFILEデータバケットを見ていく。

(1)apr_bucket_file情報

FILEデータバケットを見てみる。
FILEデータバケットの格納データはapr_bucket_file情報だ。

apr_bucket_file情報は、以下の情報を持っていて、バケット情報のタイプ依存変数 data にセットされる。

(apr-util-1.5.4/include/apr_brigade.h)
    612 struct apr_bucket_file {
    613     /** Number of buckets using this memory */
    614     apr_bucket_refcount  refcount;

共有リソース用の参照回数の情報

    615     /** The file this bucket refers to */
    616     apr_file_t *fd;

FILEバケット情報の処理対象のapr_file_t情報がセットされる

    617     /** The pool into which any needed structures should
    618      *  be created while reading from this file bucket */
    619     apr_pool_t *readpool;
    620 #if APR_HAS_MMAP
    621     /** Whether this bucket should be memory-mapped if
    622      *  a caller tries to read from it */
    623     int can_mmap;

mmapが使用できるかどうかのフラグ。
centos環境ではAPR_HAS_MMAPは定義されているので、この値は1が初期値。

    624 #endif /* APR_HAS_MMAP */
    625 };

この構造体の先頭の変数、refcountは、この情報が複数のバケットで共有されるタイプのリソースであることを意味している。
この変数には次の構造体 apr_bucket_refcount を使ってアクセスされる。

(include/apr_buckets.h)

    524 typedef struct apr_bucket_refcount apr_bucket_refcount;

    531 struct apr_bucket_refcount {
    532     /** The number of references to this bucket */
    533     int          refcount;
    534 };

(2)FILEデータバケットタイプ情報

FILEデータバケットのバケットタイプ apr_bucket_type_t情報は次の通り。

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

    221 APU_DECLARE_DATA const apr_bucket_type_t apr_bucket_type_file = {
    222     "FILE", 5, APR_BUCKET_DATA,
    223     file_bucket_destroy,
    224     file_bucket_read,
    225     file_bucket_setaside,
    226     apr_bucket_shared_split,
    227     apr_bucket_shared_copy
    228 };

これを見ると、split関数とcopy関数に共有リソースの処理用関数がそのまま使われている。
また、file_bucket_destroy()からも、共有リソース破棄用の関数(apr_bucket_shared_destroy)が呼び出されている。

共有リソースというのは、参照するデータを複数のバケットで共有するための仕組みで、refcountはその名の通り、このリソースを参照しているバケット数を格納するようになっている。
初期値は1で、コピー(copy)や、分割(split)によって、その数はカウントアップされる。

(2.1)copy関数

下記はcopy関数だ。

apr-util-1.5.4/buckets/apr_buckets_refcount.c)

     33 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_shared_copy(apr_bucket *a,
     34                                                         apr_bucket **b)
     35 {
     36     apr_bucket_refcount *r = a->data;
     37
     38     apr_bucket_simple_copy(a, b);

apr_bucket_simple_copy(a,b)は、 apr_bucket *bにapr_bucketの領域を確保し、値を*aからコピーする処理だ。

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

     19 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_simple_copy(apr_bucket *a,
     20                                                         apr_bucket **b)
     21 {
     22     *b = apr_bucket_alloc(sizeof(**b), a->list); /* XXX: check for failure? */
     23     **b = *a;
     24
     25     return APR_SUCCESS;
     26 }

この時、タイプ依存情報のdataのアドレスもコピーされるので、a,bは同じapr_bucket_file情報を参照するようになる。

     39     r->refcount++;

そして、データ依存関数のrefcountをカウントアップする。

     40
     41     return APR_SUCCESS;
     42 }


(2.2)split関数

続いて、split関数だ

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

     19 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_shared_split(apr_bucket *a,
     20                                                          apr_size_t point)
     21 {
     22     apr_bucket_refcount *r = a->data;
     23     apr_status_t rv;
     24
     25     if ((rv = apr_bucket_simple_split(a, point)) != APR_SUCCESS) {

apr_bucket_simple_splitでは、内部でapr_bucket_simple_copy()を実行し、同じapr_bucketを複製する。
そして、バケット情報のstartとlengthを調整している。

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

     28 APU_DECLARE_NONSTD(apr_status_t) apr_bucket_simple_split(apr_bucket *a,
     29                                                          apr_size_t point)
     30 {
     31     apr_bucket *b;
     32
     33     if (point > a->length) {

pointがバケットaのサイズより大きければ、エラーで返している。

     34         return APR_EINVAL;
     35     }
     36
     37     apr_bucket_simple_copy(a, &b);

バケットaの複製バケットbを作成する。

     38
     39     a->length  = point;

バケットaの方のlengthをpointにおきかえる。

     40     b->length -= point;

バケットbの方のlengthはpoint分、引き算する。

     41     b->start  += point;

バケットbの先頭を元のaの先頭から、point分、先に進める。

     42
     43     APR_BUCKET_INSERT_AFTER(a, b);

そして、バケットbをbucket brigadeの バケットaの後ろに挿入する。

     44
     45     return APR_SUCCESS;
     46 }

バケットaは、aの先頭から、pointまでのデータを含むようになり、
aから複製されたバケットbは、先頭がa+pointに、そして、もとの長さ(length)のpointから後ろ分を格納する。
新しいバケットbは、bucket brigadeのaの後ろに追加される。

     26         return rv;
     27     }
     28     r->refcount++;

apr_bucket_simple_split()でもタイプ異存情報dataは同じアドレスの情報を参照している。
そこでrefcountをカウントアップしている。

     29
     30     return APR_SUCCESS;
     31 }

(2.3)destroy関数

バケットを破棄(destroy)するときにマイナスされていく。refcount==0となると、このリソースを参照しているバケットがないことになる。
FILEデータタイプの場合、その場合に、実際のそのリソースを破棄する処理を行うようになっている。

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

    32 static void file_bucket_destroy(void *data)
     33 {
     34     apr_bucket_file *f = data;
     35
     36     if (apr_bucket_shared_destroy(f)) {
     37         /* no need to close the file here; it will get
     38          * done automatically when the pool gets cleaned up */
     39         apr_bucket_free(f);
     40     }
     41 }

この36行目 apr_bucket_shared_destroy(f)が、共有リソースのrefcountをマイナスする処理で、マイナスした結果が0だと真を返す。
refcountが0になると(返り値が真だと)、この関数では、apr_bucket_free()を実行するようになっている。

(2.4)read関数


read関数は、次のようになっている

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

     75 static apr_status_t file_bucket_read(apr_bucket *e, const char **str,
     76                                      apr_size_t *len, apr_read_type_e block)

FILEデータバケットを読み込み、データのアドレスをstrにセットする。
lenは、読み込みデータ長が格納される。
読み込み時のブロックモードをblockに指定する。

     77 {
     78     apr_bucket_file *a = e->data;
     79     apr_file_t *f = a->fd;
     80     apr_bucket *b = NULL;
     81     char *buf;
     82     apr_status_t rv;
     83     apr_size_t filelength = e->length;  /* bytes remaining in file past offset */
     84     apr_off_t fileoffset = e->start;

     86     apr_int32_t flags;

     88

     90     if (file_make_mmap(e, filelength, fileoffset, a->readpool)) {

file_make_mmapでは、FILEデータバケットをMMAPデータバケットに変換している。
FILEデータバケットの can_mmapが0の場合にはMMAPに変換できないが、
初期値では、ビルド環境依存になるが、mmapが利用可能(APR_HAS_MMAPが定義されている)なら、このフラグは1で初期化されている。

ちなみに、MMAPデータバケットには参照できるデータサイズの上限が決まっている。
APR_MMAP_LIMITバイト(MMAP_LIMITまたは、4*1024*1024(4MB))だ。
これを越えるファイルは珍しくないだろう。
この場合、FILEデータオブジェクトが、4MBまででsplitされ、先頭の4MBがMMAPデータバケットに変換される。

     91         return apr_bucket_read(e, str, len, block);

その後、MMAPデータバケットのread()関数を実行している。

     92     }

ここ以降は、MMAPデータバケットに変換できない場合の処理だ。

今回は割愛する。

    :
    154 }

(2.5)setaside関数

setaside関数は次のようになっている。

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

    201 static apr_status_t file_bucket_setaside(apr_bucket *data, apr_pool_t *reqpool)

バケット dataを メモリプールreqpoolに確保しなおす処理だ。
現在使用されるメモリプールが短命な場合に不都合が生じる(バケットが使用される予定があるのに破棄されてしまう)ようなケースで、より長命なプール上に退避するために使用する。

    202 {
    203     apr_bucket_file *a = data->data;
    204     apr_file_t *fd = NULL;
    205     apr_file_t *f = a->fd;
    206     apr_pool_t *curpool = apr_file_pool_get(f);

apr_foo_pool_get()はgrepしても見つからない。
APR_POOL_DECLARE_ACCESSOR(foo)マクロ
および
APR_POOL_IMPLEMENT_ACCESSOR(foo)マクロ
で定義されており、apr_foo_t構造体のpoolを返す。

    207
    208     if (apr_pool_is_ancestor(curpool, reqpool)) {

メモリプールは、aprライブラリが提供する機能で、親子(parent-child)関係と兄弟姉妹(sibling)関係を持っている。
apr_pool_create_ex()で引数にメモリプールが指定されると、生成されるプールの親にそのメモリプールがセットされ、親メモリプール側には子として新しいメモリプールがセットされる。
親メモリプールが複数の子メモリプールを持つ場合には、子メモリプールには兄弟関係を持つメモリプールのリストが接続される。
メモリプールが破棄される場合、そのメモリプールの子孫のプールもその兄弟も同時に破棄される。
reqpoolに確保しなおす要求の目的はより長命なプールにバケットを移すことであるから、reqpool自体が現在確保されているcurpoolの子孫であった場合には、確保しなおしても意味がないことになる。

apr_pool_is_ancestor()は、reqpoolのparentを辿って、curpoolが現れないかどうかをチェックする。
もし現れた場合(reqpoolがcurpoolの子孫の場合)、この関数では処理を行わない。
なお、reqpoolとcurpoolが同じプールである場合もこの関数は真を返す。

    209         return APR_SUCCESS;
    210     }
    211

以降は、reqpoolがcurpoolの子孫でない場合の処理だ。

    212     if (!apr_pool_is_ancestor(a->readpool, reqpool)) {

apr_bucket_file情報の内部に持っているメモリプール readpool も 親子関係をチェックする。

    213         a->readpool = reqpool;
    214     }
    215
    216     apr_file_setaside(&fd, f, reqpool);

apr_file_t情報をreqpoolに退避する。
aprライブラリ関数で定義されている。

    217     a->fd = fd;

apr_bucket_file情報のapr_file_t情報を fdを退避先のfdに更新して、退避処理が終わる。

    218     return APR_SUCCESS;
    219 }

(3)apr_file_t情報

apr_bucket_file情報のファイル情報は、apr_file_t情報が持っている。
これは次のような構造体だ。

(apr-1.5.1/include/arch/unix/apr_arch_file_io.h)

     93 struct apr_file_t {
     94     apr_pool_t *pool;
     95     int filedes;

ファイルディスクリプタがセットされる。
FILEデータバケットがcopy()やsplit()で新たなapr_file_t情報を生成しては、内部で次々にファイルをオープンすることになりかねない
共有リソース化することでそういった事象が回避できる。

     96     char *fname;

ファイル名

     97     apr_int32_t flags;

apr_file_open()で指定されたフラグが保持されている。
EnableSendfileが有効な場合には、APR_SENDFILE_ENABLEDフラグもONとなっている。

     98     int eof_hit;
     99     int is_pipe;
    100     apr_interval_time_t timeout;
    101     int buffered;

入出力をバッファモードで処理するかどうかの識別。
フラグにAPR_FOPEN_BUFFEREDか、APR_BUFFEREDを指定した場合に有効となる。

    102     enum {BLK_UNKNOWN, BLK_OFF, BLK_ON } blocking;
    103     int ungetchar;    /* Last char provided by an unget op. (-1 = no char)*/

    105     /* if there is a timeout set, then this pollset is used */
    106     apr_pollset_t *pollset;

    108     /* Stuff for buffered mode */
    109     char *buffer;
    110     apr_size_t bufpos;        /* Read/Write position in buffer */
    111     apr_size_t bufsize;       /* The size of the buffer */
    112     unsigned long dataRead;   /* amount of valid data read into buffer */
    113     int direction;            /* buffer being used for 0 = read, 1 = write */
    114     apr_off_t filePtr;        /* position in file of handle */

    116     struct apr_thread_mutex_t *thlock;

マルチスレッドでの排他制御用

    118 };

(4)apr_file_t情報のセットアップ : default_handler

FILEデータバケットは、apr_brigade_insert_file()で作成される。

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

    707 APU_DECLARE(apr_bucket *) apr_brigade_insert_file(apr_bucket_brigade *bb,
    708                                                   apr_file_t *f,
    709                                                   apr_off_t start,
    710                                                   apr_off_t length,
    711                                                   apr_pool_t *p)

この関数の引数には、apr_file_t情報が渡されている。

apr_bucket_brigade *bb 生成されたFILEデータバケットはこのbucket brigade bbの末尾に追加される。

apr_file_t *f 生成するFILEデータバケットに格納するapr_file_t情報

apr_off_t start 生成するFILEデータバケットは、対象のapr_file_t情報のatartバイト目からを参照する。

apr_off_t length 生成するFILEデータバケットは、対象のapr_file_t情報のstartバイト目から、lengthバイト分を参照する。

apr_pool_t *p 処理で使用するメモリプール

FILEデータバケットが内部で参照するデータ長には上限が設けられている。
1GBだ。

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

    704 /* A "safe" maximum bucket size, 1Gb */
    705 #define MAX_BUCKET_SIZE (0x40000000)

例えば、10GBのファイルの場合、1GB単位に分割されて、10個のFILEデータバケットが生成されている。
このとき、バケットの格納データ自体は同じ、apr_file_tで、バケット情報に持つstartとlengthが連続したデータを参照するように調整されることになる。
つまり、1つめのバケットはstart=0でlength=4GB、2つめのバケットは、startが4GBでlengthは同じく4GBと続き、10個のFILEバケットが生成される。

このapr_brigade_insert_file()関数の利用例を見てみる。
例えば、ハンドラフック関数であるdefault_handler()で使われている。

(httpd-2.4.9/server/core.c)

   4248 static int default_handler(request_rec *r)
   4249 {
    :
   4281     if (r->method_number == M_GET || r->method_number == M_POST) {

GET/POSTリクエストの処理の経路となる。

    :
   4330         if ((status = apr_file_open(&fd, r->filename, APR_READ | APR_BINARY

   4332                             | AP_SENDFILE_ENABLED(d->enable_sendfile)

   4334                                     , 0, r->pool)) != APR_SUCCESS) {

apr_file_open()で、r->filenameに指定されたファイルをオープンする。
この処理で生成されるのがapr_file_t情報で、fdに格納される。
メモリ領域は、r->poolを使用する。
この処理では、 (APR_READ|APR_BINARY) → (O_RDONLY|O_BINARY) に変換され、ファイルをオープンする際のフラグに使用される。
ファイル識別子は、apr_file_t情報のfiledes変数にセットされる。
また、open()では使用されないAP_SENDFILE_ENABLEDフラグを含め、フラグ情報は apr_file_t情報のflagsにセットされる。
初期値では、blockingはBLK_ON(ブロック有効)。
bufferedは、APR_FOPEN_BUFFEREDフラグの有無で判定されるが、ここでは指定されていないので、0。
bufferやbuffsizeはAPR_FOPEN_BUFFEREDが有効な場合に使用さえるが、ここでは使用されない。
その他の変数が初期化される。
そして、このapr_file_t情報のcleanup関数が登録される。
以下、apr_file_openのコードを抜粋引用しておく。

(apr-1.5.1/file_io/unix/open.c)

     90 APR_DECLARE(apr_status_t) apr_file_open(apr_file_t **new,
     91                                         const char *fname,
     92                                         apr_int32_t flag,
     93                                         apr_fileperms_t perm,
     94                                         apr_pool_t *pool)
     95 {
     96     apr_os_file_t fd;
     97     int oflags = 0;

     99     apr_thread_mutex_t *thlock;
    100     apr_status_t rv;

    102
    103     if ((flag & APR_FOPEN_READ) && (flag & APR_FOPEN_WRITE)) {
    104         oflags = O_RDWR;
    105     }
    106     else if (flag & APR_FOPEN_READ) {
    107         oflags = O_RDONLY;
    108     }
    109     else if (flag & APR_FOPEN_WRITE) {
    110         oflags = O_WRONLY;
    111     }
    112     else {
    113         return APR_EACCES;
    114     }
     :

    133     if (flag & APR_FOPEN_BINARY) {
    134         oflags |= O_BINARY;
    135     }
     :

    171
    172     if (perm == APR_OS_DEFAULT) {
    173         fd = open(fname, oflags, 0666);
    174     }
    175     else {
    176         fd = open(fname, oflags, apr_unix_perms2mode(perm));
    177     }
    178     if (fd < 0) {
    179        return errno;
    180     }
    181     if (!(flag & APR_FOPEN_NOCLEANUP)) {

    183         static int has_o_cloexec = 0;
    184         if (!has_o_cloexec)

    186         {
    187             int flags;
    188
    189             if ((flags = fcntl(fd, F_GETFD)) == -1) {
    190                 close(fd);
    191                 return errno;
    192             }
    193             if ((flags & FD_CLOEXEC) == 0) {
    194                 flags |= FD_CLOEXEC;
    195                 if (fcntl(fd, F_SETFD, flags) == -1) {
    196                     close(fd);
    197                     return errno;
    198                 }
    199             }

    201             else {
    202                 has_o_cloexec = 1;
    203             }

    205         }
    206     }
    207
    208     (*new) = (apr_file_t *)apr_pcalloc(pool, sizeof(apr_file_t));
    209     (*new)->pool = pool;
    210     (*new)->flags = flag;
    211     (*new)->filedes = fd;
    212
    213     (*new)->fname = apr_pstrdup(pool, fname);
    214
    215     (*new)->blocking = BLK_ON;
    216     (*new)->buffered = (flag & APR_FOPEN_BUFFERED) > 0;
    217
    218     if ((*new)->buffered) {
    219         (*new)->buffer = apr_palloc(pool, APR_FILE_DEFAULT_BUFSIZE);
    220         (*new)->bufsize = APR_FILE_DEFAULT_BUFSIZE;

    222         if ((*new)->flags & APR_FOPEN_XTHREAD) {
    223             (*new)->thlock = thlock;
    224         }

    226     }
    227     else {
    228         (*new)->buffer = NULL;
    229     }
    230
    231     (*new)->is_pipe = 0;
    232     (*new)->timeout = -1;
    233     (*new)->ungetchar = -1;
    234     (*new)->eof_hit = 0;
    235     (*new)->filePtr = 0;
    236     (*new)->bufpos = 0;
    237     (*new)->dataRead = 0;
    238     (*new)->direction = 0;

    240     /* Start out with no pollset.  apr_wait_for_io_or_timeout() will
    241      * initialize the pollset if needed.
    242      */
    243     (*new)->pollset = NULL;

    245     if (!(flag & APR_FOPEN_NOCLEANUP)) {
    246         apr_pool_cleanup_register((*new)->pool, (void *)(*new),
    247                                   apr_unix_file_cleanup,
    248                                   apr_unix_child_file_cleanup);
    249     }
    250     return APR_SUCCESS;
    251 }

この処理が失敗した場合がこのif文内の経路だ。
エラーログを出力して、HTTP_FORBIDDENが戻り値になっている。

   4335             ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00132)
   4336                           "file permissions deny server access: %s", r->filename);
   4337             return HTTP_FORBIDDEN;
   4338         }
    :

ファイルオープン後の処理に進む。

   4350         bb = apr_brigade_create(r->pool, c->bucket_alloc);

bucket brigade bbを作成する。
生成するのはrequestプール(r->pool)上になっている。

   4351
   4352         if ((errstatus = ap_meets_conditions(r)) != OK) {
   4353             apr_file_close(fd);
   4354             r->status = errstatus;
   4355         }
   4356         else {
   4357             e = apr_brigade_insert_file(bb, fd, 0, r->finfo.size, r->pool);

ここで、先に生成したapr_file_t情報 fdを指定して、FILEデータバケットを生成し、bucket brigade bbに追加する処理が行われる。
第3引数は対象ファイルのデータの先頭位置を指定している。0は先頭ということだ。
第4引数は、データサイズ。r->finfo.sizeには、ファイルサイズがセット済みだ。
この関数では、つまり、r->filenameで用意されたファイル全体をFILEデータバケットとして、bucket brigadeに追加している。

   4358
   4359 #if APR_HAS_MMAP
   4360             if (d->enable_mmap == ENABLE_MMAP_OFF) {
   4361                 (void)apr_bucket_file_enable_mmap(e, 0);
   4362             }
   4363 #endif
   4364         }
   4365
   4366         e = apr_bucket_eos_create(c->bucket_alloc);
   4367         APR_BRIGADE_INSERT_TAIL(bb, e);

EOSメタデータバケットを作成し、さらにbucket brigade bbに追加する。

   4368
   4369         status = ap_pass_brigade(r->output_filters, bb);

そして、この処理では、生成したbucket brigade bbを出力フィルタに引き渡している。
出力フィルタは、最終的にはCORE出力フィルタに引き渡され、AP_SENDFILE_ENABLED フラグが有効かどうかによって最終的な処理の方式は異なるが、このFILEデータバケットはソケットからクライアントにデータとして送信されることになる。

    :


今回はここまで

0 件のコメント:

コメントを投稿