ljbphoebe 发表于 2012-11-06 16:42

ip_rcv函数中调用skb_share_check克隆skb疑问

本帖最后由 ljbphoebe 于 2012-11-06 16:51 编辑

问:原来的skb哪去了?
    ip_rcv函数处理从链路层来的skb数据包,该函数会检查skb是否被共享,如果被共享就clone一个skb,原来的skb的user引用计数减1。
    如果skb的user引用数为3的话,那么就会进入skb_share_check中的if (skb_shared(skb)) 段中执行,就会clone 一个新的skb,然后kfree_skb(skb)将旧的skb的引用计数减1,user为2,
    struct sk_buff *nskb = skb_clone(skb, pri);
                kfree_skb(skb);
                skb = nskb;
从代码上看,skb指针指向了新clone的skb,kfree_skb(skb)并没有真正释放原来skb,那么原来的skb哪去了呢?岂不是内核内存泄露了?谁知道这是为什么?谢谢~int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
        struct iphdr *iph;

        /* When the interface is in promisc. mode, drop all the crap
       * that it receives, do not try to analyse it.
       */
        if (skb->pkt_type == PACKET_OTHERHOST)
                goto drop;

        IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);

        if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
                IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
                goto out;
        }
......

}

static inline struct sk_buff *skb_share_check(struct sk_buff *skb, int pri)
{
        might_sleep_if(pri & __GFP_WAIT);
        if (skb_shared(skb)) {
                struct sk_buff *nskb = skb_clone(skb, pri);
                kfree_skb(skb);
                skb = nskb;
        }
        return skb;
}

static inline void kfree_skb(struct sk_buff *skb)
{
        if (atomic_read(&skb->users) == 1 || atomic_dec_and_test(&skb->users))
                __kfree_skb(skb);
}

whaaat 发表于 2012-11-06 17:23

按照你的假设skb的user传进来时就是3,表明有其他地方使用这个skb,使用的地方会负责释放的

ljbphoebe 发表于 2012-11-06 17:57

本帖最后由 ljbphoebe 于 2012-11-06 17:59 编辑

我查看了原来的skb的出处,调用路径是这样的:
process_backlog函数中,skb = __skb_dequeue(&queue->input_pkt_queue);   此时的skb是孤立的个体,没有其他指针指向它
然后调用netif_receive_skb(skb);       将skb和所有的已注册的协议相比较,
然后调用deliver_skb(skb, pt_prev);       对user引用计数递增,调用上层协议处理函数处理此skb,对于ip层,就直接调用ip_rcv函数了

多谢提醒啊,我明白了。感觉应该是这样:
int netif_receive_skb(struct sk_buff *skb)
{
    ......

        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);
                        pt_prev = ptype;
                }
        }
   ........
        list_for_each_entry_rcu(ptype, &ptype_base, list) {
                if (ptype->type == type &&
                  (!ptype->dev || ptype->dev == skb->dev)) {
                        if (pt_prev)
                                ret = deliver_skb(skb, pt_prev);
                        pt_prev = ptype;
                }
        }
   .......
}

在每次比较协议的时候,都会调用相应协议的处理函数来对之前的skb操作,所以除了在ip层的ip_rcv函数之外,还有其他协议的处理函数操作此skb,应该是这样了

hk_sean 发表于 2012-11-08 10:23

路过看看,求更深入分析

ljbphoebe 发表于 2012-11-08 18:39

本帖最后由 ljbphoebe 于 2012-11-08 20:32 编辑

从linux网络内核源码看,我也只是看大概的流程,没有细看具体的细节

      每个网卡在linux启动之初都跟内核相关联上,硬件是内核函数pci_init()内部通过层层调用,最后通过pci_scan_device()函数将网卡结构挂着pci_devices全局变量上。网卡驱动程序是通过pci_register_driver ()函数将网卡驱动挂入全局驱动链表pci_drivers中,并且将驱动和对应的device相关联。在网卡驱动结构struct pci_driver中,是一些函数指针,最主要的是probe,指向具体类型网卡相应的初始化函数,如rtl8139_init_one,这个函数是真正和具体网卡有直接关系的驱动程序,主要负责建立网卡设备结构net_device,申请中断号,映射内存IO端口等任务。而在net_device结构中就是内核可以直接操作网卡的一系列函数指针open,hard_start_xmit,poll等等,net_device结构比较大,不能全列出来
