一、 网络设备的初始化

网络设备初始化就是调用具有__init 前缀的net_dev_init函数完成的,网络设备初始化包含两个部分(在linux内核2.4办源代码分析大全一书的第550页有详细说明),就是:

在系统初始化期间对系统已知的网络设备进行初始化过程,也就是,我们在编译内核时选择编入内核的那部分网卡设备就会在这个时候逐个进行初始化工作。系统中已知的网络设备都存储在一个全局表中,dev_base[],它将所有网络设备的net_devive结构连接在一起。

int __init net_dev_init(void)

{

       struct net_device *dev, **dp;

       int i;

      

       /*

       这里如果是从系统初始化调用的则全局变量dev_boot_phase1,如果是模块插入则dev_boot_phase0,到达这个地方以后就直接返回了。这就保证了系统调用设备初始化函数一次。

       */

       if (!dev_boot_phase)

              return 0;

       /*

        在这里初始化数据包接收队列。

       */

 

       for (i = 0; i < NR_CPUS; i++) {

              struct softnet_data *queue;

              /*

              softnet_data数组,内核为每一个CPU都维护一个接收数据包的队列,这样不同的CPU之间就不需要进行互斥访问操作了。当然,如果只有一个CPU的话,这个队列的维数就是1

struct softnet_data{

int  throttle;                  /* 1 表示当前队列的数据包被禁止*/

int  cng_level;                /*表示当前处理器的数据包处理拥塞程度*/

int  avg_blog;                 /*某个处理器的平均拥塞度*/

struct sk_buff_head input_pkt_queue;/*接收缓冲区的sk_buff队列*/

struct list_head   poll_list;       /*POLL设备队列头*/

struct net_device   output_queue;   /*网络设备发送队列的队列头*/

struct sk_buff    completion_queue; /*完成发送的数据包等待释放的队列*/

struct net_device backlog_dev;     /*表示当前参与POLL处理的网络设备*/

};

              在这里我们初始化网络接收数据报队列的相关参数,初始化输入包队列、完成队列等,其中应当注意的是咱这里注册了网卡的通用poll方法,该方法在网络软中断处理当中负责从数据包队列当中取出待处理数据,并递交给上层协议进行处理。每个网卡驱动程序可以自己实现poll函数,例如e1000就实现了poll方法,而一般的讲,是不需要自己实现该函数的,因为系统已经提供了一个默认函数process_backlog

 

              */

              queue = &softnet_data[i];

              skb_queue_head_init(&queue->input_pkt_queue);

              queue->throttle = 0;

              queue->cng_level = 0;

              queue->avg_blog = 10; /* arbitrary non-zero */

              queue->completion_queue = NULL;

              INIT_LIST_HEAD(&queue->poll_list);

              set_bit(__LINK_STATE_START, &queue->blog_dev.state);

              queue->blog_dev.weight = weight_p;

              queue->blog_dev.poll = process_backlog;

              atomic_set(&queue->blog_dev.refcnt, 1);

       }

       /*

       开始添加设备,这里从dev_base列表当中逐个取出net_device结构,如果该结构存在,则对结构的一些参数进行初始化。最为关键的就是在这里调用每个设备的init函数,如果该设备已经存在则init函数应该调用成功,如果调用失败,则表明可能该设备并不存在,应当从dev_base列表中清除。

       对于每个设备的dev->init方法对于不同类型的网络设备各不相同,对于以太网卡来说,在driver/net/Space.c当中将其初始化为ethif_probe

        */

       dp = &dev_base;

       while ((dev = *dp) != NULL) {

              spin_lock_init(&dev->queue_lock);

              spin_lock_init(&dev->xmit_lock);

 

              dev->xmit_lock_owner = -1;

              dev->iflink = -1;

              dev_hold(dev);

 

              /*

               * Allocate name. If the init() fails

               * the name will be reissued correctly.

               */

              if (strchr(dev->name, '%'))

                     dev_alloc_name(dev, dev->name);

 

              /*

               * Check boot time settings for the device.

               */

              netdev_boot_setup_check(dev);

 

              if (dev->init && dev->init(dev)) {

                     /*

                     Init函数调用成功返回0,否则返回非0,这里返回非0,表明初始化过程出现了问题,我们将它的deadbeaf标志置为,并在下面把这个设备从列表中删除。如果init成功,则进一步进行初始化。

                      */

                     dev->deadbeaf = 1;

                     dp = &dev->next;

              } else {

                     dp = &dev->next;

                     dev->ifindex = dev_new_index();

                     if (dev->iflink == -1)

                            dev->iflink = dev->ifindex;

                     if (dev->rebuild_header == NULL)

                            dev->rebuild_header = default_rebuild_header;

                     dev_init_scheduler(dev);

                     set_bit(__LINK_STATE_PRESENT, &dev->state);

              }

       }

 

       /*

       在这里根据deadbeaf标志讲所有init调用失败的设备删除。

        */

       dp = &dev_base;

       while ((dev = *dp) != NULL) {

              if (dev->deadbeaf) {

                     write_lock_bh(&dev_base_lock);

                     *dp = dev->next;

                     write_unlock_bh(&dev_base_lock);

                     dev_put(dev);

              } else {

                     dp = &dev->next;

              }

       }

       /*

       到这里内核初始化过程已经完成。

       */

       dev_boot_phase = 0;

       /*

       注册网络接收和发送软中断处理函数,发送处理函数是net_tx_action,接收处理函数是net_rx_action。当系统调用软中断入口函数do_softirq时,就是调用net_rx_action函数对接收数据包队列进行处理的,在这个函数当中又调用了相应网络设备的poll方法,以轮询的方式遍历数据包队列,将数据报向上层协议转发。

       */

       open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);

       open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

 

       dst_init();

       dev_mcast_init();

       /*

        *    Initialise network devices

        */  

       net_device_init();

 

       return 0;

 
 

}

