新協議的插入點是ETH_P_ALL
static struct packet_type new_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_ALL),
.func = new_procol_rcv,
};
ETH_P_ALL這個插入點呢,比較常見的就是tcpdump,這個地方所有的報文都會經過,所以使用的時候特別小心。這不我都遇到兩個問題,驚天大bug,會嚇死人的那種,設備崩掉的那種。
進入正題
接上一篇那個問題解決之後,以爲萬事大吉了,結果是剛開始,設備打流的時候內存泄漏,漏的也不快1個小時也就1個G,同事都給這個bug定義了一個很好的名字:側漏。
當然了天大的bug也不要慌,分析問題先,排除測試,先看看什麼情況下不漏吧。
先後在原代碼的基礎上測試,新協議的從接口,配置成橋模式和不是橋模式來測試,,依然側漏,好吧!!! 一萬個草泥馬飄過。
翻看內核的代碼上下文吧!等等忘了最重要的一句話,內存泄漏嘛,用計算機話說就是內存沒有釋放,報文處理中釋放內存,首先想到的就是 skb,,沒錯就是它,,
原代碼中,new_procol_rcv的返回是這樣的:
return 0;
當初以爲沒毛病的啦!
翻看new_procol_rcv函數執行的上下文:
調用位置在 :netif_receive_skb -> netif_receive_skb_internal ->__netif_receive_skb->__netif_receive_skb_core
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
省略N行.....
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
省略N行。。。。
}
這個地方的deliver_skb函數:
static inline int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
return -ENOMEM;
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
這裏的 pt_prev->func就是new_procol_rcv,這裏面調用了一次atomic_inc(&skb->users);然而new_procol_rcv中並沒有將skb->users減一啊,這樣skb一直被佔用着,無法釋放,說的很有道理的樣子,試試吧!(skb->users不明白的自個百度,懶得貼了),
so 改代碼,返回的時候skb->users減一下
更改如下:
int new_procol_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
省略N行。。。。
kfree_skb(skb);
return NET_RX_DROP;/* linux/netdevice.h */
}
然後測試,新協議的從接口是橋的模式的時候,不會側漏,但是,從接口不是橋模式的時候,還有漏,,好吧!有點進展了,看到希望了,
仔細理一理思路:
netif_receive_skb中調用:
new_procol_rcv
然後執行橋的流程:
br_handler
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
省略N行。。。
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
省略N行。。。
rx_handler = rcu_dereference(skb->dev->rx_handler);
省略N行。。。
}
然後,new_procol_rcv中會調用 netif_receive_skb(),遞歸了呀
。。。。。
手跟不上腦子的速度了。。。。
。。。。。
。。。。
、。。。
。。
。。
。
就這樣的吧,說不清楚了,看代碼:
int new_procol_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
省略N行。。。。
/* 數據報文 */
if(IS_DATA_PACKET(plkjhh))
{
if(!(skb->dev->priv_flags & IFF_BRIDGE_PORT))
{/* skb->dev 不是橋模式| 不是橋接口*/
new_recv_data_packet(skb,pVdev);
netif_receive_skb(skb);
/*使用ETH_P_ALL 的情況下 這裏 不能執行 kfree_skb(skb) */
atomic_read(&skb->users);
return NET_RX_SUCCESS;
}else{
/* skb->dev 是橋模式| 是橋接口*/
new_recv_data_packet(skb,pVdev);
kfree_skb(skb);
return NET_RX_SUCCESS;
}
}
省略N行。。。。
}
總之 橋接口在減skb->users的時候 使用kfree_skb(skb);,非橋接口在減skb->users的時候使用atomic_read(&skb->users);
atomic_read(&skb->users);跟kfree_skb(skb);的區別就是在skb->users爲0時是否kfree(skb).