網絡設備的初始化
網絡設備初始化就是調用具有__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_phase置1,如果是模塊插入則dev_boot_phase置0,到達這個地方以後就直接返回了。這就保證了系統調用設備初始化函數一次。
*/
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;
partition_setup()
device_init()
net_dev_init()
dev->init()
ethif_probe()
p->probe()
probe_list()
}
在這裏我們列出了網絡設備在內核初始化過程中的初始化函數調用關係。
上面對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的很多工作,設備驅動程序的模塊化啓動順序圖示如下:
init_module()
模塊初始化函數 ()
pci_register_driver ()
pci_announce_device ()
pci_driver->probe ()
register_netdev()
register_netdevice ()
dev->init ()
insert into dev_base (netdevice)
*/
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網絡驅動上的應用》。這裏我暫時只分析傳統的中斷機制。
四、網絡處理的軟中斷機制分析