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()だ。



これは、APRライブラリに実装されている。
ここでは、第2引数に指定されたsock->socketdesをacceptし、新しい通信ソケットを第1引数apr_socket_t *newに返す。

(apr-1.5.1/network_io/unix/sockets.c)

    201 apr_status_t apr_socket_accept(apr_socket_t **new, apr_socket_t *sock,
    202                                apr_pool_t *connection_context)
    203 {
    204     int s;
    205     apr_sockaddr_t sa;
    206
    207     sa.salen = sizeof(sa.sa);
    208
    209 #ifdef HAVE_ACCEPT4
    210     {
    211         int flags = SOCK_CLOEXEC;
     :
    222         s = accept4(sock->socketdes, (struct sockaddr *)&sa.sa, &sa.salen, flags);
    223     }

手元の実装ではaccept4が利用できるようだ。
man accept4 を見ると、第4引数のflagsにはSOCK_NONBLOCK、SOCK_CLOEXECが指定できるとある(flags==0の場合は、通常のacceptと同じ動きとなる)。
ここではSOCK_CLOEXECフラグが指定されている。
このフラグがONの場合 execv()の実行時にファイルディスクリプタが自動的にクローズされるようになる。
つまり、これを実行中の別のスレッドでfork() & execv()が実行された場合、このフラグが無効だと、accept()されたソケットがオープン状態のまま残される可能性があるが、これをONにすることで、その場合に自動的にクローズされるようにできる。

    224 #else
     :
    226 #endif
    227
    228     if (s < 0) {
    229         return errno;
    230     }
     :
    237     alloc_socket(new, connection_context);
    238
    239     /* Set up socket variables -- note that it may be possible for
    240      * *new to be an AF_INET socket when sock is AF_INET6 in some
    241      * dual-stack configurations, so ensure that the remote_/local_addr
    242      * structures are adjusted for the family of the accepted
    243      * socket: */
    244     set_socket_vars(*new, sa.sa.sin.sin_family, SOCK_STREAM, sock->protocol);
    245
     :
    249     (*new)->timeout = -1;
    250
    251     (*new)->remote_addr_unknown = 0;
    252
    253     (*new)->socketdes = s;
    254
    255     /* Copy in peer's address. */
    256     (*new)->remote_addr->sa = sa.sa;
    257     (*new)->remote_addr->salen = sa.salen;
    258
    259     *(*new)->local_addr = *sock->local_addr;
    260
    261     /* The above assignment just overwrote the pool entry. Setting the local_addr
    262        pool for the accepted socket back to what it should be.  Otherwise all
    263        allocations for this socket will come from a server pool that is not
    264        freed until the process goes down.*/
    265     (*new)->local_addr->pool = connection_context;
    266
    267     /* fix up any pointers which are no longer valid */
    268     if (sock->local_addr->sa.sin.sin_family == AF_INET) {
    269         (*new)->local_addr->ipaddr_ptr = &(*new)->local_addr->sa.sin.sin_addr;
    270     }
    271 #if APR_HAVE_IPV6
    272     else if (sock->local_addr->sa.sin.sin_family == AF_INET6) {
    273         (*new)->local_addr->ipaddr_ptr = &(*new)->local_addr->sa.sin6.sin6_addr;
    274     }
    275 #endif
    276     (*new)->remote_addr->port = ntohs((*new)->remote_addr->sa.sin.sin_port);
    277     if (sock->local_port_unknown) {
    278         /* not likely for a listening socket, but theoretically possible :) */
    279         (*new)->local_port_unknown = 1;
    280     }
    281
    282 #if APR_TCP_NODELAY_INHERITED
    283     if (apr_is_option_set(sock, APR_TCP_NODELAY) == 1) {
    284         apr_set_option(*new, APR_TCP_NODELAY, 1);

APR_TCP_NODELAYをONにする処理はリクエスト処理の流れcore_pre_connectionの処理でも見た。
Nagle(ネーグル)アルゴリズムを無効にする。

    285     }
    286 #endif /* TCP_NODELAY_INHERITED */
     :
    292
    293     if (sock->local_interface_unknown ||
    294         !memcmp(sock->local_addr->ipaddr_ptr,
    295                 generic_inaddr_any,
    296                 sock->local_addr->ipaddr_len)) {
    297         /* If the interface address inside the listening socket's local_addr wasn't
    298          * up-to-date, we don't know local interface of the connected socket either.
    299          *
    300          * If the listening socket was not bound to a specific interface, we
    301          * don't know the local_addr of the connected socket.
    302          */
    303         (*new)->local_interface_unknown = 1;
    304     }
    305
     :
    318
    319     (*new)->inherit = 0;
    320     apr_pool_cleanup_register((*new)->pool, (void *)(*new), socket_cleanup,
    321                               socket_cleanup);
    322     return APR_SUCCESS;
    323 }