在这里我们列出了网络设备在内核初始化过程中的初始化函数调用关系。

 

上面对net_dev_init函数的分析已经知道,为每一个dev调用init函数,对于不同的设备系统在drivers/net/Space.c当中已经分别进行了注册,对于以太网卡则将其init方法注册为ethif_probe,它调用probe_list函数为每一个以太网卡调用已经注册好的probe方法。对于基于pci设备的网卡驱动程序来说,应该为每一个网卡创建一个pci_driver结构,该结构定义了该设备如何探测,如何从系统中删除等等基本的操作函数。每当实现一个新的网卡驱动程序时,就可以将相应的处理函数注册到该结构当中,调用点就在probe_list函数中。

对于e1000网卡驱动来说,它的probe函数注册为e1000_probe,因此这也就是这个网卡最早被调用的函数:

static int __devinit e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)

{

       struct net_device *netdev;

       struct e1000_adapter *adapter;

       static int cards_found = 0;

       unsigned long mmio_start;

       int mmio_len;

       int pci_using_dac;

       int i;

       int err;

       uint16_t eeprom_data;

 

       if((err = pci_enable_device(pdev)))

              return err;

       /*

       初始化网卡的DMA传输机制。

       */

       if(!(err = pci_set_dma_mask(pdev, PCI_DMA_64BIT))) {

              pci_using_dac = 1;

       } else {

              if((err = pci_set_dma_mask(pdev, PCI_DMA_32BIT))) {

                     E1000_ERR("No usable DMA configuration, aborting/n");

                     return err;

              }

              pci_using_dac = 0;

       }

      

       if((err = pci_request_regions(pdev, e1000_driver_name)))

              return err;

 

       pci_set_master(pdev);

 

       /*

       为这个设备分配一个net_device结构,并且调用ether_setup初始化该结构的部分成员,特别是所有以太网设备的共同参数。

       */

       netdev = alloc_etherdev(sizeof(struct e1000_adapter));

       if(!netdev) {

              err = -ENOMEM;

              goto err_alloc_etherdev;

       }

 

       SET_MODULE_OWNER(netdev);

 

       pci_set_drvdata(pdev, netdev);

       adapter = netdev->priv;

       adapter->netdev = netdev;

       adapter->pdev = pdev;

       adapter->hw.back = adapter;

 

       mmio_start = pci_resource_start(pdev, BAR_0);

       mmio_len = pci_resource_len(pdev, BAR_0);

 

       adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);

       if(!adapter->hw.hw_addr) {

              err = -EIO;

              goto err_ioremap;

       }

 

       for(i = BAR_1; i <= BAR_5; i++) {

              if(pci_resource_len(pdev, i) == 0)

                     continue;

              if(pci_resource_flags(pdev, i) & IORESOURCE_IO) {

                     adapter->hw.io_base = pci_resource_start(pdev, i);

                     break;

              }

       }

