Receive

1.  napi && none napi

讲网络收报过程,必然要涉及到网卡收报模型发展历史。总体上看,网络收报过经历了如下发展过程:

轮询 ---à 中断 ---à 中断期间处理多帧 ---à定时器驱动的中断 ---à中断加轮询

轮询:最早出现的收报方式,易于理解和编程,核心思想是cpu不断读取网卡指定寄存器来判断是否有数据达到netdevice,从而进一步决定是否启动收报线程。轮询的特点是低流量时浪费cpu资源,cpu负载过高,高流量时表现较好。

中断:每当有一个数据帧到达网卡时候,网卡负责发出收报中断,cpu启动收报线程。显而易见,在低负载时候,cpu只需要响应网卡的收报中断,其他时间可以shedule 别的内核线程,资源利用率较高,然而在高负载的情况下,cpu必然后因为疲于应付网卡中断而无暇顾及其它优先级较低的中断,耗费掉大量的cpu资源。此方式即为我们常说的 none napi。

中断期间处理多帧:中断收报方式的改进,一次收报中断,cpu处理多个网络数据帧。在网络流量较大的情况下,避免了频繁中断。此情况网卡需要较大缓存。

定时器驱动中断:由网卡定时发出中断(也可由cpu模拟)。

中断加轮询:结合中断在低负载和轮询在高负载的优势, mac收到一个包来后会产生接收中断,但是马上关闭。直到收够了netdev_max_backlog个包(默认300),或者收完mac上所有包后,才再打开接收中断。此方式即为我们常说的napi。

2. data structure

  1. 1218 struct softnet_data
  2. 1219 {
  3. 1220 struct Qdisc *output_queue;
  4. 1221 struct sk_buff_head input_pkt_queue;
  5. 1222 struct list_head poll_list;
  6. 1223 struct sk_buff *completion_queue;
  7. 1224
  8. 1225 struct napi_struct backlog;
  9. 1226 };

上面提到的softdate_net结构是用于进行报文收发调度的结构,内核为每个CPU维护一个这样的结构,这样不同CPU之间就没必要使用上锁机制。其中需要重点关注如下三种数据结构:

a.input_pkt_queue: none napi情况下,接受到的skb被放入该队列。

b.backlog: none napi情况下会用到的一个虚拟网络设备。

c.poll_list: 网络设备dev的队列。其中的设备接收到了报文,需要被处理;napi和none napi都会用到的。

  如下图所示,napi 和 none napi 方式,都会调用 __netif_rx_schedule 将收到数据的dev链接到poll_list结构,然后触发软中段,稍后再由软中断处理函数 net_rx_action 对当前CPU的softdate_net结构的poll_list队列中的所有dev,调用dev->poll方法。对于napi 来说,dev->poll 方法是驱动程序自己提供的。对于 none napi设备来说,为了兼容这样的处理方式,接收到skb被放入input_pkt_queue队列,然后虚拟设备backlog_dev被加入poll_list。而最后, process_backlog作为虚拟设备backlog_dev->poll函数将对input_pkt_queue队列中的skb进行处理。

3. napi(e100网卡)

每个网络设备(MAC层)都有自己的net_device数据结构,这个结构上有napi_struct。每当收到数据包时,网络设备驱动会把自己的napi_struct挂到CPU私有变量上。

这样在软中断时,net_rx_action会遍历cpu私有变量的poll_list,执行上面所挂的napi_struct结构的poll钩子函数,将数据包从驱动传到网络协议栈。

3.1 初始化相关全局数据结构,并注册收报和发包相关软中断处理函数

start_kernel()
    --> rest_init()
        -->
do_basic_setup()
       
    --> do_initcall

            
  -->net_dev_init

  1. static int __init net_dev_init(void)
  2. {
  3.  
  4. for_each_possible_cpu(i) {
  5. struct softnet_data *queue;
  6.  
  7. queue = &per_cpu(softnet_data, i);
  8. skb_queue_head_init(&queue->input_pkt_queue);
  9. queue->completion_queue = NULL;
  10. INIT_LIST_HEAD(&queue->poll_list);
  11.  
  12. queue->backlog.poll = process_backlog;
  13. queue->backlog.weight = weight_p;
  14. queue->backlog.gro_list = NULL;
  15. queue->backlog.gro_count = ;
  16. }
  17. goto out;
  18.  
  19. open_softirq(NET_TX_SOFTIRQ, net_tx_action);
  20. open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  21.  
  22. }

