本文主要讲解了Linux内核数据包的传输流程,使用的内核的版本是2.6.32.27
为了方便理解,本文采用整体流程图加伪代码的方式
从内核高层面上梳理了二层数据包发送传输的流程,
希望可以对大家有所帮助。
阅读本文章假设大家对C语言有了一定的了解
整体流程如下
数据包的传输可以分为两种:
一种是正常的传输流程,即一般网卡的发送流程用于一般的;另一种是基于软中断的发送流程,这种发送流程用于CPU冲突时候的重新调度和QOS的流量整形
正常的传输流程伪代码如下:
- /*正常传输流程*/
- /*高层协议dev_queue_xmit(skb)发送数据报文*/
- static int pppoe_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len)
- {
- ` skb = sock_wmalloc(sk, total_len + dev->hard_header_len + 32, 0, GFP_KERNEL);
- if (!skb) {
- error = -ENOMEM;
- goto end;
- }
- //......
- dev_queue_xmit(skb);
- end:
- release_sock(sk);
- return error;
- }
- /*正常传输流程*/
- int dev_queue_xmit(struct sk_buff *skb)
- {
- struct net_device *dev = skb->dev; /*得到网络设备*/
- struct Qdisc *q;
- /*选取net_device中的_tx队列*/
- txq = dev_pick_tx(dev, skb);
- {
- txq = &dev->_tx[index]
- }
- /*取得Qdisc*/
- q = rcu_dereference(txq->qdisc);
- {
- q = dev->_tx[index]->qdisc;
- }
- if (q->enqueue)
- {
- rc = __dev_xmit_skb(skb, q, dev, txq);
- {
- rc = qdisc_enqueue_root(skb, q);
- {
- struct Qdisc *sch = q;
- /*将数据报文至于设备发送队列中, 一般采用FIFO算法进行发送,标记数据包发送准备完毕*/
- qdisc_enqueue(skb, sch) ;
- {
- sch->enqueue(skb, sch);
- }
- }
- /*触发对发送准备完毕的数据包的进一步处理*/
- qdisc_run(q);
- }
- goto out;
- }
- /*如果没有定义队列的管理方法,即q->enqueue == NULL,直接调用dev_hard_start_xmit发送数据包
- * 一般是逻辑网络设备,如环回口或者隧道口
- */
- if (dev->flags & IFF_UP) {
- dev_hard_start_xmit(skb, dev, txq)
- {
- const struct net_device_ops *ops = dev->netdev_ops;
- int rc;
- rc = ops->ndo_start_xmit(skb, dev); /*硬件发送数据包*/
- }
- }
- out_kfree_skb:
- kfree_skb(skb);
- return rc;
- out:
- rcu_read_unlock_bh();
- return rc;
- }
- static inline void qdisc_run(struct Qdisc *q)
- {
- /*反复调用qdisc_restart直到返回空值(队列中不在含有数据包)
- *或者网络不在接受任何数据包为止*/
- __qdisc_run(q);
- {
- while (qdisc_restart(q))
- {
- if (need_resched() || jiffies != start_time) {
- __netif_schedule(q);
- break;
- }
- }
- }
- }
- /*
- * 从设备队列中获取下一个数据包并将其发出,
- * 一般而言只有一个队列并按照FIFO原则运作
- * 可以通过Qdisc对它们赋予某种策略
- */
- static inline int qdisc_restart(struct Qdisc *q)
- {
- struct netdev_queue *txq;
- struct net_device *dev;
- spinlock_t *root_lock;
- struct sk_buff *skb;
- /*请求下一个数据包*/
- skb = dequeue_skb(q);
- {
- skb = q->dequeue(q);
- }
- /*没有stop的情况下,发送数据包*/
- sch_direct_xmit(skb, q, dev, txq, root_lock);
- {
- /*试图获取txq->_xmit_loc的锁,并记录CPU到txq->xmit_lock_owner中*/
- HARD_TX_LOCK(dev, txq, smp_processor_id());
- {
- __netif_tx_lock(txq, cpu);
- {
- spin_lock(&txq->_xmit_lock);
- txq->xmit_lock_owner = cpu;
- }
- }
- if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq))
- ret = dev_hard_start_xmit(skb, dev, txq);
- {
- const struct net_device_ops *ops = dev->netdev_ops;
- int rc;
- rc = ops->ndo_start_xmit(skb, dev); /*硬件发送数据包*/
- }
- HARD_TX_UNLOCK(dev, txq);
- switch (ret) {
- case NETDEV_TX_OK:
- /* Driver sent out skb successfully */
- ret = qdisc_qlen(q);
- break;
- case NETDEV_TX_LOCKED:
- /* Driver try lock failed */
- ret = handle_dev_cpu_collision(skb, txq, q);
- {
- if (unlikely(dev_queue->xmit_lock_owner == smp_processor_id())) {
- /*同一个CPU被锁住了,说明存在处理器死循环,该网络适配器的数据包发送因为某种原因失败了,
- * 并做出了重新传输一个数据包的尝试,这里只做简单丢弃并立即从qdisc_run返回以完成第一个传输进程*/
- kfree_skb(skb);
- printk(KERN_WARNING "Dead loop on netdevice %s, fix it urgently!\n", dev_queue->dev->name);
- } else {
- /*其他CPU正在发送其他的数据包,使用requeue()进行重新调度,
- *在netif_schedule()中激活NET_TX_SOFTIRQ以便再次触发发送*/
- ret = dev_requeue_skb(skb, q);
- {
- __netif_schedule(q);
- {
- __netif_reschedule(q)
- {
- raise_softirq_irqoff(NET_TX_SOFTIRQ);
- }
- }
- }
- }
- }
- break;
- }
- }
- }
基于软中断的发送流程伪代码如下,要理解这部分内容,需要大家对中断下半部的软中断机制和作用做一些了解,以后的博客文章中会进行讲解
如果需要QOS的流量整形,那么内核启动定时器,定时调用__netif_schedule()来触发软中断,达到定时定流量的作用
- /*NETIF_TX_SOFTIRQ上的传输,通过__netif_schedule触发
- * 使用的场景是
- * 一:数据报文发送失败
- * 二:在QOS中进行流量整形,按照一定的时间定时启动定时器,进行数据定速率发送
- *对__netif_schedule的调用在下一次CPU进行调度的时候被触发
- */
- void __netif_schedule(struct Qdisc *q)
- {
- __netif_reschedule(q);
- {
- raise_softirq_irqoff(NET_TX_SOFTIRQ);
- }
- }
- /*
- * 设定NET_TX_SOFTIRQ 对应的处理句柄是net_tx_action
- */
- static int __init net_dev_init(void)
- {
- open_softirq(NET_TX_SOFTIRQ, net_tx_action);
- }
- /*调用qdisc_run启动网络设备数据包的传输*/
- static void net_tx_action(struct softirq_action *h)
- {
- while (head) {
- qdisc_run(q);
- }
- }
我们可以清晰的了解到Linux上对数据报文的一般发送传输的流程。
希望大家批评指正
original link:http://blog.csdn.net/eric_liufeng/article/details/10252857