linux0.99网络模块-数据链路层(发送)

在前面的文章中,分析了网络层数据报的传输过程,其中调用了下面的代码
net/tcp/ip.c
855  dev->queue_xmit(skb, dev, sk->priority);
现在我们就来看一下该设备(wd8003)注册的发送函数,在网络设备初始化一篇中我们分析了wd8003的初始化过程,发送函数正是在那里面注册的,如下:
net/tcp/Space.c
648 wd8003_init(struct device *dev)
676   dev->queue_xmit = dev_queue_xmit;
现在就来分析dev_queue_xmit的处理过程
net/tcp/dev.c
156 void
157 dev_queue_xmit (struct sk_buff *skb, struct device *dev, int pri)
158 {
159   struct sk_buff *skb2;
160   PRINTK ("dev_queue_xmit (skb=%X, dev=%X, pri = %d)\n", skb, dev, pri);

162   if (dev == NULL)
163     {
164       printk ("dev.c: dev_queue_xmit: dev = NULL\n");
165       return;
166     }

168   skb->dev = dev;
169   if (skb->next != NULL)
170     {
171       /* make sure we haven't missed an interrupt. */
172        dev->hard_start_xmit (NULL, dev);
173        return;
174     }

176   if (pri < 0 || pri >= DEV_NUMBUFFS)
177     {
178        printk ("bad priority in dev_queue_xmit.\n");
179        pri = 1;
180     }

182   if (dev->hard_start_xmit(skb, dev) == 0)
184        return;
185     }
如果发送出去就直接返回,否则没有发送出去,这时可能因为设备正忙于发送其他数据报,就执行下面的代码

187   /* put skb into a bidirectional circular linked list. */
188   PRINTK ("dev_queue_xmit dev->buffs[%d]=%X\n",pri, dev->buffs[pri]);
189   /* interrupts should already be cleared by hard_start_xmit */
190   cli();
191   if (dev->buffs[pri] == NULL)
192     {
193       dev->buffs[pri]=skb;
194       skb->next = skb;
195       skb->prev = skb;
196     }
197   else
198     {
199       skb2=dev->buffs[pri];
200      skb->next = skb2;
201       skb->prev = skb2->prev;
202       skb->next->prev = skb;
203       skb->prev->next = skb;
204     }
可以看到,如果由于设备正忙,没能按时将该数据报发送出去,就把数据报根据其优先级加入到相应的buffs中,等待合适的时机再将其发出,这是一个双向循环链表。
205   skb->magic = DEV_QUEUE_MAGIC;
206   sti();
208 }

可以看到这个函数也非常简单,那么我们就继续分析一下182行涉及到的硬件发送函数hard_start_xmit。直接告诉大家,它对应的是wd8003_start_xmit。
223 int
224 wd8003_start_xmit(struct sk_buff *skb, struct device *dev)
225 {
226   unsigned char cmd;
227   int len;
228
229   cli();
230   if (status & TRS_BUSY)
231     {
232        /* put in a time out. */
233        if (jiffies - dev->trans_start < 30)
234      {
235        return (1);
236      }
              这里处理超时的情况,我们可以发现,整个函数返回1的情况只有这一个,其他均返回0.对应上一个函数182行的条件判断就很容易理解那里我们所做的解释了。
238        printk ("wd8003 transmit timed out. \n");
239     }

240   status |= TRS_BUSY;
运行到这里说明现在可以进行数据报的发送,把设备状态设为TRS_BUSY
242   if (skb == NULL)
243     {
244       sti();
245       wd_trs(dev);
246       return (0);
247     }

249   /* this should check to see if it's been killed. */
250   if (skb->dev != dev)
251     {
252       sti();
253       return (0);
254     }
255
256
257   if (!skb->arp)
258     {
259       if ( dev->rebuild_header (skb+1, dev))
260     {
261       cli();
262       if (skb->dev == dev)
263         {
264           arp_queue (skb);
265         }
266        cli (); /* arp_queue turns them back on. */
267        status &= ~TRS_BUSY;
268        sti();
269        return (0);
270     }
271     }
上面推测是如果没有进行arp解析的话,就重新创建首部,加入到arp队列中进行解析。
273   memcpy ((unsigned char *)dev->mem_start, skb+1, skb->len);
把数据拷贝到设备的缓冲区中
275   len = skb->len;
277   /* now we need to set up the card info. */
278   dev->trans_start = jiffies;
279   len=max(len, ETHER_MIN_LEN); /* actually we should zero out
280                   the extra memory. */
281 /*  printk ("start_xmit len - %d\n", len);*/
283   cmd=inb_p(WD_COMM);
284   cmd &= ~CPAGE;
285   outb_p(cmd, WD_COMM);
287   interrupt_mask |= TRANS_MASK;
288   if (!(status & IN_INT))
289       outb (interrupt_mask, WD_IMR);
290 
291   outb_p(len&0xff,WD_TB0);
292   outb_p(len>>8,WD_TB1);
293   cmd |= CTRANS;
294   outb_p(cmd,WD_COMM);
295   sti();
以上是对寄存器的一些读写操作,具体我现在也不是很清楚,不过并不影响我们把握整体逻辑
297   if (skb->free)
298     {
299         kfree_skb (skb, FREE_WRITE);  //对于UDP传下来的数据报,发送完成之后就释放了,这里释放的还是上层的空间
300     }
301   
302   return (0);
303 }