struct net_device{
    ........
   int        (*open)(struct net_device *dev);
   int        (*stop)(struct net_device *dev);
   int        (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
   int        (*poll) (struct net_device *dev, int *quota);
   ........
}
      open指向rtl8139_open,hard_start_xmit指向rtl8139_start_xmit,poll指向rtl8139_poll,stop指向rtl8139_close函数,分别是打开设备,发送数据包,轮训设备接收数据包,关闭设备。显然打开和关闭设备分别在系统启动和系统关闭时的操作;发送和接收是随时的操作。在open操作中驱动程序会为此网卡申请一个中断号,并且关联上中断服务程序。对于网络接收数据包有两种方式:1.中断方式;2.轮训方式
1.中断方式:
      在2.4及2.4之前的内核是以中断方式接收的,即中断服务程序通过将接收到的数据拷贝到内核缓冲包结构skb中,然后通过netif_rx()函数将skb挂入软中断数据结构softnet_data结构中的input_pkt_queue队列上,再通过netif_rx_schedule()函数将设备poll_list加入softnet_data的poll_list列表中并标记软中断,等待内核处理。这部分是硬中断处理部分。
      内核在软中断处理时机,net_rx_action()函数调用process_backlog函数(也是poll,是blog_dev的poll,2.6中是backlog_dev)将skb从input_pkt_queue队列取出,然后调用netif_receive_skb()函数将skb数据包提交给网络上层。
struct softnet_data
{
   ……
        struct sk_buff_head        input_pkt_queue;
        struct list_head        poll_list;
   ……
};
2.轮训方式:
      在2.6内核中增加了轮训机制。这种机制,从我看内核源码中发现,它不再将skb挂入input_pkt_queue了(不知道我看的对不对,有高手给予指正),在中断服务程序中就不一样了。当中断到来时,它是先将对应设备的poll_list挂入struct softnet_data的poll_list列表中,然后打软中断标记,先不对数据包进行接收,把接收数据包的任务给软中断了。
      在软中断执行时机,net_rx_action()调用poll函数将网卡数据交给skb,然后直接调用netif_receive_skb()函数交给上层。
      我感觉中断和轮训处理方式不同的是在接收skb数据包的时机不同了(虽然都用poll,但是poll处理数据包时机不同),中断方式是在硬中断过程中接收的,而轮训方式是在软中断过程中接收的;另一个就是轮训方式是不将skb保存在input_pkt_queue队列中。对于这一点,我还不确定我看的对不对,希望大家讨论一下

      接下来,netif_receive_skb()得到skb包之后就处理该数据包。先检查包的正确性(netpoll_rx函数检查),打时间戳,……,再之后就是扫描ptype_all链表与ptype_base哈希表。ptype_base是各种已经注册的协议的哈希表,根据每个数据包协议不同分派给不同的协议来处理,在这里递增skb进行索引计数,然后会在后续的操作中对skb进行拷贝。我们讨论的是IP协议,所以就通过ETH_P_IP标志传给IP层处理函数ip_rcv(),这是通过struct packet_type的func函数指针调用的(ip_rcv是在内核初始化时通过ip_init函数与func指针关联的)。
      在ip_rcv函数中通过skb_share_check函数对skb进行拷贝,接下来调整IP层的data起始位置,最后通过ip_rcv_finish处理路由转发还是继续往上层传递。
      在ip_rcv_finish中也是对skb包进行一系列检查,调用ip_route_input函数处理路由,设置此数据包的路由出口,接下来调用dst_input函数处理转发还是向上传。如果是转发则调用ip_forward,如果是向上传则调用ip_local_deliver。

      这是大概的包流程,写出来和大家一起探讨,互相进步。不一定对啊,仅供参考。

wuweiyoung 发表于 2014-11-24 17:18

这个论坛的同志就是叼
页: [1]
查看完整版本: ip_rcv函数中调用skb_share_check克隆skb疑问