/*

       e1000网卡驱动程序的各种处理函数注册到net_device结构的各个成员上,open方法用来打开一个网卡设备,hard_start_xmit方法用来进行网络数据传输,而poll方法就是在软中断处理程序net_rx_action中被调用的poll方法。并且调用ether_setup初始化该结构的部分成员,特别是所有以太网设备的共同参数。

       */

       netdev->open = &e1000_open;

       netdev->stop = &e1000_close;

       netdev->hard_start_xmit = &e1000_xmit_frame;

       netdev->get_stats = &e1000_get_stats;

       netdev->set_multicast_list = &e1000_set_multi;

       netdev->set_mac_address = &e1000_set_mac;

       netdev->change_mtu = &e1000_change_mtu;

       netdev->do_ioctl = &e1000_ioctl;

       netdev->tx_timeout = &e1000_tx_timeout;

       netdev->watchdog_timeo = 5 * HZ;

#ifdef CONFIG_E1000_NAPI

       /*

       如果在配置内核参数时选择了采用NAPI处理网络数据,则定义e1000自己的poll处理函数(e1000_clean),否则就采用系统默认的处理函数process_backlog。以后会对这两个函数的作用及不同点进行分析和比较。

       */

       netdev->poll = &e1000_clean;

       netdev->weight = 64;

#endif

       netdev->vlan_rx_register = e1000_vlan_rx_register;

       netdev->vlan_rx_add_vid = e1000_vlan_rx_add_vid;

       netdev->vlan_rx_kill_vid = e1000_vlan_rx_kill_vid;

 

       netdev->irq = pdev->irq;

       netdev->mem_start = mmio_start;

       netdev->mem_end = mmio_start + mmio_len;

       netdev->base_addr = adapter->hw.io_base;

 

       adapter->bd_number = cards_found;

 

       /* setup the private structure */

 

       if((err = e1000_sw_init(adapter)))

              goto err_sw_init;

 

       if(adapter->hw.mac_type >= e1000_82543) {

              netdev->features = NETIF_F_SG |

                               NETIF_F_HW_CSUM |

                               NETIF_F_HW_VLAN_TX |

                               NETIF_F_HW_VLAN_RX |

                               NETIF_F_HW_VLAN_FILTER;

       } else {

              netdev->features = NETIF_F_SG;

       }

 

#ifdef NETIF_F_TSO

       if((adapter->hw.mac_type >= e1000_82544) &&

          (adapter->hw.mac_type != e1000_82547))

              netdev->features |= NETIF_F_TSO;

