關于skb_header_pointer函數 2012-02-21 15:17:46
分類:
原文位址:關于skb_header_pointer函數 作者:luoyan_xy
最近一段時間看核心代碼,總是看到skb_header_pointer函數,這個函數的主要功能很簡單,就是從skb字段中擷取指定長度到内容到緩存中。函數原型是這個樣子的: static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
int len, void *buffer) 也就是從skb中skb->data開始的offset偏移處,擷取len長度的内容到buff中。 看起來這個并沒有什麼問題,不過無意中在網上搜了一下這個函數,卻發現還真有一些别的意義在裡面。。好吧,看一下代碼,依然是2.6.30版本核心。 static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
int len, void *buffer)
{
int hlen = skb_headlen(skb); if (hlen - offset >= len)
return skb->data + offset; if ( skb_copy_bits(skb, offset, buffer, len) < 0)
return NULL; return buffer;
} 參數為:
skb:資料包struct sk_buff的指針
offset:相對資料起始頭(如IP頭)的偏移量
len:資料長度
buffer:緩沖區,大小不小于len 其中skb_headlen()函數的定義為: static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
return skb->len - skb->data_len;
}
其中skb->len是資料包長度,在IPv4中就是單個完整IP包的總長,但這些資料并不一定都在目前記憶體頁;skb->data_len表示在其他頁的資料長度(包括本skb在其他頁中的資料以及分片skb中的資料),是以skb->len - skb->data_len表示在目前頁的資料大小。 如果skb->data_len不為0,表示該IP包的資料分屬不同的頁,該資料包也就被成為非線性化的,函數skb_is_nonlinear()就是通過該參數判斷,一般剛進行完碎片重組的skb包就屬于此類。 這樣skb_header_pointer()函數就好了解了,先判斷要處理的資料是否都在目前頁面内,如果是,則傳回可以直接對資料處理,傳回所求資料指針,否則用skb_copy_bits()函數進行拷貝,下面再來看一下這個函數的實作過程,并不複雜。 int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
int i, copy;
int start = skb_headlen(skb); if (offset > (int)skb->len - len)
goto fault;
if ((copy = start - offset) > 0) {
if (copy > len)
copy = len;
skb_copy_from_linear_data_offset(skb, offset, to, copy);
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end; WARN_ON(start > offset + len); end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
u8 *vaddr; if (copy > len)
copy = len; vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
memcpy(to,
vaddr + skb_shinfo(skb)->frags[i].page_offset+
offset - start, copy);
kunmap_skb_frag(vaddr); if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list; for (; list; list = list->next) {
int end; WARN_ON(start > offset + len); end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
if ( skb_copy_bits(list, offset - start,
to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
}
if (!len)
return 0; fault:
return -EFAULT;
} 其中的skb_copy_from_linear_data_offset函數,就是一個線性拷貝的過程,内部是對memcpy的一個封裝。 kmap_skb_frap沒有看明白,不過其意思應該是把分片資料所在的page位址映射到了一個核心可通路的虛拟位址上,通過這個虛拟位址完成資料的拷貝,最後再通過kunmap_skb_frag完成映射位址的釋放。 這些都不是重點,主要的是我在網上看到了這樣一些話,便直接把它們拷貝過來吧: 在2.4中是沒有這一函數的,因為2.4的netfilter首先進行碎片包重組,随即進行skb的線性化檢查,對非線性skb包進行線性化,是以合法skb包進入後續hook點操作時實際skb->data_len就都是0了,可以直接操作。 netfilter的碎片重組函數為ip_ct_gather_frags(),在2.4中碎片重組完還進行線性化,而2.6中重組完就直接傳回了,并不進行線性化操作,是以以後在使用的時候必須檢查要處理的資料是否在記憶體頁面中。 由于2.6中的碎片重組操作後不進行skb資料包的線性化,是以資料可能存在于不同的記憶體頁面中,對于不在同一頁面中的情況不能直接進行資料操作,需要将資料拷貝到一個單獨緩沖區後再進行處理。 關于核心為什麼這樣做,我沒找到标志答案,下面是一個網友的回複,也粘貼過來,有機會的話就慢慢了解了:
非線性化很重要的一點是為了支援網卡晶片的一些功能,這些功能可以大大增加TCP的性能。
如TSO(tcp segment offload),在sendfile中。 系統隻是增加file對應的page cache 的引用數,接着将一個個頁面放在SKB中發送,一次可以放接近65535 - TCP頭的資料。網卡如E1000,會根據MSS大小切割封包并計算校驗後發送。