新しいapr_socket_t情報を見ると、
249行目でtimeoutは-1がセットされている。
また、初期状態ではソケットはブロックモードになっている。

apr_socket_t情報のoptions変数には、次のようなフラグがセットされている(一部)。
(apr-1.5.1/include/apr_network_io.h)

     63 #define APR_SO_LINGER        1    /**< Linger */
     64 #define APR_SO_KEEPALIVE     2    /**< Keepalive */
     65 #define APR_SO_DEBUG         4    /**< Debug */
     66 #define APR_SO_NONBLOCK      8    /**< Non-blocking IO */
     67 #define APR_SO_REUSEADDR     16   /**< Reuse addresses */
     68 #define APR_SO_SNDBUF        64   /**< Send buffer */
     69 #define APR_SO_RCVBUF        128  /**< Receive buffer */
     70 #define APR_SO_DISCONNECTED  256  /**< Disconnected */
     71 #define APR_TCP_NODELAY      512  /**< For SCTP sockets, this is mapped
     72                                    * to STCP_NODELAY internally.
     73                                    */
     74 #define APR_TCP_NOPUSH       1024 /**< No push */
     :
fcntl()やsetsockopt()でソケットのオプションを設定した場合、このoptionsも同期してフラグをON/OFFしている。
optionsは初期状態では512、つまり、APR_TCP_NODELAYのフラグが立っている。
apr_socket_accept()の283行目の処理でこのオプションが有効化されている状態だ。

