受信処理アルゴリズム

まず、プロセス側の動きから説明する。

アプリケーションからのデータ受信処理要求は、socketレイヤを経由しinet_recvmsg関数を呼び出す(struct proto_ops inet_stream_opsインターフェイステーブル経由)ことにより実現される。inet_recvmsg関数は、即tcp_recvmsg関数を呼び出す。(struct proto tcp_protインターフェイステーブル経由)。

tcp_recvmsg関数は、ソケットのreceive_queueにリンクされているパケットのデータをユーザ空間にコピーし、パケットを解放する。もしreceive_queueにデータが無い場合は、パケット到着を待つ。

img108.gif

tcp_recvmsg関数は、ソケットのreceive_queueの先頭から順にパケット内を参照(skb_peek関数)し、指定されたサイズ分だけパケット内のデータをユーザ空間にコピー(memcpy_toiovec関数)する。処理は複数のパケットに跨ることもある。(この時点ではreceive_queueから外さない)

目的としたサイズのデータの読み込みを終了すると、完全にデータを読みだされたパケットの後処理を行う(cleanup_rbuf関数)。cleanup_rbuf関数は、ソケットのreceive_queueから不必要になったパケットを外し、パケットを捨てる(tcp_eat_skb関数)。またtcp_read_wakeup関数により、受信windowサイズ大きくなったことを送信相手に即通知する(tcp_send_ack関数)。これによりフロー制御で停止していた送信処理(相手からこちら)が即再開される。

もし目的としたサイズを満たすデータがreceive_queueにつながっているパケットだけでは不十分な場合、パケット到着待ちの状態に入る(tcp_data_wait関数)。(指定されたオプションにより、期待されたサイズ以下でも処理を完了させたり、 データが全くない場合はエラーにすることもある)

しばらくしてパケット到着すると、ソケットで待ちに入っているプロセスを起床する。起床したプロセスはtcp_recvmsg処理を再開する。

img109.gif

次に、割り込み側のパケット受信処理に関して説明する。

受信パケットはtcp_v4_rcv関数経由でTCPプロトコルスタックに送られる。tcp_v4_rcv関数は、受信パケットの送信ポート番号(TCPヘッダのdestフィールド)を元に、TCP port番号キャッシュを検索し、そのポートにbindされているソケット(またはそれに相当するもの)をわりだす(tcp_v4_lookup関数)。

わりだした結果、それがTCP_TIME_WAIT状態のtcp_tw_bucket構造体(ソケットに相当する)であった場合は、tcp_timewait_state_process関数を呼び出す。この先の処理に関してはコネクションの切断処理の章で説明する。

TCP_TIME_WAIT状態以外の場合はtcp_v4_do_rcv関数を呼び出す。tcp_v4_do_rcv関数は、受信するソケットの状態により呼び出す関数を切り替える。TCP_ESTABLISHED状態のソケットであればtcp_rcv_established関数を呼び出す。TCP_LISTEN状態のソケットであればtcp_v4_hnd_req関数を呼び出す。それ以外の状態のソケットであればtcp_rcv_state_process関数を呼び出す。

tcp_v4_hdn_req関数、tcp_rcv_state_process関数にの処理に関しては後の章(コネクションの確立と切断)で説明する。ここではコネクション確立状態における受信処理tcp_rcv_established関数に関して説明する。