3.2 在驱动的 e100_probe 方法中,初始化napi结构,注册 e100_poll 轮询处理函数.

  1. 2717 static int __devinit e100_probe(struct pci_dev *pdev,
  2. 2718 const struct pci_device_id *ent)
  3. 2719 {
  4. 2720 struct net_device *netdev;
  5. 2721 struct nic *nic;
  6. 2722 int err;
  7. 2723
  8. 2724 if (!(netdev = alloc_etherdev(sizeof(struct nic)))) {
  9. 2725 if (((1 << debug) - 1) & NETIF_MSG_PROBE)
  10. 2726 printk(KERN_ERR PFX "Etherdev alloc failed, abort.\n");
  11. 2727 return -ENOMEM;
  12. 2728 }
  13. 2729
  14. 2730 netdev->netdev_ops = &e100_netdev_ops;
  15. 2731 SET_ETHTOOL_OPS(netdev, &e100_ethtool_ops);
  16. 2732 netdev->watchdog_timeo = E100_WATCHDOG_PERIOD;
  17. 2733 strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);
  18. 2734
  19. 2735 nic = netdev_priv(netdev);
  20. 2736 netif_napi_add(netdev, &nic->napi, e100_poll, E100_NAPI_WEIGHT);
  21. 2737 nic->netdev = netdev;
  22. 2738 nic->pdev = pdev;
  23. 2739 nic->msg_enable = (1 << debug) - 1;

  

3.3. 在 e100_open 方法:

a.分配存储以太网包的skb:

e100_open()

àe100_up()