#endif

 

       if(pci_using_dac)

              netdev->features |= NETIF_F_HIGHDMA;

 

       /* before reading the EEPROM, reset the controller to

        * put the device in a known good starting state */

      

       e1000_reset_hw(&adapter->hw);

 

       /* make sure the EEPROM is good */

 

       if(e1000_validate_eeprom_checksum(&adapter->hw) < 0) {

              printk(KERN_ERR "The EEPROM Checksum Is Not Valid/n");

              err = -EIO;

              goto err_eeprom;

       }

 

       /* copy the MAC address out of the EEPROM */

 

       e1000_read_mac_addr(&adapter->hw);

       memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len);

 

       if(!is_valid_ether_addr(netdev->dev_addr)) {

              err = -EIO;

              goto err_eeprom;

       }

 

       e1000_read_part_num(&adapter->hw, &(adapter->part_num));

 

       e1000_get_bus_info(&adapter->hw);

 

       init_timer(&adapter->tx_fifo_stall_timer);

       adapter->tx_fifo_stall_timer.function = &e1000_82547_tx_fifo_stall;

       adapter->tx_fifo_stall_timer.data = (unsigned long) adapter;

 

       init_timer(&adapter->watchdog_timer);

       adapter->watchdog_timer.function = &e1000_watchdog;

       adapter->watchdog_timer.data = (unsigned long) adapter;

 

       init_timer(&adapter->phy_info_timer);

       adapter->phy_info_timer.function = &e1000_update_phy_info;

       adapter->phy_info_timer.data = (unsigned long) adapter;

 

       INIT_TQUEUE(&adapter->tx_timeout_task,

              (void (*)(void *))e1000_tx_timeout_task, netdev);

      

       /*

       将自己注册到dev_base数组当中,这个调用主要是为了模块启动准备的,因为如果该网卡在net_dev_init已经初始化,则dev_base数组当中已经存在,在register_netdevice函数中就会判断当前的net_device是否与dev_base中的某个相同,如果发现相同的就立刻返回。对于模块方式来说,此时正是该设备初始化的好时机,首先调用dev->init方法尝试对设备进行初始化工作,然后将自己添加到dev_base数组的最后,这样系统就可以利用这个设备收发数据了。

       register_netdev事实上是重复了net_dev_init的很多工作,设备驱动程序的模块化启动顺序图示如下:

 

 

 

 
 

       */

       register_netdev(netdev);

 

       /* we're going to reset, so assume we have no link for now */

 

       netif_carrier_off(netdev);

       netif_stop_queue(netdev);

 

       printk(KERN_INFO "%s: Intel(R) PRO/1000 Network Connection/n",

              netdev->name);

       e1000_check_options(adapter);

}

 

二、 网络设备的打开和关闭操作

每一个pci设备的驱动程序在注册自己的时候都提供了open方法和close方法用来对设备进行打开和关闭操作。

设备的open操作在net/core/dev.c中的dev_open函数中调用。究竟dev_open是由谁调用的我们并不关心。

网卡设备是否处于打开状态由dev->flags标志控制,如果该标志置为IFF_UP,则表明设备已经打开。

 

三、 网络数据的接收过程

网络数据的接收过程是根据硬件的中断来实现的,每个网卡驱动程序提供硬件中断处理函数,系统收到某个硬件发来的中断信号以后,调用do_IRQ函数。该函数根据中断信号中带的中断号判断是那一个硬件产生的中断,然后查找相应的设备的中断处理入口函数,调用handle_IRQ_event,在这个函数中调用了网卡的中断处理函数。以e1000为例,则是e1000_intr

static irqreturn_t e1000_intr(int irq, void *data, struct pt_regs *regs)

{

       struct net_device *netdev = data;

       struct e1000_adapter *adapter = netdev->priv;

       uint32_t icr = E1000_READ_REG(&adapter->hw, ICR);

#ifndef CONFIG_E1000_NAPI

       unsigned int i;

#endif

       /*

       首先判断是否是该网卡的中断号。

       */

       if(!icr)

              return IRQ_NONE;  /* Not our interrupt */

 

       if(icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) {

              adapter->hw.get_link_status = 1;

              mod_timer(&adapter->watchdog_timer, jiffies);

       }

/*

如果采用网卡的NAPI方法,则在这里转入轮询的处理过程。

*/

#ifdef CONFIG_E1000_NAPI

       if(netif_rx_schedule_prep(netdev)) {

 

              /* Disable interrupts and register for poll. The flush

                of the posted write is intentionally left out.

              */

 

              atomic_inc(&adapter->irq_sem);

              E1000_WRITE_REG(&adapter->hw, IMC, ~0);

              __netif_rx_schedule(netdev);

       }

#else

/*

否则,采用正常的中断处理机制,这里设置一个变量E1000_MAX_INTR可能表示一次中断处理,网卡能够处理的网络数据包的个数。因为从效率的角度上讲,中断一次能够处理的数据包越多越好,但是中断处理函数需要屏蔽所有的中断,因此,过长的处理时间会导致系统不能同时相应其他的中断请求。

*/

       for(i = 0; i < E1000_MAX_INTR; i++)

              if(!e1000_clean_rx_irq(adapter) &

                 !e1000_clean_tx_irq(adapter))

                     break;

#endif

 

       return IRQ_HANDLED;

}

 

