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データバケットはソケットからクライアントにデータとして送信されることになる。

    :


今回はここまで

2014年12月15日月曜日

CORE出力フィルタ: ソケット送信処理(1)

CORE出力フィルタ(core_output_filter)から呼び出されているソケット送信処理を実行する関数は以下の2つだった。

(1) send_brigade_nonblocking
(2) send_brigade_blocking

これを追ってみる。

(1)send_brigade_nonblocking関数


bucket brigade bb の先頭から、データを持っているデータバケットを対象に送信を行う。
ソケット送信を実行すると、この関数は(未送信のバケットがあっても)復帰する。
送信済みのbucketは、bbから外され、破棄される。

データバケットはFILEバケットをEnableSendFile on でsendfile()で送信する場合は、
ファイルサイズが256バイト以上であれば、sendfile()で送信する。
sendfileで完全に送信が完了しなかった場合には、FILEバケットが分割され、未送信のFILEバケットが bucket brigade bb に残される。

それ以外のデータバケットの場合、writev()で送信する。この時データバケットごとに
I/Oベクトルの配列にデータをセットし、一定数を超えた場合にはソケット送信を実行する。

あるいは、bucket brigade bbのすべてを処理終えたした時にも、送信データがあればソケット送信を行う。

全データが送信できなかった場合には、送信済みのデータバケットは破棄され、未送信の
データバケットが bucket brigade bb に残される。

以下詳細を見てみる。

(httpd-2.4.9/server/core_filters.c)

    616 static apr_status_t send_brigade_nonblocking(apr_socket_t *s,
    617                                              apr_bucket_brigade *bb,
    618                                              apr_size_t *bytes_written,
    619                                              conn_rec *c)

