linux0.99网络模块-数据链路层(接收)

在《linux0.99网络模块-物理层(中断处理)》中我们知道网卡设备接收到数据报后就会触发中断,在中断处理函数中会把接收到的封装为sk_buff结构的数据添加到链路层的backlog中,并设置了下半部激活标志。现在我们就来看一下链路层是如何把数据交给上层的。
我们从中断下半部的处理开始:
net/tcp/dev.c
294 void
295 inet_bh(void *tmp)
296 {
297   struct sk_buff *skb;
298   struct packet_type *ptype;
299   unsigned short type;
300   unsigned char flag =0;
301   static int in_bh=0;

303   cli();
304   if (in_bh != 0)     //如果正在处理中断下半部,那么直接返回
305     {
306       sti();
307       return;
308     }
309   in_bh=1;     //设置标记防止嵌套调用
311   /* anything left to process? */
313   while (backlog != NULL)
314      {
315        cli();
316        skb= backlog;
317        if (skb->next == skb)     //如果只有一个sk_buff
318      {
319        backlog = NULL;
320      }
321        else                             //如果有多个数据包,从积压队列中取下头部的sk_buff(由skb指向)
322      {
323        backlog = skb->next;
324        skb->next->prev = skb->prev;
325        skb->prev->next = skb->next;
326      }
327        sti();              //现在可以开中断了(上面的过程不关中断会造成积压队列的错乱修改)
329        /* bump the pointer to the next structure. */
330        skb->h.raw = (unsigned char *)(skb+1) + skb->dev->hard_header_len;
这里的raw指向的是当前所在协议层首部第一个字节地址,这里指向的是IP层首部第一个字节,因为该sk_buff即将发送给网络层
331        skb->len -= skb->dev->hard_header_len;
skb->len是其中数据部分的长度(不包括结构体本身),现在数据包已经成为链路层的包,需要除去首部。这里的首部包括源物理地址,目的物理地址和类型。除去这些内容后还剩下数据部分和校验和。(见net/tcp/eth.h:60行)
333        /* convert the type to an ethernet type. */
334        type = skb->dev->type_trans (skb, skb->dev);
类型转换根据数据包首部字段来确定。
336        /* if there get to be a lot of types we should changes this to
337       a bunch of linked lists like we do for ip protocols. */
338        for (ptype = ptype_base; ptype != NULL; ptype=ptype->next)
339      {
340        if (ptype->type == type)
341          {
342            struct sk_buff *skb2;
343            /* copy the packet if we need to. */
344            if (ptype->copy)
345          {
346            skb2 = kmalloc (skb->mem_len, GFP_ATOMIC);
347            if (skb2 == NULL) continue;
348            memcpy (skb2, skb, skb->mem_len);
349            skb2->mem_addr = skb2;
350            skb2->lock = 0;
351            skb2->h.raw = (void *)((unsigned long)skb2
352                       + (unsigned long)skb->h.raw
353                       - (unsigned long)skb);
355          }
356            else
357          {
358            skb2 = skb;
359            flag = 1;
360          }
362            ptype->func (skb2, skb->dev, ptype);
363          }
364      }//for
注意这里的ptype_base是packet_type类型的,它定义于./net/tcp/dev.h
 95 struct packet_type
 96 {
 97    unsigned short type; /* This is really NET16(ether_type) other devices
 98                will have to translate appropriately. */
 99    unsigned short copy:1;
100    int (*func) (struct sk_buff *, struct device *, struct packet_type *);
101    void *data;
102    struct packet_type *next;
103 };
可以看到它可以通过next指针链接成链,并且有一个函数指针。其实这个结构是用来保存上层在链路层的注册的。
我们看338行的for循环,它遍历ptype_base链,如果积压队列中的数据报类型与注册的目标数据包类型相同,也就是340行为true。然后进一步判断是否需要拷贝一份副本传给相应的处理函数,最后362行调用注册的处理函数对数据包进行处理。
366        if (!flag)
367      {
368        PRINTK ("discarding packet type = %X\n", type);
369        kfree_skb (skb, FREE_READ);
370      }//如果已经拷贝过就可以释放了,如果没有拷贝,由上层负责释放
371      }//while
372   in_bh = 0;     //清除标记
373   sti();     //
374 }
到此就分析完了下半部的处理流程。

