Linux Kernel(2.6)の実装に関するメモ書き

sk_buff


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参照。


図2.2 sk_buffの構造(データがあるケース)


表2.1 sk_buffのデータ位置を管理するフィールド 
フィールド
説明
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フラグが設定されているので、送信デバイスにこのフラグが立っている場合のみ、このような形式を使う。


図3.1 frags配列

[図3.1 補足]

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 



図3.3 フラグメントされたパケットの送信

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をインクリメントする。


図4.1 sk_buffのclone

[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がいないことを判定できる。

4.2 高速なClone

上記の通常のCloneではCloneの度にsk_buffの確保が発生する。あらかじめCloneされることがわかっているsk_buffについては、オリジナルを確保する時に後でCloneされる分のsk_buffも一緒に確保してしまうと、Clone時のメモリ確保を省略できる。このClone分のsk_buffもまとめて確保する関数がalloc_skb_fclone()。fcloneの'f'はおそらく'fast'を意味する。

alloc_skb_fclone()で確保したsk_buffは図4.2のような形になる。alloc_skb()の場合はsk_buffは1つしかないのだが、alloc_skb_fclone()では後でCloneした時に使用されるsk_buffが1つ余計に確保される。そして2つのsk_buffの後に、このsk_buffの領域の参照カウンタが置かれる。sk_buffのfcloneにはClone状態が保持される。オリジナルのsk_buffにはSKB_FCLONE_ORIGがセットされ、Clone用のsk_buffにはSKB_FCLONE_UNAVAILABLEがセットされる。



図4.2 alloc_skb_fclone()で確保したsk_buff

alloc_skb_fclone()で確保したsk_buffをskb_clone()で複製すると図4.3のようになる。Clone用のsk_buffがオリジナルと同じデータを共有するようになり、fcloneはSKB_FCLONE_CLONEがセットされる。sk_buff後ろの参照カウンタはインクリメントされ2になる。


図4.3 cloneした状態

5. 関連関数

sk_buffでよく使う関数。

5.1 割り当て

alloc_skb(size, priority)
空のsk_buffを確保する。(図2.1のような形式)
priorityにはGFPマスクを指定する。通常GFP_ATOMIC/GFP_KERNELを使用する。

alloc_skb_fclone(size, priority)
fclone形式のsk_buffを確保する。(図4.2の形式)

alloc_skb_from_cache(cp, size, priority)
基本的にalloc_skb()と同じだが、データ領域のバッファを指定したkmem_cache(cp)から取得する。

5.2 データの追加/削除

skb_push(skb, len)
skbのデータの頭にlenバイトだけの領域を確保する。(skb->dataポインタをlenだけ減算し、skb->lenをlenだけ加算する)

skb_pull(skb, pull)
skbのデータの頭からlenバイト分を削除する。skb_push()の逆。

5.3 Clone関連

skb_clone(skb, gfp_mask)
skbのcloneを作成する。「sk_buffの複製」参照。

skb_cloned(skb)
skbにCloneが存在するかを返す。
skb->clonedフラグをチェックして、立っていたらCloneが存在するかんもしれないので参照カウンタ(skb_shinfo(skb)->dataref)をチェックする。


最終更新 2007/05/13 17:31:20 - kztomita
(2007/04/19 18:48:38 作成)
添付ファイル
skbuf_layout.png - kztomita
skbuf_layout2.png - kztomita
frags_array.png - kztomita
frag_list.png - kztomita
ip_fragment.png - kztomita
skbuf_clone.png - kztomita
skbuf_fclone.png - kztomita
skbuf_fclone2.png - kztomita


リンク
最近更新したページ
検索