从网络协议到网卡数据报传输的具体过程,以下研究的代码来基于2.4.
无论是ip还是arp协议,当有数据要发送的时候都会调用dev_queue_xmit函数,也就是说,dev_queue_xmit是驱动程序对上面的接口.
由上面的注释可以看到,dev_queue_xmit会遇到两种情况,一种情况是有traffic control层(QoS层),一种是直接调用设备驱动程序的发送函数hard_start_xmit(比如一般的虚拟设备都是如此)前种情况的路线是:dev_queue_xmit-->qdisc_run-->qdisc_restart(dev),后者的路线是:dev_queue_xmit-->hard_start_xmit.
qdisc的源代码为:
static inline void qdisc_run(struct net_device *dev) |
可以看出,qdisc_run只是qdisc_restart的包裹函数,此函数过滤了发送队列被禁止的dev,然后调用qdisc_restart.
/*函数作用:当device有自己的发送队列时的发送函数. |
可以看到,调用了qdisk_restart函数的一般都是device有自己的发送队列的情况,此时在出队列函数dequeue处体现了traffic control的作用.
由代码可知,在发送失败时,会将skb重新放入队列中,然后调用netif_schedule(dev)将其重新发送.下面来看看netif_schedule的源代码.
static inline void netif_schedule(struct net_device *dev) |
可以看到,netif_schedule是__netif_schedule(dev)的包裹函数.
/* |
如注释所示,此函数功能很简单,把参数传进来的dev放到softnet_data[cpu].output_queue的链表首,优先等待调度,然后触发发送软中断,调用相应的发送处理函数,即net_tx_action.读到此,产生了两个问题,就是说,发送成功后,直接返回-1,由于某种原因不能发送skb时才会有这些操作,也就是说,只有在因为某种原因不能发送skb时,需要将数据报重新放回队列,然后把dev放在output_queue的链表首,此时才触发发送软中断,不是每次发送数据时都触发发送软中断?还有,我记得当数据报发送数据包成功后会把skb加入到completion_queue中,但是怎么没看到相关代码?这两个问题先放下,看看读完net_tx_action的代码后能不能找到答案.
/* |
此函数完成两件事,如上面代码注释可知: 1,释放softnet_data[cpu].completion_queue中发送成功的sk_buff. 2,调度softnet_data[cpu].output_queue中的device发送其发送队列中的数据.突然想起在以前读ldd2的时候看见过如下一句话:网络接口在两种可能的事件下中断处理器:新数据包的到达,或者外发数据包的传输已经完成.好像想起了点什么,觉得应该是这样:当数据报成功传送完之后,把其加入到completion_queue中的代码应该是在网卡驱动中具体实现的,传输成功后,会调用net_tx_action来处理completion_queue中发送成功的数据报,然后在调度output_queue中的device传送其发送队列中的数据包.读到这时,最少两种情况会调用net_tx_action,第一种情况是在由于某种原因不能发送skb时(比如不能获得dev的发送队列的锁dev->xmit_lock),或者在网卡驱动程序成功发送完数据包后,会产生中断,然后执行发送软中断处理函数.这样理解可以理解通,不知道实事上是不是这样,如果我想错了,知道正确答案的朋友,希望能给予及时指正,不胜感激.
这里要知道,跟接收数据包时的原理一样,一般不会将所有事情都在中断环境中解决,所以当一个网卡发送完数据包后,并不是直接的在中断环境下释放skb资源,而是将其放入completion_queue中,这块也只是指针的指向,而不是数据包的拷贝,然后调用发送软中断来处理数据包.当驱动程序传输完数据包后,会调用dev_kfree_skb_irq来释放sk_buff.
static inline void dev_kfree_skb_irq(struct sk_buff *skb) |
此处可以看出来,此函数只是把skb放入completion_queue的链表头,真正的释放工作在发送软中断net_tx_action中完成.
ok,分析到这,基本上算是通了.下面是自己在研究以上代码时随手记的一些零散知识点,有看到这篇笔记的朋友们可以直接pass了,自己不舍得扔掉,于是贴在此.
数据包的发送过程和接收过程大体上类似:
1,NET_TX_SOFTIRQ:发送软中断 <--------> NET_RX_SOFTIRQ:接收软中断 ; net_tx_action:发送软中断处理函数 <--------> net_rx_action:接收软中断处理函数 ; dev_queue_xmit <--------> netif_rx ; output_queue:NAPI和non-NAPI都用<-------->poll_list:只有non_NAPI用.
2,当一个device被调用以用来接收数据时,其device的__LINK_STATE_RX_SCHED被标记.当一个device被调用以用来发送数据时,其device的__LINK_STATR_SCHED被标记.
3,当一个device正在被调用时,为什么要使能或禁止队列的传输功能呢?
一个原因是,一个device可能暂时用光其存储空间,所以会导致传输失败.
解决办法:当驱动程序获知devicedevice没有足够的space来存储一个数据包时,调用netif_stop_queue禁止传输.相反,当驱动程序获知device有足够的space来存储一个skb时,则调用netif_start_queue使能传输功能.
4,netif_wake_queue = netif_start_queue + netif_schedule
5,当传输一个数据时.当以下条件发生时,需要把数据包重新入队.
1)发送队列被禁止(netif_queue_stopped(dev) is true)
2)另一个cpu正持有此device driver的lock.
3)传输失败.
6,dev_kfree_skb:释放skb ; dev_kfree_skb_irq:仅仅把skb放入到softnet_data的completion_queue中,然后调用发送软中断,让net_tx_action完成真正的释放工作.
============================================================================================
二层(链路层)数据包发送过程分析
涉及内核版本为2.6.32
当上层准备好一个包之后,交给链路层,链路层数据包发送主要通过dev_queue_xmit函数处理。数据包的发送可分为两种,一种是正常的传输流程,即通过网卡驱动,另一种是通过软中断(见注3)。为了理解方便,首先看一下dev_queue_xmi函数的整体调用关系图。
//000
- dev_queue_xmit
本函数用来将带发送的skb加入一个dev的队列(Queue),调用这个函数前必须设置好skb的device和priority,本函数可以在中断上下文中被调用。
返回值:
返回非0(正数或负数)表示函数出错,返回0表示成功,但是并不表示数据包被成功发送出去,因为数据包可能因为限速等原因被丢掉。
函数执行后传入的skb将被释放,所以如果想控制数据包,实现对skb的重传时需要增加skb的引用计数。
当调用此函数时中断必须是打开的,因为BH enable必须要求IRQ enable,否则会造成死锁。
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | int dev_queue_xmit(struct sk_buff *skb) { struct net_device *dev = skb->dev; struct netdev_queue *txq; struct Qdisc *q; int rc = -ENOMEM; /* GSO will handle the following emulations directly. */ if (netif_needs_gso(dev, skb)) goto gso; if (skb_has_frags(skb) && !(dev->features & NETIF_F_FRAGLIST) && __skb_linearize(skb)) goto out_kfree_skb; //如果skb有分片但是发送设备不支持分片,或分片中有分片在高端内存但发送设备不支持DMA,需要将所有段重新组合成一个段 ,这里__skb_linearize其实就是__pskb_pull_tail(skb, skb->data_len),这个函数基本上等同于pskb_may_pull ,pskb_may_pull的作用就是检测skb对应的主buf中是否有足够的空间来pull出len长度,如果不够就重新分配skb并将frags中的数据拷贝入新分配的主buff中,而这里将参数len设置为skb->datalen, 也就是会将所有的数据全部拷贝到主buff中,以这种方式完成skb的线性化。 if (skb_shinfo(skb)->nr_frags && (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) && __skb_linearize(skb)) goto out_kfree_skb; //如果数据包没有被计算校验和并且发送设备不支持这个协议的校验,则在此进行校验和的计算(注1)。如果上面已经线性化了一次,这里的__skb_linearize就会直接返回,注意区别frags和frag_list,前者是将多的数据放到单独分配的页面中,sk_buff只有一个。而后者则是连接多个sk_buff if (skb->ip_summed == CHECKSUM_PARTIAL) { skb_set_transport_header(skb, skb->csum_start - skb_headroom(skb)); if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb)) goto out_kfree_skb; } gso: //关闭软中断,禁止cpu抢占 rcu_read_lock_bh(); //选择一个发送队列,如果设备提供了select_queue回调函数就使用它,否则由内核选择一个队列,这里只是Linux内核多队列的实现,但是要真正的使用都队列,需要网卡支持多队列才可以,一般的网卡都只有一个队列。在调用alloc_etherdev分配net_device是,设置队列的个数 txq = dev_pick_tx(dev, skb); // 从netdev_queue结构上获取设备的qdisc q = rcu_dereference(txq->qdisc); //如果该设备有队列可用,就调用__dev_xmit_skb if (q->enqueue) { rc = __dev_xmit_skb(skb, q, dev, txq); goto out; } //下面的处理是在没有发送队列的情况,软设备一般没有发送队列:如lo、tunnle;我们所要做的就是直接调用驱动的hard_start_xmit将它发送出去 如果发送失败就直接丢弃,因为没有队列可以保存它 if (dev->flags & IFF_UP) { //确定设备是否开启 int cpu = smp_processor_id(); /* ok because BHs are off */ if (txq->xmit_lock_owner != cpu) {//是否在同一个cpu上 HARD_TX_LOCK(dev, txq, cpu); if (!netif_tx_queue_stopped(txq)) {//确定队列是运行状态 rc = NET_XMIT_SUCCESS; if (!dev_hard_start_xmit(skb, dev, txq)) { HARD_TX_UNLOCK(dev, txq); goto out; } } HARD_TX_UNLOCK(dev, txq); if (net_ratelimit()) printk(KERN_CRIT "Virtual device %s asks to " "queue packet!\n", dev->name); } else {// txq->xmit_lock_owner == cpu的情况,说明发生递归 if (net_ratelimit()) printk(KERN_CRIT "Dead loop on virtual device " "%s, fix it urgently!\n", dev->name); } } rc = -ENETDOWN; rcu_read_unlock_bh(); out_kfree_skb: kfree_skb(skb); return rc; out: rcu_read_unlock_bh(); return rc; } |
- __dev_xmit_skb
__dev_xmit_skb函数主要做两件事情:
(1)如果流控对象为空的,试图直接发送数据包。
(2)如果流控对象不空,将数据包加入流控对象,并运行流控对象。
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | staticinlineint__dev_xmit_skb(structsk_buff*skb,structQdisc*q, structnet_device*dev, structnetdev_queue*txq) { spinlock_t*root_lock=qdisc_lock(q);//见注2 intrc; spin_lock(root_lock);//锁qdisc if(unlikely(test_bit(__QDISC_STATE_DEACTIVATED,&q->state))){//判断队列是否失效 kfree_skb(skb); rc=NET_XMIT_DROP; }elseif((q->flags&TCQ_F_CAN_BYPASS)&&!qdisc_qlen(q)&& !test_and_set_bit(__QDISC_STATE_RUNNING,&q->state)){ /* * This is a work-conserving queue; there are no old skbs * waiting to be sent out; and the qdisc is not running - * xmit the skb directly. */ __qdisc_update_bstats(q,skb->len); if(sch_direct_xmit(skb,q,dev,txq,root_lock)) __qdisc_run(q); else clear_bit(__QDISC_STATE_RUNNING,&q->state); rc=NET_XMIT_SUCCESS; }else{ rc=qdisc_enqueue_root(skb,q); qdisc_run(q); } spin_unlock(root_lock); returnrc; } lqdisc_run 有两个时机将会调用qdisc_run(): 1.__dev_xmit_skb() 2.软中断服务线程NET_TX_SOFTIRQ 点击(此处)折叠或打开 staticinlinevoidqdisc_run(structQdisc*q) { if(!test_and_set_bit(__QDISC_STATE_RUNNING,&q->state))//将队列设置为运行状态 __qdisc_run(q); } l__qdisc_run 点击(此处)折叠或打开 void__qdisc_run(structQdisc*q) { unsignedlongstart_time=jiffies; while(qdisc_restart(q)){//返回值大于0,说明流控对象非空 /*如果发现本队列运行的时间太长了,将会停止队列的运行,并将队列加入output_queue链表头 * Postpone processing if (延迟处理) * 1. another process needs the CPU; * 2. we've been doing it for too long. */ if(need_resched()||jiffies!=start_time){//已经不允许继续运行本流控对象 __netif_schedule(q);//将本qdisc加入每cpu变量softnet_data的output_queue链表中 break; } } //清除队列的运行标识 clear_bit(__QDISC_STATE_RUNNING,&q->state); } |
循环调用qdisc_restart发送数据,下面这个函数qdisc_restart是真正发送数据包的函数,它从队列上取下一个帧,然后尝试将它发送出去,若发送失败则一般是重新入队。
此函数返回值为:发送成功时返回剩余队列长度,发送失败时返回0(若发送成功且剩余队列长度为0也返回0)
- qdisc_restart
__QDISC_STATE_RUNNING状态保证同一时刻只有一个cpu在处理这个qdisc,qdisc_lock(q)用来保证对这个队列的顺序访问。
通常netif_tx_lock用来确保本设备驱动的顺序(独占)访问的,qdisc_lock(q)用来保证qdisc的顺序访问,这两个是互斥的,获得其中一个必须释放另一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static inline int qdisc_restart(struct Qdisc *q) { struct netdev_queue *txq; struct net_device *dev; spinlock_t *root_lock; struct sk_buff *skb; /* Dequeue packet */ skb = dequeue_skb(q); //一开始就调用dequeue函数 if (unlikely(!skb)) return 0; //返回0说明队列是空的或者被限制 root_lock = qdisc_lock(q); dev = qdisc_dev(q); txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb)); return sch_direct_xmit(skb, q, dev, txq, root_lock); //用于发送数据包 } |
- sch_direct_xmit
发送一个skb,将队列置为__QDISC_STATE_RUNNING状态,保证只有一个cpu运行这个函数,返回0表示队列为空或者发送受限,大于0表示队列非空。
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | intsch_direct_xmit(structsk_buff*skb,structQdisc*q, structnet_device*dev,structnetdev_queue*txq, spinlock_t*root_lock) { intret=NETDEV_TX_BUSY; spin_unlock(root_lock);// release qdisc,因为后面要获取设备锁 // 调用__netif_tx_lockà spin_lock(&txq->_xmit_lock,,保证设备驱动的独占访问 HARD_TX_LOCK(dev,txq,smp_processor_id()); if(!netif_tx_queue_stopped(txq)&&//设备没有被停止,且发送队列没有被冻结 !netif_tx_queue_frozen(txq)) ret=dev_hard_start_xmit(skb,dev,txq);//发送数据包 HARD_TX_UNLOCK(dev,txq);// 调用__netif_tx_unlock spin_lock(root_lock); switch(ret){ caseNETDEV_TX_OK://如果设备成功将数据包发送出去 ret=qdisc_qlen(q);//返回剩余的队列长度 break; caseNETDEV_TX_LOCKED://获取设备锁失败 ret=handle_dev_cpu_collision(skb,txq,q); break; default://设备繁忙,重新入队发送(利用softirq) if(unlikely(ret!=NETDEV_TX_BUSY&&net_ratelimit())) printk(KERN_WARNING"BUG %s code %d qlen %d\n", dev->name,ret,q->q.qlen); ret=dev_requeue_skb(skb,q); break; } if(ret&&(netif_tx_queue_stopped(txq)|| netif_tx_queue_frozen(txq))) ret=0; returnret; } |
- dev_hard_start_xmit
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq) { const struct net_device_ops *ops = dev->netdev_ops; int rc; if (likely(!skb->next)) { //从这里可以看出,对于每一个发送的包也会发给ptype_all一份, 而packet套接字创建时对于proto为ETH_P_ALL的会在ptype_all中注册一个成员,因此对于协议号为ETH_P_ALL的packet套接字来说,发送和接受的数据都能收到 if (!list_empty(&ptype_all)) dev_queue_xmit_nit(skb, dev); if (netif_needs_gso(dev, skb)) { if (unlikely(dev_gso_segment(skb))) goto out_kfree_skb; if (skb->next) goto gso; } //如果发送设备不需要skb->dst,则在此将其释放 if (dev->priv_flags & IFF_XMIT_DST_RELEASE) skb_dst_drop(skb); //调用设备注册的发送函数,即dev->netdev_ops-> ndo_start_xmit(skb, dev) rc = ops->ndo_start_xmit(skb, dev); if (rc == NETDEV_TX_OK) txq_trans_update(txq); return rc; } gso: …… } |
- dev_queue_xmit_nit
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | staticvoiddev_queue_xmit_nit(structsk_buff*skb,structnet_device*dev) { structpacket_type*ptype; #ifdef CONFIG_NET_CLS_ACT if(!(skb->tstamp.tv64&&(G_TC_FROM(skb->tc_verd)&AT_INGRESS))) net_timestamp(skb);//记录该数据包输入的时间戳 #else net_timestamp(skb); #endif rcu_read_lock(); list_for_each_entry_rcu(ptype,&ptype_all,list){ /* Never send packets back to the socket they originated from */ //遍历ptype_all链表,查找所有符合输入条件的原始套接口,并循环将数据包输入到满足条件的套接口 if((ptype->dev==dev||!ptype->dev)&& (ptype->af_packet_priv==NULL|| (structsock*)ptype->af_packet_priv!=skb->sk)){ //由于该数据包是额外输入到这个原始套接口的,因此需要克隆一个数据包 structsk_buff*skb2=skb_clone(skb,GFP_ATOMIC); if(!skb2) break; /* skb->nh should be correctly(确保头部偏移正确) set by sender, so that the second statement is just protection against buggy protocols. */ skb_reset_mac_header(skb2); if(skb_network_header(skb2)<skb2->data|| skb2->network_header>skb2->tail){ if(net_ratelimit())//net_ratelimit用来保证网络代码中printk的频率 printk(KERN_CRIT"protocol %04x is " "buggy, dev %s\n", skb2->protocol,dev->name); skb_reset_network_header(skb2);//重新设置L3头部偏移 } skb2->transport_header=skb2->network_header; skb2->pkt_type=PACKET_OUTGOING; ptype->func(skb2,skb->dev,ptype,skb->dev);//调用协议(ptype_all)接受函数 } } rcu_read_unlock(); } |
- 环回设备
对于环回设备loopback,设备的ops->ndo_start_xmit被初始化为loopback_xmit函数。
1 2 3 4 5 | static const struct net_device_ops loopback_ops = { .ndo_init = loopback_dev_init, .ndo_start_xmit= loopback_xmit, .ndo_get_stats = loopback_get_stats, }; |
- drivers/net/loopback.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | staticnetdev_tx_tloopback_xmit(structsk_buff *skb, structnet_device *dev) { structpcpu_lstats *pcpu_lstats,*lb_stats; intlen; skb_orphan(skb); skb->protocol=eth_type_trans(skb,dev); /* it's OK to use per_cpu_ptr() because BHs are off */ pcpu_lstats=dev->ml_priv; lb_stats=per_cpu_ptr(pcpu_lstats,smp_processor_id()); len=skb->len; if(likely(netif_rx(skb)==NET_RX_SUCCESS)){//直接调用了netif_rx进行了接收处理 lb_stats->bytes+=len; lb_stats->packets++; }else lb_stats->drops++; returnNETDEV_TX_OK; } |
- 注:
1. CHECKSUM_PARTIAL表示使用硬件checksum ,L4层的伪头的校验已经完毕,并且已经加入uh->check字段中,此时只需要设备计算整个头4层头的校验值。
2. 整个数据包发送逻辑中会涉及到三个用于互斥访问的代码:
(1)spinlock_t *root_lock = qdisc_lock(q);
(2)test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)
(3)__netif_tx_lockà spin_lock(&txq->_xmit_lock)
其中(1)(3)分别对应一个spinlock,(2)对应一个队列状态。在了解代码中如何使用这三个同步方法时,首先看一下相关数据结构的关系,如下。
图中绿色部分表示(1)(3)两处spinlock。首先看(1)处对应的代码:
1 2 3 4 | staticinlinespinlock_t *qdisc_lock(structQdisc *qdisc) { return&qdisc->q.lock; } |
所以root_lock是用于控制qdisc中skb队列访问的锁,当需要对skb队列进行enqueue、dequeue、requeue时,就需要加锁。
__QDISC_STATE_RUNNING标志用于保证一个流控对象(qdisc)不会同时被多个cpu访问。
而(3)处的spinlock,即struct netdev_queue中的_xmit_lock,则用于保证dev的注册函数的互斥访问,即deriver的同步。
另外,内核代码注释中写到,(1)和(3)是互斥的,获得(1)处的锁时必须先保证释放(3)处的锁,反之亦然,为什么要这样还没有想明白。。。。哪位大神知道还望指点
3. 已经有了dev_queue_xmit函数,为什么还需要软中断来发送呢?
我们可以看到在dev_queue_xmit中将skb进行了一些处理(比如合并成一个包,计算校验和等),处理完的skb是可以直接发送的了,这时dev_queue_xmit也会先将skb入队(skb一般都是在这个函数中入队的),并且调用qdisc_run尝试发送,但是有可能发送失败,这时就将skb重新入队,调度软中断,并且自己直接返回。
软中断只是发送队列中的skb以及释放已经发送的skb,它无需再对skb进行线性化或者校验和处理。另外在队列被停止的情况下,dev_queue_xmit仍然可以把包加入队列,但是不能发送,这样在队列被唤醒的时候就需要通过软中断来发送停止期间积压的包。简而言之,dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去。(其实发送软中断还有一个作用,就是释放已经发送的包,因为某些情况下发送是在硬件中断中完成的,为了提高硬件中断处理效率,内核提供一种方式将释放skb放到软中断中进行,这时只要调用dev_kfree_skb_irq,它将skb加入softnet_data的completion_queue中,然后开启发送软中断,net_tx_action会在软中断中将completion_queue中的skb全部释放掉)