àe100_rx_alloc_list()

  1. 2065 static int e100_rx_alloc_list(struct nic *nic)
  2. 2066 {
  3. 2067 struct rx *rx;
  4. 2068 unsigned int i, count = nic->params.rfds.count;
  5. 2069 struct rfd *before_last;
  6. 2070
  7. 2071 nic->rx_to_use = nic->rx_to_clean = NULL;
  8. 2072 nic->ru_running = RU_UNINITIALIZED;
  9. 2073
  10. 2074 if (!(nic->rxs = kcalloc(count, sizeof(struct rx), GFP_ATOMIC)))
  11. 2075 return -ENOMEM;
  12. 2076
  13. 2077 for (rx = nic->rxs, i = 0; i < count; rx++, i++) {
  14. 2078 rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;
  15. 2079 rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;
  16. 2080 if (e100_rx_alloc_skb(nic, rx)) {
  17. 2081 e100_rx_clean_list(nic);
  18. 2082 return -ENOMEM;
  19. 2083 }
  20. 2084 }

 b.e100_up中注册收报硬中断处理函数e100_intr().

  1. if ((err = request_irq(nic->pdev->irq, e100_intr, IRQF_SHARED,
  2. nic->netdev->name, nic->netdev)))
  3. goto err_no_irq;

3.4  ok,前期贮备工作好了,下面开始收报流程。

网卡收到数据包后,将数据DMA到skb->data结构中,然后保存现场,根据中断掩码,调用硬中断处理函数e100_intr()。e100_intr() 调用 __napi_schedule 将该网卡的 napi 结构挂载到当前cpu 的poll_list ,同时调用 __raise_softirq_irqoff() 触发收报软中断处理函数。

  1. static irqreturn_t e100_intr(int irq, void *dev_id)
  2. {
  3.  
  4. if (likely(napi_schedule_prep(&nic->napi))) {
  5. e100_disable_irq(nic);
  6. __napi_schedule(&nic->napi);
  7. }
  8.  
  9. void __napi_schedule(struct napi_struct *n)
  10. {
  11. unsigned long flags;
  12.  
  13. local_irq_save(flags);
  14. list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
  15. __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  16. local_irq_restore(flags);
  17. }

3.5 软中断函数net_rx_action().主要工作是遍历有数据帧等待接收的设备链表,对于每个设备,执行它相应的poll函数。

  1. 2834 static void net_rx_action(struct softirq_action *h)
  2. 2835 {
  3. 2836 struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
  4. //通过 napi_struct.poll_list, 将N多个 napi_struct 链接到一条链上
  5. //通过 CPU私有变量,我们找到了链头,然后开始遍历这个链
  6. 2837 unsigned long time_limit = jiffies + 2;
  7. 2838 int budget = netdev_budget;
  8. //这个值就是 net.core.netdev_max_backlog,通过sysctl来修改
  9. 2839 void *have;
  10. 2840
  11. 2841 local_irq_disable();
  12. 2842
  13. 2843 while (!list_empty(list)) {
  14. 2844 struct napi_struct *n;
  15. 2845 int work, weight;
  16. 2846
  17. 2847 /* If softirq window is exhuasted then punt.
  18. 2848 * Allow this to run for 2 jiffies since which will allow
  19. 2849 * an average latency of 1.5/HZ.
  20. 2850 */
  21. 2851 if (unlikely(budget <= 0 || time_after_eq(jiffies, time_limit)))
  22. 2852 goto softnet_break;
  23. 2853
  24. 2854 local_irq_enable();
  25. 2855
  26. 2861 n = list_entry(list->next, struct napi_struct, poll_list);
  27. 2862
  28. 2863 have = netpoll_poll_lock(n);
  29. 2864
  30. 2865 weight = n->weight;
  31. 2866
  32. 2867 /* This NAPI_STATE_SCHED test is for avoiding a race
  33. 2868 * with netpoll's poll_napi(). Only the entity which
  34. 2869 * obtains the lock and sees NAPI_STATE_SCHED set will
  35. 2870 * actually make the ->poll() call. Therefore we avoid
  36. 2871 * accidently calling ->poll() when NAPI is not scheduled.
  37. 2872 */
  38. 2873 work = 0;
  39. 2874 if (test_bit(NAPI_STATE_SCHED, &n->state)) {//检查状态标记,此标记在接收中断里加上的.
  40. 2875 work = n->poll(n, weight);
  41. //使用NAPI的话,使用的是网络设备自己的napi_struct.poll/对于e100,是e100_poll
  42. 2876 trace_napi_poll(n);
  43. 2877 }
  44. 2878
  45. 2879 WARN_ON_ONCE(work > weight);
  46.  
  47. 3.6 e100_poll.
  48.  
  49. 2132 static int e100_poll(struct napi_struct *napi, int budget)
  50. 2133 {
  51. 2137 e100_rx_clean(nic, &work_done, budget);
  52. 2138 e100_tx_clean(nic);
  53. 2139 ……
  54. 2147 }
  55.  
  56. 1967 static void e100_rx_clean(struct nic *nic, unsigned int *work_done,
  57. 1968 unsigned int work_to_do)
  58. 1969 {
  59. 1974
  60. 1975 /* Indicate newly arrived packets */
  61. 1976 for (rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
  62. 1977 err = e100_rx_indicate(nic, rx, work_done, work_to_do);
  63. 1981 }
  64.  
  65. 1884 static int e100_rx_indicate(struct nic *nic, struct rx *rx,
  66. 1885 unsigned int *work_done, unsigned int work_to_do)
  67. 1886 {
  68. 1887 struct net_device *dev = nic->netdev;
  69. 1888 struct sk_buff *skb = rx->skb;
  70. 1889 struct rfd *rfd = (struct rfd *)skb->data;
  71. 1890 u16 rfd_status, actual_size;
  72. 1891
  73. 1941
  74. 1942 /* Pull off the RFD and put the actual data (minus eth hdr) */
  75. 1943 skb_reserve(skb, sizeof(struct rfd));
  76. 1944 skb_put(skb, actual_size);
  77. 1945 skb->protocol = eth_type_trans(skb, nic->netdev);
  78. 1946
  79. 1947 if (unlikely(!(rfd_status & cb_ok))) {
  80. 1948 /* Don't indicate if hardware indicates errors */
  81. 1949 dev_kfree_skb_any(skb);
  82. 1950 } else if (actual_size > ETH_DATA_LEN + VLAN_ETH_HLEN) {
  83. 1951 /* Don't indicate oversized frames */
  84. 1952 nic->rx_over_length_errors++;
  85. 1953 dev_kfree_skb_any(skb);
  86. 1954 } else {
  87. 1955 dev->stats.rx_packets++;
  88. 1956 dev->stats.rx_bytes += actual_size;
  89. 1957 netif_receive_skb(skb);
  90. 1958 if (work_done)
  91. 1959 (*work_done)++;
  92. 1960 }
  93. 1961
  94. 1962 rx->skb = NULL;
  95. 1963
  96. 1964 return 0;
  97. 1965 }

  

主要工作在e100_rx_indicate()中完成,这主要重设SKB的一些参数,然后跟process_backlog(),一样,最终调用netif_receive_skb(skb)。

3.7 netif_receive_skb(skb)

这是一个辅助函数,用于在poll中处理接收到的帧。它主要是向各个已注册的协议处理例程发送一个SKB。

4. none napi (3c59x)

4.1 vortex_open() 方法注册硬中断处理函数 vortex_interrupt().

  1. 1698 vortex_open(struct net_device *dev)
  2. 1699 {
  3. 1700 struct vortex_private *vp = netdev_priv(dev);
  4. 1701 int i;
  5. 1702 int retval;
  6. 1703
  7. 1704 /* Use the now-standard shared IRQ implementation. */
  8. 1705 if ((retval = request_irq(dev->irq, vp->full_bus_master_rx ?
  9. 1706 &boomerang_interrupt : &vortex_interrupt, IRQF_SHARED, dev->name, dev))) {
  10. 1707 pr_err("%s: Could not reserve IRQ %d\n", dev->name, dev->irq);
  11. 1708 goto err;
  12. 1709 }

  

vortex_interrupt(),它会判断寄存器的值作出相应的动作:

  1. if (status & RxComplete)
  2. vortex_rx(dev);

  

如上,当中断指示,有数据包在等待接收,这时,中断例程会调用接收函数vortex_rx(dev)接收新到来的包(如下,只保留核心部分):

  1. static int vortex_rx(struct net_device *dev)
  2. {
  3. int pkt_len = rx_status & 0x1fff;
  4. struct sk_buff *skb;
  5.  
  6. skb = dev_alloc_skb(pkt_len + );
  7. if (vortex_debug > )
  8. pr_debug("Receiving packet size %d status %4.4x.\n",
  9. pkt_len, rx_status);
  10. if (skb != NULL) {
  11. skb_reserve(skb, ); /* Align IP on 16 byte boundaries */
  12. /* 'skb_put()' points to the start of sk_buff data area. */
  13. if (vp->bus_master &&
  14. ! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) {
  15. dma_addr_t dma = pci_map_single(VORTEX_PCI(vp), skb_put(skb, pkt_len),
  16. pkt_len, PCI_DMA_FROMDEVICE);
  17. iowrite32(dma, ioaddr + Wn7_MasterAddr);
  18. iowrite16((skb->len + ) & ~, ioaddr + Wn7_MasterLen);
  19. iowrite16(StartDMAUp, ioaddr + EL3_CMD);
  20. while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)
  21. ;
  22. pci_unmap_single(VORTEX_PCI(vp), dma, pkt_len, PCI_DMA_FROMDEVICE);
  23. } else {
  24. ioread32_rep(ioaddr + RX_FIFO,
  25. skb_put(skb, pkt_len),
  26. (pkt_len + ) >> );
  27. }
  28. iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
  29. skb->protocol = eth_type_trans(skb, dev);
  30. netif_rx(skb);
  31. dev->stats.rx_packets++;
  32. /* Wait a limited time to go to next packet. */
  33. for (i = ; i >= ; i--)
  34. if ( ! (ioread16(ioaddr + EL3_STATUS) & CmdInProgress))
  35. break;
  36. continue;

它首先为新到来的数据包分配一个skb结构及pkt_len+5大小的数据长度,然后便将接收到的数据从网卡复制到(DMA)这个SKB的数据部分中。最后,调用netif_rx(skb)进一步处理数据:

  1. 2016 int netif_rx(struct sk_buff *skb)
  2. 2017 {
  3. 2018 struct softnet_data *queue;
  4. 2019 unsigned long flags;
  5. 2031 */
  6. 2032 local_irq_save(flags);
  7. 2033 queue = &__get_cpu_var(softnet_data);
  8. 2034
  9. 2035 __get_cpu_var(netdev_rx_stat).total++;
  10. 2036 if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
  11. 2037 if (queue->input_pkt_queue.qlen) {
  12. 2038 enqueue:
  13. 2039 __skb_queue_tail(&queue->input_pkt_queue, skb);
  14. 2040 local_irq_restore(flags);
  15. 2041 return NET_RX_SUCCESS;
  16. 2042 }
  17. 2043
  18. 2044 napi_schedule(&queue->backlog);
  19. 2045 goto enqueue;
  20. 2046 }
  21. 2047
  22. 2048 __get_cpu_var(netdev_rx_stat).dropped++;
  23. 2049 local_irq_restore(flags);
  24. 2050
  25. 2051 kfree_skb(skb);
  26. 2052 return NET_RX_DROP;
  27. 2053 }

  

这段代码关键是,将这个SKB加入到相应的input_pkt_queue队列中,并调用napi_schedule(),

  1. static inline void napi_schedule(struct napi_struct *n)
  2. {
  3. if (napi_schedule_prep(n))
  4. __napi_schedule(n);
  5. }

napi_schedule()调用__napi_schedule(),__napi_schedule()作用在前面已经见过。到这里,napi和 none napi 方式函数调用路径得到统一.

总之,NONE-NAPI的中断上半部接收过程可以简单的描述为,它首先为新到来的数据帧分配合适长度的SKB,再将接收到的数据从NIC中拷贝过来,然后将这个SKB链入当前CPU的softnet_data中的链表中,最后进一步触发中断下半部继续处理。

4.2 process_backlog:

process_backlog 为none-napi 对应的poll 函数。

  1. static int process_backlog(struct napi_struct *napi, int quota)
  2. {
  3. int work = ;
  4. struct softnet_data *queue = &__get_cpu_var(softnet_data);
  5. unsigned long start_time = jiffies;
  6.  
  7. napi->weight = weight_p;
  8. do {
  9. struct sk_buff *skb;
  10.  
  11. local_irq_disable();
  12. skb = __skb_dequeue(&queue->input_pkt_queue);
  13. if (!skb) {
  14. __napi_complete(napi);
  15. local_irq_enable();
  16. break;
  17. }
  18. local_irq_enable();
  19.  
  20. netif_receive_skb(skb);
  21. } while (++work < quota && jiffies == start_time);
  22.  
  23. return work;
  24. }

它首先找到当前CPU的softnet_data结构,然后遍历其数据队SKB,并将数据上交netif_receive_skb(skb)处理。

Transmit

报文的发送是由网络协议栈的上层发起的。网络协议栈上层构造一个需要发送的skb结构后(该skb已经包含了数据链路层的报头),调用dev_queue_xmit函数进行发送;

dev_queue_xmit(skb);

该函数先会处理一些缓冲区重组、计算校验和之类的杂事,然后开始处理报文的发送。
发送报文有两种策略,有队列或无队列。这是由网络设备驱动程序在定义其对应的dev结构时指定的,一般的设备都会使用队列。
dev->qdisc指向一个队列的实例,里面包含了队列本身以及操作队列的方法(enqueue、dequeue、requeue)。这些方法的集合组成了一种队列规则(skb将以某种规则入队、以某种规则出队,并不一定是简单的先进先出),这样的规则可用于流量控制。
网络设备驱动程序可以选择自己的设备使用什么样的队列,或是不使用队列。

  1. int dev_queue_xmit(struct sk_buff *skb)
  2. {
  3. struct net_device *dev = skb->dev;
  4. struct netdev_queue *txq;
  5. struct Qdisc *q;
  6. int rc = -ENOMEM;
  7.  
  8. /* GSO will handle the following emulations directly. */
  9. if (netif_needs_gso(dev, skb))
  10. goto gso;
  11.  
  12. if (skb_has_frags(skb) &&
  13. !(dev->features & NETIF_F_FRAGLIST) &&
  14. __skb_linearize(skb))
  15. goto out_kfree_skb;
  16.  
  17. /* Fragmented skb is linearized if device does not support SG,
  18. 1903 * or if at least one of fragments is in highmem and device
  19. 1904 * does not support DMA from it.
  20. 1905 */
  21. if (skb_shinfo(skb)->nr_frags &&
  22. (!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&
  23. __skb_linearize(skb))
  24. goto out_kfree_skb;
  25. skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
  26. #endif
  27. //对于有队列设备处理
  28. if (q->enqueue) {
  29. rc = __dev_xmit_skb(skb, q, dev, txq);
  30. goto out;
  31. }
  32. if (dev->flags & IFF_UP) {
  33. int cpu = smp_processor_id(); /* ok because BHs are off */
  34.  
  35. if (txq->xmit_lock_owner != cpu) {
  36.  
  37. HARD_TX_LOCK(dev, txq, cpu);
  38. if (!netif_tx_queue_stopped(txq)) {
  39. rc = NET_XMIT_SUCCESS;
  40. //对于无队列设备直接调用dev_hard_start_xmit发送
  41. if (!dev_hard_start_xmit(skb, dev, txq)) {
  42. HARD_TX_UNLOCK(dev, txq);
  43. goto out;
  44. }
  45. }
  46.  
  47. 对于__dev_xmit_skb包含了enqueueqdis_run函数的调用.
  48.  
  49. static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
  50. struct net_device *dev,
  51. struct netdev_queue *txq)
  52. {
  53. spinlock_t *root_lock = qdisc_lock(q);
  54. int rc;
  55.  
  56. } else {
  57. rc = qdisc_enqueue_root(skb, q);
  58. qdisc_run(q);
  59. }
  60. qdis_run__qdis_runqdis_restart
  61.  
  62. static inline int qdisc_restart(struct Qdisc *q)
  63. {
  64. struct netdev_queue *txq;
  65. struct net_device *dev;
  66. spinlock_t *root_lock;
  67. struct sk_buff *skb;
  68.  
  69. /* Dequeue packet */
  70. skb = dequeue_skb(q);
  71. if (unlikely(!skb))
  72. return ;
  73.  
  74. root_lock = qdisc_lock(q);
  75. dev = qdisc_dev(q);
  76. txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
  77.  
  78. return sch_direct_xmit(skb, q, dev, txq, root_lock);
  79. }

qdisc_restart的主要工作就是不断调用dev->qdisc->dequeue方法从队列中取出待发送的报文,然后调用sch_direct_xmit方法进行发送。sch_direct_xmit间接调用设备驱动程序实现的方法,会直接和网络设备去打交道,将报文发送出去.

如果报文发送失败,sch_direct_xmit会调用dev->qdisc->requeue方法将skb重新放回队列

  1. default:
  2. /* Driver returned NETDEV_TX_BUSY - requeue skb */
  3. if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
  4. printk(KERN_WARNING "BUG %s code %d qlen %d\n",
  5. dev->name, ret, q->q.qlen);
  6.  
  7. ret = dev_requeue_skb(skb, q);
  8. break;
  9. }

__qdisc_run会循环调用qdisc_restart,当此函数调用时间过长或者有其它进程需要调度的时候,调用__netif_schedule.

  1. void __qdisc_run(struct Qdisc *q)
  2. {
  3. unsigned long start_time = jiffies;
  4.  
  5. while (qdisc_restart(q)) {
  6. /*
  7. 201 * Postpone processing if
  8. 202 * 1. another process needs the CPU;
  9. 203 * 2. we've been doing it for too long.
  10. 204 */
  11. if (need_resched() || jiffies != start_time) {
  12. __netif_schedule(q);
  13. break;
  14. }
  15. }
  16.  
  17. clear_bit(__QDISC_STATE_RUNNING, &q->state);
  18. }
  19.  
  20. __netif_schedule __netif_reschedule
  21.  
  22. static inline void __netif_reschedule(struct Qdisc *q)
  23. {
  24. struct softnet_data *sd;
  25. unsigned long flags;
  26.  
  27. local_irq_save(flags);
  28. sd = &__get_cpu_var(softnet_data);
  29. q->next_sched = sd->output_queue;
  30. sd->output_queue = q;
  31. raise_softirq_irqoff(NET_TX_SOFTIRQ);
  32. local_irq_restore(flags);
  33. }

__netif_reschedule函数将dev加入softdate_net的output_queue队列中(其中的设备都是有报文等待发送的,将在稍后被处理)。然后触发一次NET_TX_SOFTIRQ软中断。于是在下一个中断到来时,对应的软中断处理函数net_tx_action将被调用

软中断NET_TX_SOFTIRQ被触发,将使得net_tx_action函数被调用。该函数主要做了两件事:
1、从softdate_net的completion_queue队列中取出每一个skb,将其释放;
2、对于softdate_net的output_queue队列中的dev,调用qdisc_run继续尝试发送其qdisc队列中的报文;

参考资料

http://blog.chinaunix.net/uid-24148050-id-473352.html

https://yq.aliyun.com/articles/8898

http://bbs.chinaunix.net/thread-2141004-1-1.html

source code : linux-2.6.32.67

Packet flow in l2(receive and transmit)的更多相关文章

  1. RFID Reader 线路图收集

    This 125 kHz RFID reader http://www.serasidis.gr/circuits/RFID_reader/125kHz_RFID_reader.htm http:// ...

  2. VNF网络性能提升解决方案及实践

    VNF网络性能提升解决方案及实践 2016年7月 作者:    王智民 贡献者:     创建时间:    2016-7-20 稳定程度:    初稿 修改历史 版本 日期 修订人 说明 1.0 20 ...

  3. linux kernel 关于RSS/RPS/RFS/XPS的介绍

    Introduction============ This document describes a set of complementary techniques in the Linuxnetwo ...

  4. Lock-less and zero copy messaging scheme for telecommunication network applications

    A computer-implemented system and method for a lock-less, zero data copy messaging mechanism in a mu ...

  5. sock skbuf 结构:

    /** * struct sock - network layer representation of sockets * @__sk_common: shared layout with inet_ ...

  6. 扩展Linux网络栈

    扩展Linux网络栈 来自Linux内核文档.之前看过这篇文章,一直好奇,问什么一条网络流会固定在一个CPU上进行处理,本文档可以解决这个疑问.为了更好地理解本文章中的功能,将这篇文章穿插入内. 简介 ...

  7. 蓝牙BLE实用教程

    蓝牙BLE实用教程 Bluetooth BLE 欢迎使用 小书匠(xiaoshujiang)编辑器,您可以通过 设置 里的修改模板来改变新建文章的内容. 1.蓝牙BLE常见问答 Q: Smart Re ...

  8. Enhancing the Scalability of Memcached

    原文地址: https://software.intel.com/en-us/articles/enhancing-the-scalability-of-memcached-0 1 Introduct ...

  9. Monitoring and Tuning the Linux Networking Stack: Receiving Data

    http://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/ ...

随机推荐

  1. 计算机网络(九),HTTP简介

    目录 1.超文本传输协议HTTP的主要特点 2.HTTP请求结构 3.HTTP响应结构 4.http请求/响应的步骤 九.HTTP简介 1.超文本传输协议HTTP的主要特点 (1)支持客户/服务器模式 ...

  2. CSP2019-S2参赛总结 暨 近期学习反思

    前言 岁月不居,时节如流.眨眼间,2019的联赛就已经落下帷幕了,回忆这一年的学习,有许许多多的事情想写下来.趁联赛结果还未出来,赶紧写下这篇文章,以记录我这段时间的学习和生活. "你怎么又 ...

  3. Acwing:137. 雪花雪花雪花(Hash表)

    有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,…,ai,6ai,1,ai,2,…,ai,6. 因为雪花的形状是封闭的环形,所以 ...

  4. H5 video全屏与取消全屏兼容

    H5 video全屏与取消全屏各浏览器兼容,  requestFullscreen()全屏方法,exitFullscreen()退出全屏方法.兼容各个浏览器与css3兼容一样加个前缀即可. // 全屏 ...

  5. php 将几个变量合为数组,变量名和值对应

    <?php $firstname = "Bill"; $lastname = "Gates"; $age = "60"; $resul ...

  6. Java内存模型之可见性问题

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 前言 之前的文章中讲到,JMM是内存模型规范在Java语 ...

  7. 深入理解java集合

    集合 Java集合分为三大接口:①Collection ②Map ③Iterator

  8. 一、基础篇--1.2Java集合-ArrayList和Vector的区别

     ArrayList和Vector的区别 ArrayList和Vector都是基于动态数组实现的.  区别 ArrayList是非线程安全的,Vector是线程安全的. Vector的方法都加了同步锁 ...

  9. 【flask】flask项目配置 app.config

    [理论] 在很多情况下,你需要设置程序的某些行为,这时你就需要使用配置变量.在Flask中,配置变量就是一些大写形式的Python变量, 你也可以称之为配置参数或配置键.使用统一的配置变量可以避免在程 ...

  10. Linux_系统破坏性修复实验

    目录 目录 修改系统用户密码 grub修复 系统修复 最后 修改系统用户密码 随便介绍一个修改Linux系统用户密码的方法. 步骤: 开机读秒时按任意键 进入grub列表项配置按e 选择系统kerne ...