在前面的文章中,分析了网络层数据报的传输过程,其中调用了下面的代码
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 }
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 }
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方法呢?我们又发现了下面的方法
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方法。接下来继续寻找:
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 };
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))
739 if (irqaction (dev->irq, &wd8003_sigaction))
终于,我们发现在wd8003设备初始化的过程中注册了中断处理动作wd8003_sigaction。
现在我们可以正向回答前面提出的问题了,当wd8003设备发送数据报完成后会触发中断,该中断找到wd8003_sigaction进行处理,它根据注册的函数句柄找到了
wd8003_interrupt,在该函数中调用了wd_trs,进一步在wd_trs中遍历buffs数组,将之前添加进来的数据报发送出去。这就回答了我们上面提出的问题。
总结
在网络层把数据报传给链路层之后,如果设备当前不忙,则可以直接发送(把数据拷贝到设备的缓冲区中),否则就把数据报按照其优先级加入不同的设备缓冲区中等待后续发送。需要注意一下发送与接收过程的不同,在接收过程中一个数据报如果需要传给多个协议,那么就需要进行数据报的拷贝。但是在发送过程中就不需要,不同层之间传递的仅仅是引用而已。只有最后需要通过网卡发送出去时才进行了数据拷贝。