apr_socket_t *s 通信用のソケット情報
apr_bucket_brigade *bb 送信データ用bucket brigade
apr_size_t *bytes_written 出力データサイズ(関数側で返す)
conn_rec *c コネクション情報

    620 {
    621     apr_bucket *bucket, *next;
    622     apr_status_t rv;
    623     struct iovec vec[MAX_IOVEC_TO_WRITE];
    624     apr_size_t nvec = 0;
    625
    626     remove_empty_buckets(bb);

remove_empty_buckets(bb)は、bucket brigade bbを先頭から辿り、
メタデータバケットかlength==0のデータバケットが続いている間、
そのバケットを bb から取り除き、破棄している。

length!=0のデータバケットが残った場合に、bucket brigadeが処理される。

    627
    628     for (bucket = APR_BRIGADE_FIRST(bb);
    629          bucket != APR_BRIGADE_SENTINEL(bb);
    630          bucket = next) {
    631         next = APR_BUCKET_NEXT(bucket);
    632 #if APR_HAS_SENDFILE

CentOSにはsendfileは存在する

    633         if (APR_BUCKET_IS_FILE(bucket)) {

FILEバケットの場合、
(1)EnableSendFileディレクティブが offではない場合
かつ
(2)サイズがAP_MIN_SENDFILE_BYTES(256)バイト以上の場合
sendfileを使った処理を行う。

EnableSendfiFileがonであっても小さなファイルの送信にはsendfileは使われないようだ。

    634             apr_bucket_file *file_bucket = (apr_bucket_file *)(bucket->data);
    635             apr_file_t *fd = file_bucket->fd;
    636             /* Use sendfile to send this file unless:
    637              *   - the platform doesn't support sendfile,
    638              *   - the file is too small for sendfile to be useful, or
    639              *   - sendfile is disabled in the httpd config via "EnableSendfile off"
    640              */
    641
    642             if ((apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) &&
    643                 (bucket->length >= AP_MIN_SENDFILE_BYTES)) {


     56 #define AP_MIN_SENDFILE_BYTES           (256)


    644                 if (nvec > 0) {

nvecは初期値は0
bucket brigadeを処理していく中で、I/Oベクトルにデータを追加している。
そのバッファされているI/Oベクトルのデータを出力しておく。

    645                     (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 1);
    646                     rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c);
    647                     nvec = 0;
    648                     if (rv != APR_SUCCESS) {
    649                         (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0);
    650                         return rv;
    651                     }
    652                 }
    653                 rv = sendfile_nonblocking(s, bucket, bytes_written, c);

FILEバケットをsendfileで送信する。
出力可能でなければ、ただちに復帰する。
ファイル全体を送信できた場合には、このbucketをbucket brigadeから取り除いて、apr_bucket_destroy()
を行う。
一部のみ送信できた場合は、このbucketを送信済みの部分と残った部分の2つに分割し、
送信済みのバケットを取り除き、apr_bucket_destroyを行う。


    654                 if (nvec > 0) {


ここには来ないのではないか(647行目で0になっている)。


    655                     (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0);
    656                 }
    657                 if (rv != APR_SUCCESS) {
    658                     return rv;
    659                 }
    660                 break;


出力したら、処理を終える(forループを抜ける)。




    661             }
    662         }
    663 #endif /* APR_HAS_SENDFILE */
    664         /* didn't sendfile */
    665         if (!APR_BUCKET_IS_METADATA(bucket)) {

このif文はデータバケットの場合の処理

    666             const char *data;
    667             apr_size_t length;
    668
    669             /* Non-blocking read first, in case this is a morphing
    670              * bucket type. */
    671             rv = apr_bucket_read(bucket, &data, &length, APR_NONBLOCK_READ);

データバケットからデータを読み込む。
読込は非ブロックモードで行っている。

    672             if (APR_STATUS_IS_EAGAIN(rv)) {

上流から渡ってきているbucket brigadeにPIPEバケットやSOCKETバケットがあった場合、非ブロックで読み込んで、読み込みデータがなければ、この経路に入る。

    673                 /* Read would block; flush any pending data and retry. */
    674                 if (nvec) {

ここは、既にデータバケットを処理しており、未送信のものが存在している場合だ。

    675                     rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c);

I/Oベクトルのデータのソケット送信を実施する。

    676                     if (rv) {

ここは rv!=APR_SUCCESS(0)の場合の経路となる。

    677                         return rv;
    678                     }
    679                     nvec = 0;

nvecの値は0でリセットする。
この経路では、データがソケット送信された場合にも、引き続き処理が行われている。

    680                 }
    681
    682                 rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ);

そして、次にブロックモードでデータを読み込む。

    683             }
    684             if (rv != APR_SUCCESS) {

671行目のapr_bucket_read(APR_NONBLOCK_READ)の戻り値がEAGAIN以外のエラーか、
682行目のapr_bucket_read(APR_BLOCK_READ)がエラーの場合に
この経路に入る。

ここでは、エラーでreturn している。

    685                 return rv;
    686             }
    687
    688             /* reading may have split the bucket, so recompute next: */
    689             next = APR_BUCKET_NEXT(bucket);
    690             vec[nvec].iov_base = (char *)data;
    691             vec[nvec].iov_len = length;
    692             nvec++;

読み込んだデータをI/Oベクトルに追加する。

    693             if (nvec == MAX_IOVEC_TO_WRITE) {

I/Oベクトルの要素数がMAX_IOVEC_TO_WRITEになったら、出力を実行する。

    694                 rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c);

writev()関数を使って、I/Oベクトルの情報を出力する。
出力可能でなけばただちに復帰する。
出力完了したデータサイズに応じて、bucket brugade bb のlengthを消化して、
bucketをbb から取り除き、apr_bucket_destroy()を行う。
bucketのデータの一部しか送信できていない場合には、そのbucketを分割し、
送信済みのbucketを取り外して、apr_bucket_destroy()を行っている。

    695                 nvec = 0;

nvecは出力後、0にリセットする。

    696                 if (rv != APR_SUCCESS) {
    697                     return rv;
    698                 }
    699                 break;

出力したら、処理を終える(forループを抜ける)。

    700             }
    701         }
    702     }
    703
    704     if (nvec > 0) {

bucket brigadeを最後までチェックして、MAX_IOVEC_TO_WRITEにならなかった場合に
ここで出力する。

    705         rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c);
    706         if (rv != APR_SUCCESS) {
    707             return rv;
    708         }
    709     }
    710
    711     remove_empty_buckets(bb);
    712
    713     return APR_SUCCESS;
    714 }