このソケット情報は引き続きworkerスレッドで処理される。
process_socket処理はすでにみた。
ここで見た、pre_connectinoフック関数である、core_pre_connection関数で幾つかのソケットオプションが指定される。
その部分を再掲する。
(httpd-2.4.9/server/core.c)

   4637 static int core_pre_connection(conn_rec *c, void *csd)
   4638 {
    :
   4649     rv = apr_socket_opt_set(csd, APR_TCP_NODELAY, 1);
    :
   4664     rv = apr_socket_timeout_set(csd, c->base_server->timeout);
4649行目は、既に有効化されているAPR_TCP_NODELAYフラグの処理だ。
4664行目は、ソケット情報のtimeout変数を設定のTimeoutディレクティブの値で更新する。
デフォルトの場合、Timeoutの値は60秒になっている。

(apr-1.5.1/network_io/unix/sockopt.c)

     75 apr_status_t apr_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
     76 {
     77     apr_status_t stat;
     78
     79     /* If our new timeout is non-negative and our old timeout was
     80      * negative, then we need to ensure that we are non-blocking.
     81      * Conversely, if our new timeout is negative and we had
     82      * non-negative timeout, we must make sure our socket is blocking.
     83      * We want to avoid calling fcntl more than necessary on the
     84      * socket.
     85      */
     86     if (t >= 0 && sock->timeout < 0) {
     87         if (apr_is_option_set(sock, APR_SO_NONBLOCK) != 1) {
     88             if ((stat = sononblock(sock->socketdes)) != APR_SUCCESS) {
     89                 return stat;
     90             }
     91             apr_set_option(sock, APR_SO_NONBLOCK, 1);
     92         }
     93     }
     94     else if (t < 0 && sock->timeout >= 0) {
     95         if (apr_is_option_set(sock, APR_SO_NONBLOCK) != 0) {
     96             if ((stat = soblock(sock->socketdes)) != APR_SUCCESS) {
     97                 return stat;
     98             }
     99             apr_set_option(sock, APR_SO_NONBLOCK, 0);
    100         }
    101     }
ここでは、
元のtimeout 変更後のtimeout(t) 処理
timeout <0 t >=0 ソケットを非ブロックに変更する
内部変数のoptionsの値を変更(APR_SO_NONBLOCKをオン)
timeout >=0 t <0 ソケットをブロックに変更する
内部変数のoptionsの値を変更(APR_SO_NONBLOCKをオフ)
という処理が行われている。


    102     /* must disable the incomplete read support if we disable
    103      * a timeout
    104      */
    105     if (t <= 0) {
    106         sock->options &= ~APR_INCOMPLETE_READ;


変更後の値が<=0の場合、optionsのAPR_INCOMPLETE_READをオフにする。


    107     }
    108     sock->timeout = t;


そして、内部変数のtimeout値を変更する。


    109     return APR_SUCCESS;
    110 }
ここで、デフォルトへの変更を考えると、元のtimeout値は-1、これを60000000マイクロ秒に更新する。
従って、ここでソケットを非ブロックに変更する処理が実行されることになる。
optionsの値は520になる。つまり、(APR_TCP_NODELAY|APR_SO_NONBLOCK)だ。

ここまででソケットの初期設定が完了する。

(3)apr_socket_t 情報の破棄

apr_socket_accept関数の末尾を再掲する。
(apr-1.5.1/network_io/unix/sockets.c)

    201 apr_status_t apr_socket_accept(apr_socket_t **new, apr_socket_t *sock,
    202                                apr_pool_t *connection_context)
    203 {
    :
    320     apr_pool_cleanup_register((*new)->pool, (void *)(*new), socket_cleanup,
    321                               socket_cleanup);
    322     return APR_SUCCESS;
    323 }
apr_pool_cleanup_register()は指定されたメモリプールが破棄されるとき(apr_pool_clear()やapr_pool_destroy())に実行される関数を登録する。

ここでの実行関数はsocket_cleanup()。引数に、(void *)(*new)が渡される。
つまり、(*new)->poolが破棄されるときに、socket_cleanupが実行される。

socket_cleanup関数は下記の通り。
(apr-1.5.1/network_io/unix/sockets.c)

     31 static apr_status_t socket_cleanup(void *sock)
     32 {
     33     apr_socket_t *thesocket = sock;
     34     int sd = thesocket->socketdes;
     35
     36     /* Set socket descriptor to -1 before close(), so that there is no
     37      * chance of returning an already closed FD from apr_os_sock_get().
     38      */
     39     thesocket->socketdes = -1;
     40
     41     if (close(sd) == 0) {
     42         return APR_SUCCESS;
     43     }
     44     else {
     45         /* Restore, close() was not successful. */
     46         thesocket->socketdes = sd;
     47
     48         return errno;
     49     }
     50 }
apr_socket_t自体はそのままで、socketdes変数にセットされているソケットディスクリプタに対してclose()を実行し、
変数には-1をセットしている。

なお、ソケットのクローズには次のapr_socket_close関数も利用される。
(apr-1.5.1/network_io/unix/sockets.c)

    172 apr_status_t apr_socket_close(apr_socket_t *thesocket)
    173 {
    174     return apr_pool_cleanup_run(thesocket->pool, thesocket, socket_cleanup);
    175 }
apr_pool_cleanup_run()関数は、第1引数のpoolに登録されている、socket_cleanup関数を削除し、その後、
socket_cleanup(thesocket)を実行する処理だ。
これを利用することで、元のpoolが破棄される際にsocket_cleanup()が二重に呼び出されることを回避できる。

(4)socket_bucket_read関数

次に、SOCKETデータバケットのread関数の実装を確認する。
(apr-util-1.5.4/buckets/apr_buckets_socket.c)

     19 static apr_status_t socket_bucket_read(apr_bucket *a, const char **str,
     20                                        apr_size_t *len, apr_read_type_e block)
     21 {
     22     apr_socket_t *p = a->data;


apr_bucket_t 情報のバケットタイプ依存情報は、SOCKETの場合、apr_socket_tとなる。


     23     char *buf;
     24     apr_status_t rv;
     25     apr_interval_time_t timeout;
     26
     27     if (block == APR_NONBLOCK_READ) {
ここは、非ブロックモード指定の場合の処理だ。


     28         apr_socket_timeout_get(p, &timeout);

apr_socket_t情報のtimeout変数の値をtimeoutに返す

     29         apr_socket_timeout_set(p, 0);

apr_socket_timeout_set関数は、先に見ておいた。
ここでは、apr_socket_tのtimeout情報を0にしている。
元のTImeoutがデフォルトの60秒の場合、

  • 29行目 60→ 0
  • 39行目 0→60

という変更になる。
この場合変更前の値も変更後の値も0以上なので、ブロックモードの処理は行われない(非ブロック状態)。

     30     }
     31
     32     *str = NULL;
     33     *len = APR_BUCKET_BUFF_SIZE;
     34     buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */

ここで受信用のバッファを確保している。

     35
     36     rv = apr_socket_recv(p, buf, len);

ソケットの受信処理を実行する。

(apr-1.5.1/network_io/unix/sendrecv.c)

     70 apr_status_t apr_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
     71 {
     72     apr_ssize_t rv;
     73     apr_status_t arv;
     74
     75     if (sock->options & APR_INCOMPLETE_READ) {


APR_INCOMPLETE_READフラグはソケットに対する操作が行われない、apr_socket_tのoptions独自のフラグだ。
非ブロックモードのソケット(timeout!=0)に対して、
直前の読み込みでバッファを満たすだけのデータの読み込みが行われなかった場合に、
その次のこのapr_socket_recv()処理において、いきなりread(81行目)を行うのではなく、まずpoll(87行目)を行わせるための
フラグだ。

このフラグは、この関数の103行目で条件を満たすとONになり、ここ(次のapr_socket_recv)でOFFになる。
その他、apr_socket_timeout_set()で変更後の値が0以下の場合にもOFFになる。
なお、プログラムで個別にON/OFFが適用される例もある。


     76         sock->options &= ~APR_INCOMPLETE_READ;
     77         goto do_select;
     78     }
     79
     80     do {
     81         rv = read(sock->socketdes, buf, (*len));


ソケットの読込処理。


     82     } while (rv == -1 && errno == EINTR);
     83
     84     while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
     85                       && (sock->timeout > 0)) {
     86 do_select:
     87         arv = apr_wait_for_io_or_timeout(NULL, sock, 1);


このapr_wait_for_io_or_timeout関数では、sock->socketdesをPOLLINに指定してpoll()を実行する。
timeoutはapr_socket_t情報のtimeoutを使用する。
戻り値は次の通り。
 APR_TIMEUP : タイムアウト
 APR_SUCCESS: 受信可能
 エラーコード(errno): エラー時


     88         if (arv != APR_SUCCESS) {

ここは、pollタイムアウトまたはエラー時の処理となる。

     89             *len = 0;
     90             return arv;
     91         }
     92         else {
ここは、受信可能な場合の処理だ。
read()が実行されている。

     93             do {
     94                 rv = read(sock->socketdes, buf, (*len));
     95             } while (rv == -1 && errno == EINTR);
     96         }
     97     }
     98     if (rv == -1) {

ここは、read()エラー時の処理となる。

     99         (*len) = 0;
    100         return errno;
    101     }
    102     if ((sock->timeout > 0) && (rv < *len)) {

timeout>0の場合でかつ、受信したデータがバッファサイズより小さかった場合にAPR_INCOMPLETE_READフラグを立てる。

    103         sock->options |= APR_INCOMPLETE_READ;
    104     }
    105     (*len) = rv;

読み込みデータ長を引数*lenにセットする。

    106     if (rv == 0) {

read()の返り値が0の場合は、APR_EOFを返す。
*lenも0になっている。

    107         return APR_EOF;
    108     }

その他の場合はAPR_SUCCESSで返る。

    109     return APR_SUCCESS;
    110 }


     37
     38     if (block == APR_NONBLOCK_READ) {

非ブロックモード指定の場合の処理。

     39         apr_socket_timeout_set(p, timeout);

29行目で0にしたapr_socket_t情報のtimeout値を、28行目で退避した元の値に戻している。

     40     }
     41
     42     if (rv != APR_SUCCESS && rv != APR_EOF) {

APR_EOF以外のエラーの場合の経路。
確保したバッファを解放して、エラーを返している。

     43         apr_bucket_free(buf);
     44         return rv;
     45     }
     46     /*
     47      * If there's more to read we have to keep the rest of the socket
     48      * for later. XXX: Note that more complicated bucket types that
     49      * refer to data not in memory and must therefore have a read()
     50      * function similar to this one should be wary of copying this
     51      * code because if they have a destroy function they probably
     52      * want to migrate the bucket's subordinate structure from the
     53      * old bucket to a raw new one and adjust it as appropriate,
     54      * rather than destroying the old one and creating a completely
     55      * new bucket.
     56      *
     57      * Even if there is nothing more to read, don't close the socket here
     58      * as we have to use it to send any response :)  We could shut it
     59      * down for reading, but there is no benefit to doing so.
     60      */
     61     if (*len > 0) {

データを受信した場合の処理となる。
この場合、作成した受信バッファをベースにHEAPバケットを作成して、bucket aを置き換えている。

     62         apr_bucket_heap *h;
     63         /* Change the current bucket to refer to what we read */
     64         a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
     65         h = a->data;
     66         h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */

HEAPデータバケットのバケットタイプ依存データは apr_bucket_heap情報で、
このalloc_lenを更新している。

     67         *str = buf;

引数で渡された *strには、確保したバッファのアドレスを返す。

     68         APR_BUCKET_INSERT_AFTER(a, apr_bucket_socket_create(p, a->list));

ここでは、元のSOCKETバケットから取り出したapr_socket_t情報 p をもとにSOCKETデータバケットを新たに作成しなおし、
それを、aを置き換えたHEAPデータバケットの後ろ(next)に追加している。

     69     }
     70     else {

*len>=0の場合の処理(EOFの場合の処理)
確保したバッファを解放して、空文字列のIMMORTALデータバケットを生成し、bucket aを置き換えている(上書き)。
これにより、SOCKETデータバケットが失われている。
EOFのソケットはread処理の対象ではないが、データ送信には使われる可能性があるので、特に
ここではソケットに対するクローズ処理などは行われていない。
コメントには、read側をshutdownすることはできるが、意味はない、と書かれている。

     71         apr_bucket_free(buf);
     72         a = apr_bucket_immortal_make(a, "", 0);

IMMORTALデータバケットを生成している。
apr_bucket_immortal_createの場合、apr_bucket_t領域を確保し、各値をIMMORTALバケットタイプに設定するが、
apr_buclet_immortal_makeの場合、apr_bucket_t領域としては、aを使いまわし、ここでは内部のデータに定数空文字列("")をセットしている。
IMMORTALデータバケットのデータは、destroy()では破棄されない。定数データなどで使用されている。
他方、HEAPバケットの場合は、destroy()関数で、使用しているメモリ領域を解放する。
ちなみに、TRANSIENTデータバケットはIMMORTALデータバケットと似ているが、setaside関数が実装されている、これによりHEAPデータバケットに変換できる点が異なっている。

これにより、aに存在していたSOCKETデータバケットはIMMORTALデータバケットで上書きされた(上記の通り、特にクローズなど行われない)。

     73         *str = a->data;

a->dataには "" がポイントされている。

     74     }
     75     return APR_SUCCESS;
     76 }

今回はここまで。

0 件のコメント:

コメントを投稿