(1) sendfile_nonblocking()
とそこから呼ばれるAPRライブラリ関数の
(2) apr_socket_sendfile()
を見る
(1)sendfile_nonblocking関数
FILEデータバケットをapr_socket_sendfile()を使って送信するための関数となる。apr_socket_sendfile()は本関数で1度実行される。
実行前に、ソケットのタイムアウト設定を0にし、実行後に元の値に戻す処理が行われている。
apr_socket_sendfile()実行後、ファイル全体の送信ができたか、送信サイズをファイルサイズを比較し、一致した場合には、送信されたFILEバケットをbucket brigadeから取り外し、削除(destroy)する。
未送信データが残っていた場合には、FILEバケットを送信済みデータ部分と未送信データ部分に
bucket分割し(split)、送信済み分のFILEバケットを取り外して、削除(destroy)する。
送信済みデータサイズは引数 *cumulative_bytes_writtenに加えられる。
834 static apr_status_t sendfile_nonblocking(apr_socket_t *s, 835 apr_bucket *bucket, 836 apr_size_t *cumulative_bytes_written, 837 conn_rec *c) 838 { 839 apr_status_t rv = APR_SUCCESS; 840 apr_bucket_file *file_bucket; 841 apr_file_t *fd; 842 apr_size_t file_length; 843 apr_off_t file_offset; 844 apr_size_t bytes_written = 0; 845 846 if (!APR_BUCKET_IS_FILE(bucket)) {
FILEデータバケットでなければ、エラーで返る。
847 ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006) 848 "core_filter: sendfile_nonblocking: " 849 "this should never happen"); 850 return APR_EGENERAL; 851 } 852 file_bucket = (apr_bucket_file *)(bucket->data);
FILEデータバケットのタイプ依存情報)apr_bucket_file)を取得する
853 fd = file_bucket->fd;
apr_bucket_fileの持つファイル情報(apt_file_t)を取得する
854 file_length = bucket->length; 855 file_offset = bucket->start;
ファイルの参照する領域(開始位置とサイズ)をbucket情報から取得する
856 857 if (bytes_written < file_length) {
ここで、apr_socket_sendfile()が実行される。
実行前にタイムアウトが0にされるのは非ブロック処理とするため。
858 apr_size_t n = file_length - bytes_written; 859 apr_status_t arv; 860 apr_interval_time_t old_timeout; 861 862 arv = apr_socket_timeout_get(s, &old_timeout);
現在セットされているタイムアウトの設定値をold_timeoutに退避する。
863 if (arv != APR_SUCCESS) { 864 return arv; 865 } 866 arv = apr_socket_timeout_set(s, 0);
タイムアウトの設定を0にする。
867 if (arv != APR_SUCCESS) {
868 return arv;
869 }
870 rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0);
apr_socket_sendfile()を実行する。
871 if (rv == APR_SUCCESS) {
成功すると、書込み済みバイト数を格納するbytes_writtenにnを加え、
開始位置であるfile_offsetがnバイト進められる。
しかし、この値はこの関数内では使われないので、この変更は意味が分からない。
872 bytes_written += n; 873 file_offset += n; 874 } 875 arv = apr_socket_timeout_set(s, old_timeout);
タイムアウトの設定をこの処理実行前の値に戻す。
876 if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) {
877 rv = arv;
878 }
879 }
880 if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) {
ap__logio_add_bytes_outはlogioモジュールが提供するオプション関数で、
ここでは、ログ書式%Oのための出力バイト数を集計している。
881 ap__logio_add_bytes_out(c, bytes_written);
882 }
883 *cumulative_bytes_written += bytes_written;
呼び出し元に返却用に、出力済みバイト数を*cumulative_bytes_writtenに加える。
884 if ((bytes_written < file_length) && (bytes_written > 0)) {
出力したバイト数が、送信する予定だったファイルサイズより小さい場合
885 apr_bucket_split(bucket, bytes_written);
送信済みの範囲と未送信の範囲にFILEデータバケットを分割(split)する。
apr_file_t 情報と FILEデータバケット (2.2)split関数 参照。
886 APR_BUCKET_REMOVE(bucket);
送信済みのFILEデータバケットをbucket brigadeから外す。
887 apr_bucket_destroy(bucket);
送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット (2.3)destroy関数参照。
888 } 889 else if (bytes_written == file_length) {
出力したバイト数と送信する予定だったファイルサイズが一致した場合
890 APR_BUCKET_REMOVE(bucket);
送信済みのFILEデータバケットをbucket brigadeから外す。
891 apr_bucket_destroy(bucket);
送信済みのFILEデータバケットを破棄(destroy)する。
apr_file_t 情報と FILEデータバケット (2.3)destroy関数参照。
892 } 893 return rv; 894 }
(2)apr_socket_sendfile
ちなみに、64bit環境で確認している。ヘッダ・フッターといった独自のデータ構造(apr_hdtr_t)を扱っており、
コード上、sendfile()の実行前後でこのデータが送信されているようだが、
sendfile_nonblocking()から呼ばれている場合には、この情報はNULL固定で、利用されていない。
(apr/network_io/unix/sendrecv.c)
257 apr_status_t apr_socket_sendfile(apr_socket_t *sock, apr_file_t *file,
258 apr_hdtr_t *hdtr, apr_off_t *offset,
259 apr_size_t *len, apr_int32_t flags)
260 {
261 int rv, nbytes = 0, total_hdrbytes, i;
262 apr_status_t arv;
263
264 #if APR_HAS_LARGE_FILES && defined(HAVE_SENDFILE64)
:
268 #elif APR_HAS_LARGE_FILES && SIZEOF_OFF_T == 4
:
279 #else
280 off_t off = *offset;
281
282 /* Multiple reports have shown sendfile failing with EINVAL if
283 * passed a >=2Gb count value on some 64-bit kernels. It won't
284 * noticably hurt performance to limit each call to <2Gb at a
285 * time, so avoid that issue here: */
一部の64bitカーネルにおいて、 2GB以上の値を渡すと、sendfileが失敗するという報告が複数ある。 一度の呼び出しでの上限を2GB未満に制限しても性能に悪影響は少ないだろうと考えられるので、 ここでその問題を回避している
286 if (sizeof(off_t) == 8 && *len > INT_MAX) {
INT_MAXは2GB-1(0x7fffffff)だ。 一度に送信するデータ長の上限をINT_MAXに制限している。
287 *len = INT_MAX; 288 } 289 #endif 290
ヘッダを送信する。
ファイル送信の前に送信するデータバケットが存在しているケースだ。
ただし、sendfile_nonblocking()から呼ばれている場合、hdtr==NULLだ。
2.4.9のソースをgrepする限り、他の箇所で呼ばれている気配はない。
2.2.27のソースをgrepすると、値が指定されているので、2.4で使わなくなったのか。
291 if (!hdtr) {
この経路に入る。
292 hdtr = &no_hdtr;
252 /* Define a structure to pass in when we have a NULL header value */ 253 static apr_hdtr_t no_hdtr;
293 } 294 295 if (hdtr->numheaders > 0) {
この経路には入らない。
296 apr_size_t hdrbytes;
297
298 /* cork before writing headers */
299 rv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 1);
TCP_CORKオプションをONにする。
オプションの説明を man tcpからコピペする。
セットされると、 partialフレームを送信しない。
このオプションが解除されると、キューイングされた partial
フレームが送られる。これは sendfile(2) を呼ぶ前にヘッダを前置したり、
スループットを最適化したい場合に便利である。現在の実装では、 TCP_CORK
で出力を抑えることができる時間の上限は 200 ミリ秒である。
この上限に達すると、キューイングされたデータは自動的に送信される。 Linux
2.5.71 以降においてのみ、このオプションを TCP_NODELAY
と同時に用いることができる。
移植性の必要なプログラムではこのオプションを用いるべきではない。
つまり、TCP送信が少し遅延するということだ。sendfileで送信されるデータと このヘッダデータが別個に送信されることを回避できる。
300 if (rv != APR_SUCCESS) { 301 return rv; 302 } 303 304 /* Now write the headers */ 305 arv = apr_socket_sendv(sock, hdtr->headers, hdtr->numheaders, 306 &hdrbytes); 307 if (arv != APR_SUCCESS) { 308 *len = 0; 309 return errno; 310 } 311 nbytes += hdrbytes; 312 313 /* If this was a partial write and we aren't doing timeouts, 314 * return now with the partial byte count; this is a non-blocking 315 * socket. 316 */ 317 total_hdrbytes = 0; 318 for (i = 0; i < hdtr->numheaders; i++) { 319 total_hdrbytes += hdtr->headers[i].iov_len; 320 } 321 if (hdrbytes < total_hdrbytes) {
ヘッダの一部だけ送信して終了した場合には、 sendfileまで行わないで、returnする。
322 *len = hdrbytes;
323 return apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
TCP_CORKをOFFにしている。 ヘッダがキューイングされていた場合には、これで送信される。
324 } 325 } 326
sendfileを実行する ここの処理の流れは、sendfileをwritevに置き換えれば、 apr_socket_sendvと同様だ。
327 if (sock->options & APR_INCOMPLETE_WRITE) { 328 sock->options &= ~APR_INCOMPLETE_WRITE; 329 goto do_select; 330 } 331 332 do { 333 rv = sendfile(sock->socketdes, /* socket */ 334 file->filedes, /* open file descriptor of the file to be sent */ 335 &off, /* where in the file to start */ 336 *len); /* number of bytes to send */ 337 } while (rv == -1 && errno == EINTR); 338 339 while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK) 340 && (sock->timeout > 0)) { 341 do_select: 342 arv = apr_wait_for_io_or_timeout(NULL, sock, 0); 343 if (arv != APR_SUCCESS) { 344 *len = 0; 345 return arv; 346 } 347 else { 348 do { 349 rv = sendfile(sock->socketdes, /* socket */ 350 file->filedes, /* open file descriptor of the file to be sent */ 351 &off, /* where in the file to start */ 352 *len); /* number of bytes to send */ 353 } while (rv == -1 && errno == EINTR); 354 } 355 } 356 357 if (rv == -1) { 358 *len = nbytes; 359 rv = errno; 360 apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0); 361 return rv; 362 } 363 364 nbytes += rv; 365 366 if (rv < *len) { 367 *len = nbytes; 368 arv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0); 369 if (rv > 0) { 370 371 /* If this was a partial write, return now with the 372 * partial byte count; this is a non-blocking socket. 373 */ 374 375 if (sock->timeout > 0) { 376 sock->options |= APR_INCOMPLETE_WRITE; 377 } 378 return arv; 379 } 380 else { 381 /* If the file got smaller mid-request, eventually the offset 382 * becomes equal to the new file size and the kernel returns 0. 383 * Make this an error so the caller knows to log something and 384 * exit. 385 */ 386 return APR_EOF; 387 } 388 } 389 390 /* Now write the footers */
フッターを送信する、とある。 ファイル送信の後に送信するデータバケットが存在しているケースだ。 ただし、上で見たとおり、sendfile_nonblocking()から呼ばれたケースでは、hdtrは空なので、 ここの処理も行われない。
391 if (hdtr->numtrailers > 0) {
sendfile_nonblocking()からでは、この経路には入らない。 処理内容は、ヘッダの時と同様だ。
392 apr_size_t trbytes; 393 arv = apr_socket_sendv(sock, hdtr->trailers, hdtr->numtrailers, 394 &trbytes); 395 nbytes += trbytes; 396 if (arv != APR_SUCCESS) { 397 *len = nbytes; 398 rv = errno; 399 apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0); 400 return rv; 401 } 402 } 403 404 apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0); 405 406 (*len) = nbytes; 407 return rv < 0 ? errno : APR_SUCCESS; 408 }
これで今回はおしまい。
0 件のコメント:
コメントを投稿