现在有两个问题:
1.下半部是在哪里被调用的?
2.上层怎么向链路层注册接收通知,如何向ptype_base中添加元素?
第一个问题:
首先我们《linux0.99网络模块-网络模块初始化 》中知道,初始化过程会调用具体协议的初始化函数进行初始化。对于TCP/IP来说就是执行net/tcp/sock.c:836:static int ip_proto_init(void);在这个过程中执行了下面语句
 879   bh_base[INET_BH].routine = inet_bh;
也就是把inet_bh注册到bh_base[INET_BH].routine。进一步我们在kernel/irq.c中可以找到如下函数:

 47 void do_bottom_half(void)
 48 {
。。。。
 69     do {
 70         count = 0;
 71         for (mask = 1, bh = bh_base; mask ; bh++, mask = mask << 1) {
 72             if (mask > bh_active)
 73                 break;
。。。
 82
 83             if (bh->routine != NULL)
 84                 bh->routine(bh->data);
。。。。
 91     } while (count > 0);
 92     in_bh = 0;
 93 }
可以看到在84行调用了routine,也就是net_bh函数。进一步do_bottom_half又是由谁调用的呢?使用grep指令搜索该函数发现在include/asm/irq.h中:
121     "call _do_IRQ\n\t" \
125     "call _do_bottom_half\n\t"\
其中具体内容我们现在先不看,从上面两行可以知道do_bottom_half其实也是由中断触发的。结合前面内容来说就是,当接收到数据报后,触发中断,在中断处理函数中把数据报传递给链路层,其实就是把数据封装成sk_buff放到积压队列中,并设置下半部激活标记,这个过程要求比较快完成。它完成后,如果没有发生其他中断,下一步就会调用do_bottom_half,这里会把数据报做进一步处理并且上传给注册在ptype_base中的上层协议,这个过程相对没有那么紧急,它可以被打断。现在就回答了第一个问题。

现在来看第二个问题
net/tcp/pack_type.c:
66:struct packet_type *ptype_base = &ip_packet_type;

 57 static struct packet_type ip_packet_type=
 58 {
 59    NET16(ETHERTYPE_IP),
 60    0, /* copy */
 61    ip_rcv,
 62    NULL,
 63    &arp_packet_type
 64 };
可以看到ptype_base指向的是ip_packet_type,而后者又链接到arp_packet_type(63行),arp_packet_type的next为NULL。也就是说,这里ptype_base中向链路层注册的协议只有ip和arp两个。
下一篇,我们就来看一下IP协议是如何处理链路层传上来的数据包的。通过61行可以知道,它注册的处理函数为ip_rcv。
另外在net/tcp/dev.c中有下面的函数:
 94 void
 95 dev_add_pack (struct packet_type *pt)
 96 {
 97    struct packet_type *p1;
 98    pt->next = ptype_base;
100    /* see if we need to copy it. */
101    for (p1 = ptype_base; p1 != NULL; p1 = p1->next)
102      {
103     if (p1->type == pt->type)
104       {
105          pt->copy = 1;
106          break;
107       }
108      }
110    ptype_base = pt;
112 }
也就是说可以通过dev_add_pack函数向链路层注册接收通知。另外留意一下103行。
到这里我们就回答了第二个问题。

总结

网卡接收到数据之后会触发中断,在中断处理函数中相应的数据报会被封装成sk_buff挂到backlog队列上,并设置下半部激活标志。这个过程中如果发生了新的中断又会进行新的中断处理,等到空闲时(也就是没有中断发生)就会调用do_bottom_half(之前待办事项会不会被过度延迟?,在这里面会调用在协议初始化阶段注册的routine处理函数,也就是inet_bh。它会遍历所有的数据报,做适当的处理(比如剥去首部),找到上层注册的对其感兴趣的协议,调用相应的上层注册的处理函数(把该数据报传给目标函数)。目前ptype_base中注册了arp和ip两种协议。并且ip协议的处理函数为ip_rcv。接下来,我们就会在下一篇中分析ip协议的处理过程。




  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值