免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4511 | 回复: 5
打印 上一主题 下一主题

[内核入门] ip_rcv函数中调用skb_share_check克隆skb疑问 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-11-06 16:42 |只看该作者 |倒序浏览
本帖最后由 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哪去了呢?岂不是内核内存泄露了?谁知道这是为什么?谢谢~
  1. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
  2. {
  3.         struct iphdr *iph;

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

  9.         IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);

  10.         if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
  11.                 IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
  12.                 goto out;
  13.         }
  14. ......

  15. }

  16. static inline struct sk_buff *skb_share_check(struct sk_buff *skb, int pri)
  17. {
  18.         might_sleep_if(pri & __GFP_WAIT);
  19.         if (skb_shared(skb)) {
  20.                 struct sk_buff *nskb = skb_clone(skb, pri);
  21.                 kfree_skb(skb);
  22.                 skb = nskb;
  23.         }
  24.         return skb;
  25. }

  26. static inline void kfree_skb(struct sk_buff *skb)
  27. {
  28.         if (atomic_read(&skb->users) == 1 || atomic_dec_and_test(&skb->users))
  29.                 __kfree_skb(skb);
  30. }
复制代码

论坛徽章:
0
2 [报告]
发表于 2012-11-06 17:23 |只看该作者
按照你的假设skb的user传进来时就是3,表明有其他地方使用这个skb,使用的地方会负责释放的

论坛徽章:
0
3 [报告]
发表于 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[ntohs(type)&15], 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,应该是这样了

论坛徽章:
0
4 [报告]
发表于 2012-11-08 10:23 |只看该作者
路过看看,求更深入分析

论坛徽章:
0
5 [报告]
发表于 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。

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

论坛徽章:
0
6 [报告]
发表于 2014-11-24 17:18 |只看该作者
这个论坛的同志就是叼
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP