新协议的插入点是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).