sk_buff
Rev.11を表示中。最新版はこちら。
1. 概要
sk_buffはパケットデータを格納するバッファ。各パケットは、それぞれsk_buffに格納されLinuxのネットワークレイヤで扱われる。
2. 基本的なデータ構造
sk_buffの構造を図2.1に示す。まずバッファの管理構造体としてstruct sk_buffがある。そして、実際にパケットデータを格納する領域が別にあり、sk_buffのhead,data,tail,endでデータの位置を管理する。また、データ格納エリアの後ろにはstruct skb_shared_infoが付いており、ここにはcloneで複製されたsk_buff間で共有するデータが格納される。
図2.1 sk_buffの構造(空のケース)
sk_buffにデータが格納されていると図2.2のようになる。sk_buffの各フィールドの意味は表2.1参照。
フィールド |
説明 |
---|---|
head |
データ格納用バッファの先頭を示す |
data |
バッファに格納されているデータの先頭 |
tail |
データの終端 |
end |
データ格納用バッファの終端 このアドレスは、skb_shared_infoの開始アドレスでもある。 |
3. その他のデータ構造
3.1 frags配列
skb_shared_info内のfrags配列がある。この配列を使用することで、パケットデータを非連続な領域に分断して保持することができる。fragsを使用した状態のsk_buffを図3.1に示す。
ただし、このようにfragsパケットデータを分断して持つ形式はネットワークカード(とそのドライバ)がScatter/gather I/O(*1)に対応していないと使えない。Scatter/gather I/Oに対応しているとデバイスにNETIF_F_SGフラグが設定されているので、送信デバイスにこのフラグが立っている場合のみ、このような形式を使う。
frags配列の要素はskb_frag_structとなっており(ポインタではなくそのもの)、これは、分断されたデータがどこにあるかを管理する。 skb_frag_structはデータが置かれている物理ページのstruct pageを指している。そして、page_offset,sizeが物理ページ内のデータ格納位置を示している。
(*1) 物理的に非連続になっている複数のバッファのデータから1つのパケットを構築(Gather)して送信したり、逆に受信パケットを分断された複数のバッファに格納(Scatter)できる ネットワークカード。この機能により、ジャンボフレームを扱う場合に、パケットサイズ分の物理的に連続なページを確保する必要がなくなる。
Scatter/Gather I/Oに対応しているドライバでは、送信時にfrags[]をチェックして、分断されたバッファのデータをまとめて送信するようにH/Wを制御する。
3.2 frag_list
skb_shared_info内のfrag_listにはIPフラグメントされたパケットのsk_buffがチェーンされる(図3.2)。図3.2ではデータ(A)がフラグメントされた先頭パケットのデータ、データ(C)が2番目のパケットのデータとなる。送信イメージは図3.3。
図3.2 frag_list
frag_listの構築はIPv4では、ip_append_data()とip_push_pending_frames()で行われる。RAW,UDPソケットでデータ送信する時、ソケットは送信データをsk_buffに格納するためにip_append_data()を呼び出す。ip_append_data()はデータをsk_buffにデータを格納して行く際、パケット長がMTUを越えるようなら新しいsk_buff作成して、ソケットの送信キュー(sock->sk_write_queue)にチェーンしていく。sk_buffの構築が終わったら、ip_push_pending_frames()で送信キュー内のパケットをfrag_listにチェーンしてIP層の送信ルーチンに渡している。
3.3 lenとdata_len
sk_buffにはデータ長を表すフィールドとしてlenとdata_lenを持つ。
lenはsk_buff内の全データの長さを示す。図3.1、3.2でいうとデータ(A)、frags[]に保持されるデータ(B)、frag_listにチェーンされているsk_buffのデータ(C)全てを足したものになる。
data_lenはデータ(B)のサイズ。frag_listにパケットがチェーンしている場合はさらにデータ(C)のサイズを足した部分のサイズになる。つまり、lenからデータ(A)のサイズを除いた分となる。skb_headlen()関数では"len - data_len"でデータ(A)の部分のサイズを求めている。
4. sk_buffの複製(Clone)
4.1 基本的なClone
ネットワーク層で複数のソケットでデータを渡す場合や、再送のためにデータを保持しておく場合は、sk_buffを複製する。これはskb_clone()で行う。skb_clone()は図4.1に示すように引数で指定されたsk_buffを複製する。複製されるのはsk_buffのみで、データ自体は複製されない。
sk_buffを複製すると、オリジナルと複製されたsk_buffのそれぞれのclonedを1にする。また、skb_shared_info内にある参照カウンタdatarefをインクリメントする。
[clonedフラグ]
clonedは一度セットすると基本的にクリアされないので、複製したsk_buffが解放されても、clonedは立ったままとなる。このため、 clonedが立っていたとしても、Clone(データを共有しているsk_buff)が"存在するかもしれない"ということしかわからず、現在も存在し ているとはかぎらない。Cloneが存在するかどうかは、clonedが立っていたらさらにdatarefを参照する必要がある。値が1より大きければ、Cloneが存在することになる。1であれば自分しか参照していないのでCloneはもう存在していないことになる。Cloneが存在するかどうかはskb_cloned()マクロで調べられるが、本マクロの実装はこのようにclonedとdatarefの2段階で判定を行っている。
そもそも、datarefだけ見てしまえばCloneがいるかどうかは判定できるのだが、clonedフラグのようなものが存在しているのは、dataref参照時に必要な排他制御のコストを省くため。Cloneしたことがないsk_buffならcloned=0なので、排他制御することなく速やかにCloneがいないことを判定できる。