linux 绑定网卡发送数据,多队列网卡简介以及Linux通过网卡发送数据包源码解读...

首先我们看一下一个主流多队列网卡(E1000)跟多核CPU之间的关系图:

0818b9ca8b590ca3270a3433284dd417.png

非多队列:

linux的网卡由结构体net_device表示,一个该结构体对应一个可以调度的数据包发送队列。

数据包的实体在内核中以结构体sk_buff(skb),形如:

0818b9ca8b590ca3270a3433284dd417.png

多队列:

一个网卡可以拥有多个队列

0818b9ca8b590ca3270a3433284dd417.png

接下来,看看TX引擎是如何工作的(注:对于发送和接收数据包有两个名词,分别应对TX,RX)

0818b9ca8b590ca3270a3433284dd417.png

注:TXQ代表网卡中的队列。

解释:

函数-dev_queue_xmit():入队一个buffer到qdisc以传输到网络驱动设备。

配合该函数的源码来解释上图的传输过程:

步骤一:可以看到如果设备支持队列,则数据包入设备队列。在入队操作前后,有加锁和解锁的过程。

0818b9ca8b590ca3270a3433284dd417.png

步骤二:调出设备的qdisc(该对象是队列的排队规则)

0818b9ca8b590ca3270a3433284dd417.png

Qdisc(排队规则)是queueingdiscipline的简写,它是理解流量控制(traffic control)的基础。无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。然后,内核会尽可能多地从qdisc里面取出数据包,把它们交给网络适配器驱动模块。最简单的qdisc是pfifo它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列。不过,它会保存网络接口一时无法处理的数据包。

步骤三:重置skb的队列映射,置为0

0818b9ca8b590ca3270a3433284dd417.png

步骤四:tx lock->hard_start_xmit

到这里,我们好像没有看到tx_lock、hard_start_xmit函数,反而我们在无队列的设备分支中看到了这些:

0818b9ca8b590ca3270a3433284dd417.png

dev_hard_start_xmit的定义:

0818b9ca8b590ca3270a3433284dd417.png

很明显我们应该拨开云雾看到一些本质,再次回到设备支持队列的分支中(这才是我们关心的): 不管怎么样,你总该有发送的函数调用吧,就是下面圈起来的这个:

0818b9ca8b590ca3270a3433284dd417.png

果不其然,这是一个封装函数:

0818b9ca8b590ca3270a3433284dd417.png

关于qdisc的说明: IP包的输出过程

输出时主要是调用qdisc_restart( )函数,该函数的作用是将一个包交给网络设备,它在很多地方被调用,也就是说,对于包的输出,Linux系统采用灵活多样的

方式。qdisc_restart( )又调用相应设备的提供的hard_start_xmit函数将包交给设备。hard_start_xmit是struct device结构中的一个函数指针,网络设备初始化时,

将这个指针赋值(具体的函数名称请参见相应的设备驱动程序),这样,给IP层提供了一个将包发往下层的接口。

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;

}

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值