(2)send_brigade_blocking関数


送信処理だが、こちらはブロックモードだ。
実装では、send_brigade_nonblocking()を呼んでいる。

ブロックモードでは、send_brigade_nonblocking()を実行して、結果がEAGAINの場合(書込みできない)、apr_socket_t情報のtimeoutの設定値を取得して、poll()で書込み可能になるまで待機する。

timeoutになる前に書込み可能になれば、再度send_brigade_nonblocking()を実行する。
timeoutした場合は、APR_TIMEUPでreturnする。
その他のエラーの場合もエラーでreturnする。

つまり、send_brigade_nonblockingと異なり、非ブロックで書き込みを行い、ソケットの書込み準備ができていない場合には、書込み可能になるまで、Timeoutディレクティブ等で指定されているタイムアウト値に従って待機するようになっている。

    726 static apr_status_t send_brigade_blocking(apr_socket_t *s,
    727                                           apr_bucket_brigade *bb,
    728                                           apr_size_t *bytes_written,
    729                                           conn_rec *c)
    730 {
    731     apr_status_t rv;
    732
    733     rv = APR_SUCCESS;
    734     while (!APR_BRIGADE_EMPTY(bb)) {
    735         rv = send_brigade_nonblocking(s, bb, bytes_written, c);

ここでsend_brigade_nonblocking()が実行されている。

    736         if (rv != APR_SUCCESS) {
    737             if (APR_STATUS_IS_EAGAIN(rv)) {

ソケットの書込み準備ができていなかった場合にこの経路に入る。


    738                 /* Wait until we can send more data */
    739                 apr_int32_t nsds;
    740                 apr_interval_time_t timeout;
    741                 apr_pollfd_t pollset;
    742
    743                 pollset.p = c->pool;
    744                 pollset.desc_type = APR_POLL_SOCKET;
    745                 pollset.reqevents = APR_POLLOUT;
    746                 pollset.desc.s = s;
    747                 apr_socket_timeout_get(s, &timeout);

apr_socket_t情報のtimeout設定を取得している。

    748                 do {
    749                     rv = apr_poll(&pollset, 1, &nsds, timeout);

ソケットが書き込み可能になるまで、所定の時間待つ。
シグナルで割りこまれた場合(EINTR)には処理を継続する。

    750                 } while (APR_STATUS_IS_EINTR(rv));
    751                 if (rv != APR_SUCCESS) {

apr_poll()でEINTR以外の何らかのエラーが発生したか、タイムアウトの場合の経路だ。

    752                     break;
    753                 }
    754             }
    755             else {

send_brigade_nonblockingがEAGAIN以外のエラーだった場合の経路になる

    756                 break;
    757             }
    758         }

send_brigade_nonblocking()が成功した場合、あるいは、ソケットが書き込み可能になった場合、
bucket brigadeが空になるまでsend_brigade_nonblocking()処理を継続する。

    759     }

send_brigade_nonblocking処理は、送信済みのbucketを破棄するので、すべて送信し終えれば、bucket brigadeが空となり
この734行からのwhile()ループを抜ける。

    760     return rv;
    761 }

以上、2関数を見たが、ここでも最終的なソケット送信関数は呼ばれていなかった。
ここでは送信処理として次の関数から呼ばれてる。

  • sendfile_nonblocking
  • writev_nonblocking

実際のところ、上記関数の内部を見ても、ソケット送信関数は呼ばれていない。
それぞれは、さらに次のAPRライブラリ関数を使用し、そこでようやく直接ソケットディスクリプタを扱う関数にたどり着く。

  • apr_socket_sendfile
  • apr_socket_sendv

もう少しなので、辿ってみることにする。

2014年12月8日月曜日

入力フィルタ: CORE_IN入力フィルタ

CORE_IN入力フィルタ: ap_core_input_filter


CORE_IN入力フィルタは、入力フィルタの最下層のネットワークフィルタになる。
この下には入力フィルタはないので、CIRE_IN入力フィルタでは、ネットワークからクライアントの送ってきたデータを受信し、処理して、上位の入力フィルタに引き渡す処理が行われる。
以前、入力フィルタの概要で少し見たが、今回はもう少し細かく全体を見ていく。

     94 apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b,
     95                                   ap_input_mode_t mode, apr_read_type_e block,
     96                                   apr_off_t readbytes)

