IP包的输出过程
2000.05.19 输出时主要是调用qdisc_restart( )函数,该函数的作用是将一个包交给网络 设备,它在很多地方被调用,也就是说,对于包的输出,Linux系统采用灵活多样的 方式。qdisc_restart( )又调用相应设备的提供的hard_start_xmit函数将包交给设 备。hard_start_xmit是struct device结构中的一个函数指针,网络设备初始化时, 将这个指针赋值(具体的函数名称请参见相应的设备驱动程序),这样,给IP层提供 了一个将包发往下层的接口。下图表示了可能的调用方式(由于bbs不能显示图片, 只好从略)。 dev_queue_xmit( )在文件net/core/dev.c中定义,这个函数将从上层传下来的包 放入设备的队列中(qdisc_enqueue,注意,这里的队列是一个很值得研究的概念,将 在下文中叙述),然后调用qdisc_wakeup( )(在文件include/net/pkt_sched.h中定义), 该函数首先判断设备是否忙(tbusy=1),如果不忙(tbusy=0),则调用 qdisc_restart( )将这个包发出;否则将直接返回,包被缓存在输出队列中。设备数据 结构struct device中有一个元素tbusy,发送包前,tbusy被置位,发送结束,tbusy被 复位。 dev_queue_xmit( )在ip_finish_output( )函数中被调用,见下面的程序: file:include/net/ip.h extern __inline__ int ip_finish_output(struct sk_buff *skb) { struct dst_entry *dst = skb->dst; struct device *dev = dst->dev; struct hh_cache *hh = dst->hh; skb->dev = dev; skb->protocol = __constant_htons(ETH_P_IP); if (hh) { read_lock_irq(&hh->hh_lock); memcpy(skb->data - 16, hh->hh_data, 16); read_unlock_irq(&hh->hh_lock); skb_push(skb, dev->hard_header_len); return hh->hh_output(skb); } else if (dst->neighbour) return dst->neighbour->output(skb); kfree_skb(skb); return -EINVAL; } 注意这里的hh_output和neighbour->output函数指针,一般都是指向 dev_queue_xmit( )函数。这是在文件net/ipv4/arp.c中的函数arp_constructor( ) 中赋值的。 include/net/pkt_sched.h extern __inline__ void qdisc_wakeup(struct device *dev) { if (!dev->tbusy) { struct Qdisc *q = dev->qdisc; if (qdisc_restart(dev) && q->h.forw == NULL) { q->h.forw = qdisc_head.forw; qdisc_head.forw = &q->h; } } } dev->tbusy标识该设备中是否有等待发送的skb.如果有则直接返回.如果没有, 则将该设备的dev->qdisc->h加入由qdisc_head标识的链表,并调用例程qdisc_restart()。 qdisc_head标识一个链表,某设备的dev->qdisc->h加入了这个链表,说明该设备的输出 队列中有等待输出的skb,且dev->tbusy=1.系统会定时遍历这个链表,也就是定时从链表 中每一个设备的队列中取出一个skb,并通过网卡发送出去.下面是例程qdisc_restart(). 它的主要作用就是从设备的队列中取出一个skb并调用dev->hard_start_xmit()将skb发 送出去。 net/sched/sch_generic.c int qdisc_restart(struct device *dev) { struct Qdisc *q = dev->qdisc; struct sk_buff *skb; if ((skb = q->dequeue(q)) != NULL) { if (netdev_nit) dev_queue_xmit_nit(skb, dev); if (dev->hard_start_xmit(skb, dev) == 0) { q->tx_last = jiffies; return -1; } /* Device kicked us out :( * This is possible in three cases: 1. fastroute is enabled 2. device cannot determine busy state before start of transmission (f.e. dialout) 3. device is buggy (ppp) */ q->ops->requeue(skb, q); return -1; } return q->q.qlen; } |