受信処理tcp_rcv_established関数は、パケットを受け取ると以下の順序で処理を行う。

  1. 高速受信処理が可能なら、以下のパスに進まず、高速受信処理を行う。 一方的なデータ転送処理において、パケットとシーケンス番号順に 受信できており(out_of_order_queueにデータが無く、 到着したパケットのシーケンス番号がソケットのrcv_nxtに一致する)、 かつパケット長やウィンドウサイズも一定のまま複数のパケットが 送り続けてこられる場合、処理を簡略化できる。
    • TCPパケットがタイムスタンプ情報を持っている場合、 そのタイムスタンプ情報を読み出す。
    • 受信したパケットがACK情報のみであった場合、 これは本ホストから相手ホストへの一方的なデータ転送で あることがわかる。 ACKに対する処理(tcp_ack関数)を行なったのち、 送信処理の再開を行う(tcp_data_snd_check関数)。 詳しくは、送信処理の章で説明してある。
    • 受信したパケットのACKシーケンス番号が先に進んでいない場合、 こちらからはACKのみを送り返し続けていること、つまり 相手ホストから一方的なデータ転送を意味する。 この場合、受信したパケットと単にソケットのreceive_queue に繋ぐ。(ACKシーケンス番号がsnd_unaと一致している)
      • ちょうどカレントプロセスがこのソケットからのデータ読み出し 処理を行っており、かつこのソケットに保留されているパケットが 一つも存在しない場合。高速処理を行う。
        • パケットのデータをカレントプロセスの空間に直接コピーする
      • それ以外の通常の場合は、
        • パケットのチェックサムを確認する(tcp_checksum_complete_user関数)
        • パケットをreceive_queueにリンク
        • ソケットのdata_ready()メンバに登録されている関数を 呼び出し、ソケットでrecv待ちに入っているプロセスを起こす。
      • ACK送出時の遅延時間の再計算(tcp_event_data_recv関数)を行い、 必要なら遅延ACK禁止(tcp_enter_quickack_mode関数)にしておく。
      • ACKパケットを送出する(tcp_ack_snd_check関数)。 (この関数の動作に関しては標準の受信処理の方の説明参照のこと)
      • プロセス空間にコピーの完了したパケットデータなら、 そのパケットは解放する(kfree_skb関数)。
  • 標準の受信処理はこちらの処理を行う。
  • パケットのチェックサムを確認する(tcp_checksum_complete_user関数)
  • TCP拡張オプションの解析を行う(tcp_fast_parse_options関数)
  • まずシーケンス番号のチェック(tcp_sequence関数)を行い、 シーケンス番号がこちらの受信ウィンドウの範囲外であれば 即ACKパケットを送り返し(tcp_enter_quickack_mode関数、 tcp_send_ack関数)、処理を終了する。 ACKパケット送信により相手に正しいウィンドウ情報を伝える。
  • リセット要求(TCPヘッダのrstフィールド)であれば、コネクションの リセット(tcp_reset関数)を行い、処理を終了する。
  • ACKフィールドが有効になっている場合、ACKに対する処理を 行う(tcp_ack関数)。 tcp_ack関数の動作に関する詳しい説明は送信処理の方を参照のこと。
  • 緊急データであれば、緊急データへのポインタを設定し、 待ちに入っているプロセスを起床する(tcp_urg関数)。
  • 受信パケット(sk_buff)をソケットの受信キューに接続する(tcp_data関数)。
    • パケットのdataポインタをTCPヘッダ分だけずらす(skb_pull関数)。 もし必要があればパケット内の各ポインタを設定し直す(skb_trim関数)
    • パケットをソケットの受信キューに繋ぐ(tcp_data_queue関数)。
      • シーケンス番号順のパケット(パケットのシーケンス番号と ソケットのrcv_nxtが一致)であった場合、パケットをソケットの receive_queueにリンクする。もしout_of_order_queueに 連続するシーケンス番号のパケットがリンクされていれば、 そのパケットをreceive_queueにリンクし直す (tcp_ofo_queue関数)。

ソケットのdata_ready()メソッド(TCP/IPではsock_def_readableという 関数が登録されている)を呼び出し、このソケットでrecv待ちに 入っているプロセスの起床を行う。(SIGIO発生要求があれば SIGIOシグナルを発生させる)

        • 高速パスと同様に、ちょうどカレントプロセスがこのソケットからの データ読み出し処理を行っている場合、パケットデータを直接 ユーザ空間にコピーする。
img110.gif
      • 古いシーケンス番号のパケット(rcv_nxtより前の シーケンス番号のパケット)が再度到着した場合は、 即ACKを返却するモードに変更する(tcp_enter_quickack_mode関数)。 tcp_ack_snd_check関数が呼び出されたとき、ACK送信を遅延せず 即送信するように指示する。
      • パケットがrcv_nxtを跨ぐようなシーケンス番号を持つ場合、 receive_queueにリンクし、可能ならout_of_order_queueの パケットをreceive_queueにリンクしなおす tcp_ofo_queue関数)。 パケットの再送が発生したとき、ネットワーク負荷を軽減する ためにパケットの結合を行うため、このようなパケットが発生する。
      • 受信ウィンドウ内のパケットであるが、シーケンス番号順でない パケットを受け取った場合は、out_of_order_queueにリンクする。
  • 送信処理の再開を行う(tcp_data_snd_check関数)。 詳しくは、送信処理の章で説明してある。
  • 受信パケットに対する応答(ACK)返送を要求する (tcp_ack_snd_check関数)。通常は遅延ACKを 要求(tcp_send_delayed_ack関数)するが、 応答性が要求される場合は即ACKを送出(tcp_send_ack関数)する。 応答性が要求される場合とは、
    • ACK応答しなければならないパケットが非常に多く溜ったとき。
    • パケットがロストしている可能性があるとき (ソケットのout_of_order_queueにパケットがあるとき)
    • その他、即応答を要求されている(tcp_in_quickack_mode関数)場合。 パケットのロストやパケットの二重受信が多いときなど。 遅延ACK送信処理(tcp_delack_timer関数)は、 一定時間後に起動され、この中からACKパケットは送信される。

(NIS)HirokazuTakahashi
2000年12月09日 (土) 23時55分06秒 JST
1