问题:在链路层加入到设备缓冲区中的数据报是怎么被发送出去的?
老办法,使用grep指令搜索buffs,找到一个最可能的进一步验证,这里我们发现了dev_tint方法,来看一下:
net/tcp/dev.c
376 /* This routine is called when an device interface is ready to
377    transmit a packet.  Buffer points to where the packet should
378    be put, and the routine returns the length of the packet.  A
379    length of zero is interrpreted to mean the transmit buffers
380    are empty, and the transmitter should be shut down. */
382  unsigned long
383  dev_tint(unsigned char *buff,  struct device *dev)
384 {
385   int i;
386   int tmp;
387   struct sk_buff *skb;
388   for (i=0; i < DEV_NUMBUFFS; i++)
389     {
390       while (dev->buffs[i]!=NULL)
391     {
392       cli();
393       skb=dev->buffs[i];
.....................
445       tmp = skb->len;
446       if (tmp <= dev->mtu)
447         {
......................
452           if (buff != NULL)
453              memcpy (buff, skb + 1, tmp);
..................
479 }
可以看到这个函数实现了从buffs中取出数据发送的操作,那么谁调用这个函数呢?搜索发现只有 wd_trs方法调用了这个方法,如下:
net/tcp/we.c
493         /* attempt to start a new transmission. */
494         len = dev_tint( (unsigned char *)dev->mem_start, dev );
可以看到上面的函数中的参数buff正是
dev->mem_start 。进一步谁来调用wd_trs方法呢?我们又发现了下面的方法
net/tcp/we.c
526 wd8003_interrupt(int reg_ptr)
574         /* This completes rx processing... whats next */
575
576         if ( inb_p( ISR ) & PTX ) { /* finished sending a packet. */
577             wd_trs( dev );
578             outb_p( PTX, ISR ); /* acknowledge interrupt */
579         }
可以看到当设备发送完一个数据报后就会调用wd_trs方法。接下来继续寻找:
net/tcp/we.c
639 static struct sigaction wd8003_sigaction =
640 {
641    wd8003_interrupt,
642    0,
643    0,
644    NULL
645 };
wd8003_sigaction注册的处理函数正是 wd8003_interrupt,继续上溯:
net/tcp/we.c
648 wd8003_init(struct device *dev)
739   if (irqaction (dev->irq, &wd8003_sigaction))
终于,我们发现在wd8003设备初始化的过程中注册了中断处理动作wd8003_sigaction
现在我们可以正向回答前面提出的问题了,当wd8003设备发送数据报完成后会触发中断,该中断找到wd8003_sigaction进行处理,它根据注册的函数句柄找到了 wd8003_interrupt,在该函数中调用了wd_trs,进一步在wd_trs中遍历buffs数组,将之前添加进来的数据报发送出去。这就回答了我们上面提出的问题。


总结

在网络层把数据报传给链路层之后,如果设备当前不忙,则可以直接发送(把数据拷贝到设备的缓冲区中),否则就把数据报按照其优先级加入不同的设备缓冲区中等待后续发送。需要注意一下发送与接收过程的不同,在接收过程中一个数据报如果需要传给多个协议,那么就需要进行数据报的拷贝。但是在发送过程中就不需要,不同层之间传递的仅仅是引用而已。只有最后需要通过网卡发送出去时才进行了数据拷贝。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值