事实上,在网卡的中断模式下,e1000_clean_rx_irq是从硬件接收数据的关键函数,同时,该函数还可以处理轮询情况。

e1000_clean_rx_irq(struct e1000_adapter *adapter)

{

       i = rx_ring->next_to_clean;

       rx_desc = E1000_RX_DESC(*rx_ring, i);

 

       while(rx_desc->status & E1000_RXD_STAT_DD) {

              buffer_info = &rx_ring->buffer_info[i];

 

#ifdef CONFIG_E1000_NAPI

              if(*work_done >= work_to_do)

                     break;

 

              (*work_done)++;

#endif

 

              cleaned = TRUE;

              /*

              当前的PCI网卡驱动程序基本上都是采用DMA传输的方式接收数据包,一次硬件中断的产生表明一批数据已经通过设备的DMA通道从硬件层传递到了DMA缓冲区当中,这个DMA缓冲区是在设备创建的时候申请的。数据已经拷贝到DMA区以后,会产生一个中断,通知中断处理函数将这些数据取走。这里调用pci_unmap_single就是将缓冲区映射解除,以便开始处理数据。

              */

              pci_unmap_single(pdev,

                               buffer_info->dma,

                               buffer_info->length,

                               PCI_DMA_FROMDEVICE);

 

              skb = buffer_info->skb;

              length = le16_to_cpu(rx_desc->length);

              /*

              这里忽略了错误处理。

              */

              /* 接收到一个正确的数据包。*/

              skb_put(skb, length - ETHERNET_FCS_SIZE);

 

              /* 校验和 */

              e1000_rx_checksum(adapter, rx_desc, skb);

 

              skb->protocol = eth_type_trans(skb, netdev);

/*

采用NAPI方法接收数据包。

*/

#ifdef CONFIG_E1000_NAPI

              if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

                     vlan_hwaccel_receive_skb(skb, adapter->vlgrp,

                            le16_to_cpu(rx_desc->special &

                                   E1000_RXD_SPC_VLAN_MASK));

              } else {

                     netif_receive_skb(skb);

              }

#else /* CONFIG_E1000_NAPI */

/*

采用正常的中断方式处理数据包。实际上就是调用netif_rx将接收到的skb放到接收缓冲区列表当中,然后就返回了。接收缓冲区队列中的数据由随后执行的软中断处理函数进一步处理。

*/

              if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

                     vlan_hwaccel_rx(skb, adapter->vlgrp,

                            le16_to_cpu(rx_desc->special &

                                   E1000_RXD_SPC_VLAN_MASK));

              } else {

                     netif_rx(skb);

              }

#endif /* CONFIG_E1000_NAPI */

              netdev->last_rx = jiffies;

 

              rx_desc->status = 0;

              buffer_info->skb = NULL;

 

              if(++i == rx_ring->count) i = 0;

 

              rx_desc = E1000_RX_DESC(*rx_ring, i);

       }

 

       rx_ring->next_to_clean = i;

 

       e1000_alloc_rx_buffers(adapter);

 

       return cleaned;

}

网卡的硬件中断处理函数主要的工作就是将DMA传输过来的数据包(skbuff)插入相应的接收缓冲区队列中,然后调用netif_rx函数将sk_buff插入接收缓冲区队列中就返回了。而在e1000的网卡中断处理程序当中用到了比较新的技术NAPI,这种技术是采用轮询的机制替换中断模式,加快网络数据的处理速度,详细的内容见《NAPI技术在Linux网络驱动上的应用》。这里我暂时只分析传统的中断机制。

 

四、 网络处理的软中断机制分析

详见《网络处理的软中断机制分析》。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