引数は次の通り。

引数 説明
ap_filter_t *f 入力フィルタ情報。fはap_core_input_filterに関する情報を格納する。
apr_bucket_brigade *b 吸い上げたデータを格納する宛先のbucket brigade。
ap_input_mode_t mode 入力モード。
AP_MODE_READBYTES 入力フィルタは最大readbytesバイト読み込んでreturn する
AP_MODE_GETLINE 入力フィルタは、1行(CRLF)分のデータを読み込んでreturnする。(1行が長すぎたり、あるいは、CRLFがないような場合には部分的なデータを読み込んでreturnする可能性がある)
AP_MODE_EATCRLF 入力フィルタは、CRLFを見つけたら消費(除去)する
AP_MODE_SPECULATIVE 入力フィルタの読み込みは投機的なものとみなされる。読み込まれたデータは、以降の別のモードでの処理のために蓄積される。
AP_MODE_EXHAUSTIVE 入力フィルタの読み込みは完全なものとみなされる。それ以上読込できなくなるまで読込が行われる。このモードは特に注意して扱うこと。
AP_MODE_INIT 入力フィルタはコネクションを初期化する。NNTP over SSL やFTP over SSL等で必要。
apr_read_type_e block 読込タイプ
APR_BLOCK_READ ブロックモード
APR_NONBLOCK_READ 非ブロックモード
apr_off_t readbytes 入力データサイズ


2014年12月1日月曜日

apr_socket_t 情報と SOCKETデータバケット

(1)SOCKETデータバケットタイプ情報

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 };

(2)apr_socket_t 情報のセットアップ


SOCKETデータバケットで、独自の実装を持っているのはread関数だけだ。
それをみておきたいが、その前に、ソケット情報(apr_socket_t)を生成する listenerスレッドのacceptの処理部分を見たい。
listenerスレッドのListenソケットの処理ではacceptの処理について触れた。


(httpd-2.4.9/server/mpm/event/event.c)
   1368 static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy)
   1369 {
    :
   1616                     rc = lr->accept_func(&csd, lr, ptrans);

accept_func()の実体は、ap_unixd_accept()だ。
(httpd-2.4.9/server/mpm/event/event.c)
   1122 static apr_status_t init_pollset(apr_pool_t *p)
   1123 {
    :
   1151         lr->accept_func = ap_unixd_accept;

ap_unixd_accept()関数で、ソケットがacceptされ、apr_socket_t情報が作成される。
このapr_socket_t情報は、listenerスレッドの、void*型の変数csdにアドレスがセットされる。
(httpd-2.4.9/os/unix/unixd.c)
    291 AP_DECLARE(apr_status_t) ap_unixd_accept(void **accepted, ap_listen_rec *lr,
    292                                          apr_pool_t *ptrans)
    293 {
    294     apr_socket_t *csd;
    :
    300     *accepted = NULL;
    301     status = apr_socket_accept(&csd, lr->sd, ptrans);
    302     if (status == APR_SUCCESS) {
    303         *accepted = csd;
この*acceptedが、listenerスレッドの処理のcsdとなっている。


apr_socket_t情報は以下の通り。
(apr-1.5.1/include/arch/unix/apr_arch_networkio.h)

    103 struct apr_socket_t {
    104     apr_pool_t *pool;
    105     int socketdes;
    106     int type;
    107     int protocol;
    108     apr_sockaddr_t *local_addr;
    109     apr_sockaddr_t *remote_addr;
    110     apr_interval_time_t timeout;
    111 #ifndef HAVE_POLL
     :
    113 #endif
    114     int local_port_unknown;
    115     int local_interface_unknown;
    116     int remote_addr_unknown;
    117     apr_int32_t options;
    118     apr_int32_t inherit;
    119     sock_userdata_t *userdata;
    120 #ifndef WAITIO_USES_POLL
     :
    123 #endif
    124 };

もう少し先を辿る。
apr_socket_accept()だ。