日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Linux TCP/IP协议栈笔记

發布時間:2023/11/27 生活经验 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux TCP/IP协议栈笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
數據包的接收

作者:kendo

Kernel:2.6.12

一、從網卡說起

這并非是一個網卡驅動分析的專門文檔,只是對網卡處理數據包的流程進行一個重點的分析。這里以Intel的e100驅動為例進行分析。
大多數網卡都是一個PCI設備,PCI設備都包含了一個標準的配置寄存器,寄存器中,包含了PCI設備的廠商ID、設備ID等等信息,驅動
程序使用來描述這些寄存器的標識符。如下:

[Copy to clipboard] ??????????? CODE: struct pci_device_id {
? ? ? ? __u32 vendor, device;? ? ? ? ? ? ? ? /* Vendor and device ID or PCI_ANY_ID*/
? ? ? ? __u32 subvendor, subdevice;? ? ? ? /* Subsystem ID's or PCI_ANY_ID */
? ? ? ? __u32 class, class_mask;? ? ? ? /* (class,subclass,prog-if) triplet */
? ? ? ? kernel_ulong_t driver_data;? ? ? ? /* Data private to the driver */
};
這樣,在驅動程序中,常常就可以看到定義一個struct pci_device_id 類型的數組,告訴內核支持不同類型的
PCI設備的列表,以e100驅動為例:

#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/
? ? ? ? PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, /
? ? ? ? PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich }
? ? ? ?
static struct pci_device_id e100_id_table[] = {
? ? ? ? INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),
? ? ? ? INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),
? ? ? ? INTEL_8255X_ETHERNET_DEVICE(0x1031, 3),
……/*略過一大堆支持的設備*/
? ? ? ? { 0, }
};

在內核中,一個PCI設備,使用struct pci_driver結構來描述,
struct pci_driver {
? ? ? ? struct list_head node;
? ? ? ? char *name;
? ? ? ? struct module *owner;
? ? ? ? const struct pci_device_id *id_table;? ? ? ? /* must be non-NULL for probe to be called */
? ? ? ? int??(*probe)??(struct pci_dev *dev, const struct pci_device_id *id);? ? ? ? /* New device inserted */
? ? ? ? void (*remove) (struct pci_dev *dev);? ? ? ? /* Device removed (NULL if not a hot-plug capable driver) */
? ? ? ? int??(*suspend) (struct pci_dev *dev, pm_message_t state);? ? ? ? /* Device suspended */
? ? ? ? int??(*resume) (struct pci_dev *dev);? ? ? ?? ?? ?? ?? ?? ???/* Device woken up */
? ? ? ? int??(*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);? ?/* Enable wake event */
? ? ? ? void (*shutdown) (struct pci_dev *dev);

? ? ? ? struct device_driver? ? ? ? driver;
? ? ? ? struct pci_dynids dynids;
};

因為在系統引導的時候,PCI設備已經被識別,當內核發現一個已經檢測到的設備同驅動注冊的id_table中的信息相匹配時,
它就會觸發驅動的probe函數,以e100為例:
/*
? * 定義一個名為e100_driver的PCI設備
* 1、設備的探測函數為e100_probe;
? * 2、設備的id_table表為e100_id_table
? */
static struct pci_driver e100_driver = {
? ? ? ? .name =? ?? ?? ?DRV_NAME,
? ? ? ? .id_table =? ???e100_id_table,
? ? ? ? .probe =? ?? ???e100_probe,
? ? ? ? .remove =? ?? ? __devexit_p(e100_remove),
#ifdef CONFIG_PM
? ? ? ? .suspend =? ?? ?e100_suspend,
? ? ? ? .resume =? ?? ? e100_resume,
#endif

? ? ? ? .driver = {
? ? ? ? ? ? ? ? .shutdown = e100_shutdown,
? ? ? ? }

};

這樣,如果系統檢測到有與id_table中對應的設備時,就調用驅動的probe函數。

驅動設備在init函數中,調用pci_module_init函數初始化PCI設備e100_driver:

static int __init e100_init_module(void)
{
? ? ? ? if(((1 << debug) - 1) & NETIF_MSG_DRV) {
? ? ? ? ? ? ? ? printk(KERN_INFO PFX "%s, %s/n", DRV_DESCRIPTION, DRV_VERSION);
? ? ? ? ? ? ? ? printk(KERN_INFO PFX "%s/n", DRV_COPYRIGHT);
? ? ? ? }
? ? ? ? return pci_module_init(&e100_driver);
}

一切順利的話,注冊的e100_probe函數將被內核調用,這個函數完成兩個重要的工作:
1、分配/初始化/注冊網絡設備;
2、完成PCI設備的I/O區域的分配和映射,以及完成硬件的其它初始化工作;

網絡設備使用struct net_device結構來描述,這個結構非常之大,許多重要的參考書籍對它都有較為深入的描述,可以參考《Linux設備驅動程序》中網卡驅動設計的相關章節。我會在后面的內容中,對其重要的成員進行注釋;

當probe函數被調用,證明已經發現了我們所支持的網卡,這樣,就可以調用register_netdev函數向內核注冊網絡設備了,注冊之前,一般會調用alloc_etherdev為以太網分析一個net_device,然后初始化它的重要成員。

除了向內核注冊網絡設備之外,探測函數另一項重要的工作就是需要對硬件進行初始化,比如,要訪問其I/O區域,需要為I/O區域分配內存區域,然后進行映射,這一步一般的流程是:
1、request_mem_region()
2、ioremap()

對于一般的PCI設備而言,可以調用:
1、pci_request_regions()
2、ioremap()

pci_request_regions函數對PCI的6個寄存器都會調用資源分配函數進行申請(需要判斷是I/O端口還是I/O內存),例如:

[Copy to clipboard] ??????????? CODE: int pci_request_regions(struct pci_dev *pdev, char *res_name)
{
? ? ? ? int i;
? ? ? ?
? ? ? ? for (i = 0; i < 6; i++)
? ? ? ? ? ? ? ? if(pci_request_region(pdev, i, res_name))
? ? ? ? ? ? ? ? ? ? ? ? goto err_out;
? ? ? ? return 0;


[Copy to clipboard] ??????????? CODE: int pci_request_region(struct pci_dev *pdev, int bar, char *res_name)
{
? ? ? ? if (pci_resource_len(pdev, bar) == 0)
? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? ? ?
? ? ? ? if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) {
? ? ? ? ? ? ? ? if (!request_region(pci_resource_start(pdev, bar),
? ? ? ? ? ? ? ? ? ? ? ?? ???pci_resource_len(pdev, bar), res_name))
? ? ? ? ? ? ? ? ? ? ? ? goto err_out;
? ? ? ? }
? ? ? ? else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
? ? ? ? ? ? ? ? if (!request_mem_region(pci_resource_start(pdev, bar),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?? ?? ?pci_resource_len(pdev, bar), res_name))
? ? ? ? ? ? ? ? ? ? ? ? goto err_out;
? ? ? ? }
? ? ? ?
? ? ? ? return 0;
有了這些基礎,我們來看設備的探測函數:
static int __devinit e100_probe(struct pci_dev *pdev,
? ? ? ? const struct pci_device_id *ent)
{
? ? ? ? struct net_device *netdev;
? ? ? ? struct nic *nic;
? ? ? ? int err;

? ? ? ? /*分配網絡設備*/
? ? ? ? if(!(netdev = alloc_etherdev(sizeof(struct nic)))) {
? ? ? ? ? ? ? ? if(((1 << debug) - 1) & NETIF_MSG_PROBE)
? ? ? ? ? ? ? ? ? ? ? ? printk(KERN_ERR PFX "Etherdev alloc failed, abort./n");
? ? ? ? ? ? ? ? return -ENOMEM;
? ? ? ? }

? ? ? ? /*設置各成員指針函數*/
? ? ? ? netdev->open = e100_open;
? ? ? ? netdev->stop = e100_close;
? ? ? ? netdev->hard_start_xmit = e100_xmit_frame;
? ? ? ? netdev->get_stats = e100_get_stats;
? ? ? ? netdev->set_multicast_list = e100_set_multicast_list;
? ? ? ? netdev->set_mac_address = e100_set_mac_address;
? ? ? ? netdev->change_mtu = e100_change_mtu;
? ? ? ? netdev->do_ioctl = e100_do_ioctl;
? ? ? ? SET_ETHTOOL_OPS(netdev, &e100_ethtool_ops);
? ? ? ? netdev->tx_timeout = e100_tx_timeout;
? ? ? ? netdev->watchdog_timeo = E100_WATCHDOG_PERIOD;
? ? ? ? netdev->poll = e100_poll;
? ? ? ? netdev->weight = E100_NAPI_WEIGHT;
#ifdef CONFIG_NET_POLL_CONTROLLER
? ? ? ? netdev->poll_controller = e100_netpoll;
#endif
? ? ? ? /*設置網絡設備名稱*/
? ? ? ? strcpy(netdev->name, pci_name(pdev));

? ? ? ? /*取得設備私有數據結構*/
? ? ? ? nic = netdev_priv(netdev);
? ? ? ? /*網絡設備指針,指向自己*/
? ? ? ? nic->netdev = netdev;
? ? ? ? /*PCIy設備指針,指向自己*/
? ? ? ? nic->pdev = pdev;
? ? ? ? nic->msg_enable = (1 << debug) - 1;
? ? ? ?
? ? ? ? /*將PCI設備的私有數據區指向網絡設備*/
? ? ? ? pci_set_drvdata(pdev, netdev);

? ? ? ? /*激活PCI設備*/
? ? ? ? if((err = pci_enable_device(pdev))) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting./n");
? ? ? ? ? ? ? ? goto err_out_free_dev;
? ? ? ? }

? ? ? ? /*判斷I/O區域是否是I/O內存,如果不是,則報錯退出*/
? ? ? ? if(!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot find proper PCI device "
? ? ? ? ? ? ? ? ? ? ? ? "base address, aborting./n");
? ? ? ? ? ? ? ? err = -ENODEV;
? ? ? ? ? ? ? ? goto err_out_disable_pdev;
? ? ? ? }

? ? ? ? /*分配I/O內存區域*/
? ? ? ? if((err = pci_request_regions(pdev, DRV_NAME))) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting./n");
? ? ? ? ? ? ? ? goto err_out_disable_pdev;
? ? ? ? }

? ? ? ? /*
? ? ? ???* 告之內核自己的DMA尋址能力,這里不是很明白,因為從0xFFFFFFFF來看,本來就是內核默認的32了
? ? ? ???* 為什么還要調用pci_set_dma_mask來重復設置呢?可能是對ULL而非UL不是很了解吧。
? ? ? ???*/
? ? ? ? if((err = pci_set_dma_mask(pdev, 0xFFFFFFFFULL))) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "No usable DMA configuration, aborting./n");
? ? ? ? ? ? ? ? goto err_out_free_res;
? ? ? ? }

? ? ? ? SET_MODULE_OWNER(netdev);
? ? ? ? SET_NETDEV_DEV(netdev, &pdev->dev);

? ? ? ? /*分配完成后,映射I/O內存*/
? ? ? ? nic->csr = ioremap(pci_resource_start(pdev, 0), sizeof(struct csr));
? ? ? ? if(!nic->csr) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot map device registers, aborting./n");
? ? ? ? ? ? ? ? err = -ENOMEM;
? ? ? ? ? ? ? ? goto err_out_free_res;
? ? ? ? }

? ? ? ? if(ent->driver_data)
? ? ? ? ? ? ? ? nic->flags |= ich;
? ? ? ? else
? ? ? ? ? ? ? ? nic->flags &= ~ich;

? ? ? ? /*設置設備私有數據結構的大部份默認參數*/
? ? ? ? e100_get_defaults(nic);

? ? ? ? /* 初始化自旋鎖,鍋的初始化必須在調用 hw_reset 之前執行*/
? ? ? ? spin_lock_init(&nic->cb_lock);
? ? ? ? spin_lock_init(&nic->cmd_lock);

? ? ? ? /* 硬件復位,通過向指定I/O端口設置復位指令實現. */
? ? ? ? e100_hw_reset(nic);

? ? ? ? /*
? ? ? ???* PCI網卡被BIOS配置后,某些特性可能會被屏蔽掉。比如,多數BIOS都會清掉“master”位,
? ? ? ???* 這導致板卡不能隨意向主存中拷貝數據。pci_set_master函數數會檢查是否需要設置標志位,
? ? ? ???* 如果需要,則會將“master”位置位。
? ? ? ???* PS:什么是PCI master?
? ? ? ???* 不同于ISA總線,PCI總線的地址總線與數據總線是分時復用的。這樣做的好處是,一方面
? ? ? ???* 可以節省接插件的管腳數,另一方面便于實現突發數據傳輸。在做數據傳輸時,由一個PCI
? ? ? ???* 設備做發起者(主控,Initiator或Master),而另一個PCI設備做目標(從設備,Target或Slave)。
? ? ? ???* 總線上的所有時序的產生與控制,都由Master來發起。PCI總線在同一時刻只能供一對設備完成傳輸。
? ? ? ???*/
? ? ? ? pci_set_master(pdev);

? ? ? ? /*添加兩個內核定時器,watchdog和blink_timer*/
? ? ? ? init_timer(&nic->watchdog);
? ? ? ? nic->watchdog.function = e100_watchdog;
? ? ? ? nic->watchdog.data = (unsigned long)nic;
? ? ? ? init_timer(&nic->blink_timer);
? ? ? ? nic->blink_timer.function = e100_blink_led;
? ? ? ? nic->blink_timer.data = (unsigned long)nic;

? ? ? ? INIT_WORK(&nic->tx_timeout_task,
? ? ? ? ? ? ? ? (void (*)(void *))e100_tx_timeout_task, netdev);

? ? ? ? if((err = e100_alloc(nic))) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot alloc driver memory, aborting./n");
? ? ? ? ? ? ? ? goto err_out_iounmap;
? ? ? ? }

? ? ? ? /*phy寄存器初始化*/
? ? ? ? e100_phy_init(nic);

? ? ? ? if((err = e100_eeprom_load(nic)))
? ? ? ? ? ? ? ? goto err_out_free;

? ? ? ? memcpy(netdev->dev_addr, nic->eeprom, ETH_ALEN);
? ? ? ? if(!is_valid_ether_addr(netdev->dev_addr)) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Invalid MAC address from "
? ? ? ? ? ? ? ? ? ? ? ? "EEPROM, aborting./n");
? ? ? ? ? ? ? ? err = -EAGAIN;
? ? ? ? ? ? ? ? goto err_out_free;
? ? ? ? }

? ? ? ? /* Wol magic packet can be enabled from eeprom */
? ? ? ? if((nic->mac >= mac_82558_D101_A4) &&
? ? ? ?? ? (nic->eeprom[eeprom_id] & eeprom_id_wol))
? ? ? ? ? ? ? ? nic->flags |= wol_magic;

? ? ? ? /* ack any pending wake events, disable PME */
? ? ? ? pci_enable_wake(pdev, 0, 0);

? ? ? ? /*注冊網絡設備*/
? ? ? ? strcpy(netdev->name, "eth%d");
? ? ? ? if((err = register_netdev(netdev))) {
? ? ? ? ? ? ? ? DPRINTK(PROBE, ERR, "Cannot register net device, aborting./n");
? ? ? ? ? ? ? ? goto err_out_free;
? ? ? ? }

? ? ? ? DPRINTK(PROBE, INFO, "addr 0x%lx, irq %d, "
? ? ? ? ? ? ? ? "MAC addr %02X:%02X:%02X:%02X:%02X:%02X/n",
? ? ? ? ? ? ? ? pci_resource_start(pdev, 0), pdev->irq,
? ? ? ? ? ? ? ? netdev->dev_addr[0], netdev->dev_addr[1], netdev->dev_addr[2],
? ? ? ? ? ? ? ? netdev->dev_addr[3], netdev->dev_addr[4], netdev->dev_addr[5]);

? ? ? ? return 0;

err_out_free:
? ? ? ? e100_free(nic);
err_out_iounmap:
? ? ? ? iounmap(nic->csr);
err_out_free_res:
? ? ? ? pci_release_regions(pdev);
err_out_disable_pdev:
? ? ? ? pci_disable_device(pdev);
err_out_free_dev:
? ? ? ? pci_set_drvdata(pdev, NULL);
? ? ? ? free_netdev(netdev);
? ? ? ? return err;
}
執行到這里,探測函數的使命就完成了,在對網絡設備重要成員初始化時,有:
netdev->open = e100_open;
指定了設備的open函數為e100_open,這樣,當第一次使用設備,比如使用ifconfig工具的時候,open函數將被調用。
?
二、打開設備

在探測函數中,設置了netdev->open = e100_open; 指定了設備的open函數為e100_open:

[Copy to clipboard] CODE: static int e100_open(struct net_device *netdev)
{
? ? ? ? struct nic *nic = netdev_priv(netdev);
? ? ? ? int err = 0;

? ? ? ? netif_carrier_off(netdev);
? ? ? ? if((err = e100_up(nic)))
? ? ? ? ? ? ? ? DPRINTK(IFUP, ERR, "Cannot open interface, aborting./n");
? ? ? ? return err;
}
大多數涉及物理設備可以感知信號載波(carrier)的存在,載波的存在意味著設備可以工作
據個例子來講:當一個用戶拔掉了網線,也就意味著信號載波的消失。
netif_carrier_off:關閉載波信號;
netif_carrier_on:打開載波信號;
netif_carrier_ok:檢測載波信號;

對于探測網卡網線是否連接,這一組函數被使用得較多;

接著,調用e100_up函數啟動網卡,這個“啟動”的過程,最重要的步驟有:
1、調用request_irq向內核注冊中斷;
2、調用netif_wake_queue函數來重新啟動傳輸隊例;

[Copy to clipboard] CODE: static int e100_up(struct nic *nic)
{
? ? ? ? int err;

? ? ? ? if((err = e100_rx_alloc_list(nic)))
? ? ? ? ? ? ? ? return err;
? ? ? ? if((err = e100_alloc_cbs(nic)))
? ? ? ? ? ? ? ? goto err_rx_clean_list;
? ? ? ? if((err = e100_hw_init(nic)))
? ? ? ? ? ? ? ? goto err_clean_cbs;
? ? ? ? e100_set_multicast_list(nic->netdev);
? ? ? ? e100_start_receiver(nic, 0);
? ? ? ? mod_timer(&nic->watchdog, jiffies);
? ? ? ? if((err = request_irq(nic->pdev->irq, e100_intr, SA_SHIRQ,
? ? ? ? ? ? ? ? nic->netdev->name, nic->netdev)))
? ? ? ? ? ? ? ? goto err_no_irq;
? ? ? ? netif_wake_queue(nic->netdev);
? ? ? ? netif_poll_enable(nic->netdev);
? ? ? ? /* enable ints _after_ enabling poll, preventing a race between
? ? ? ???* disable ints+schedule */
? ? ? ? e100_enable_irq(nic);
? ? ? ? return 0;

err_no_irq:
? ? ? ? del_timer_sync(&nic->watchdog);
err_clean_cbs:
? ? ? ? e100_clean_cbs(nic);
err_rx_clean_list:
? ? ? ? e100_rx_clean_list(nic);
? ? ? ? return err;
}

這樣,中斷函數e100_intr將被調用;
三、網卡中斷

從本質上來講,中斷,是一種電信號,當設備有某種事件發生的時候,它就會產生中斷,通過總線把電信號發送給中斷控制器,如果中斷的線是激活的,中斷控制器 就把電信號發送給處理器的某個特定引腳。處理器于是立即停止自己正在做的事,跳到內存中內核設置的中斷處理程序的入口點,進行中斷處理。
在內核中斷處理中,會檢測中斷與我們剛才注冊的中斷號匹配,于是,注冊的中斷處理函數就被調用了。

當需要發/收數據,出現錯誤,連接狀態變化等,網卡的中斷信號會被觸發。當接收到中斷后,中斷函數讀取中斷狀態位,進行合法性判斷,如判斷中斷信號是否是自己的等,然后,應答設備中斷——OK,我已經知道了,你回去繼續工作吧……
接著,它就屏蔽此中斷,然后netif_rx_schedule函數接收,接收函數 會在未來某一時刻調用設備的poll函數(對這里而言,注冊的是e100_poll)實現設備的輪詢:

[Copy to clipboard] CODE: static irqreturn_t e100_intr(int irq, void *dev_id, struct pt_regs *regs)
{
? ? ? ? struct net_device *netdev = dev_id;
? ? ? ? struct nic *nic = netdev_priv(netdev);
? ? ? ? u8 stat_ack = readb(&nic->csr->scb.stat_ack);

? ? ? ? DPRINTK(INTR, DEBUG, "stat_ack = 0x%02X/n", stat_ack);

? ? ? ? if(stat_ack == stat_ack_not_ours ||? ? ? ? /* Not our interrupt */
? ? ? ?? ? stat_ack == stat_ack_not_present)? ? ? ? /* Hardware is ejected */
? ? ? ? ? ? ? ? return IRQ_NONE;

? ? ? ? /* Ack interrupt(s) */
? ? ? ? writeb(stat_ack, &nic->csr->scb.stat_ack);

? ? ? ? /* We hit Receive No Resource (RNR); restart RU after cleaning */
? ? ? ? if(stat_ack & stat_ack_rnr)
? ? ? ? ? ? ? ? nic->ru_running = RU_SUSPENDED;

? ? ? ? e100_disable_irq(nic);
? ? ? ? netif_rx_schedule(netdev);

? ? ? ? return IRQ_HANDLED;
}
對于數據包的接收而言,我們關注的是poll函數中,調用e100_rx_clean進行數據的接收:

[Copy to clipboard] CODE: static int e100_poll(struct net_device *netdev, int *budget)
{
? ? ? ? struct nic *nic = netdev_priv(netdev);
? ?? ? /*
? ?? ?? ?* netdev->quota是當前CPU能夠從所有接口中接收數據包的最大數目,budget是在
? ?? ?? ?* 初始化階段分配給接口的weight值,輪詢函數必須接受二者之間的最小值。表示
? ?? ?? ?* 輪詢函數本次要處理的數據包個數。
? ?? ?? ?*/
? ? ? ? unsigned int work_to_do = min(netdev->quota, *budget);
? ? ? ? unsigned int work_done = 0;
? ? ? ? int tx_cleaned;

? ?? ?? ? /*進行數據包的接收和傳輸*/? ?? ?? ?? ?
? ? ? ? e100_rx_clean(nic, &work_done, work_to_do);
? ? ? ? tx_cleaned = e100_tx_clean(nic);

? ?? ?? ?/*接收和傳輸完成后,就退出poll模塊,重啟中斷*/
? ? ? ? /* If no Rx and Tx cleanup work was done, exit polling mode. */
? ? ? ? if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {
? ? ? ? ? ? ? ? netif_rx_complete(netdev);
? ? ? ? ? ? ? ? e100_enable_irq(nic);
? ? ? ? ? ? ? ? return 0;
? ? ? ? }

? ? ? ? *budget -= work_done;
? ? ? ? netdev->quota -= work_done;

? ? ? ? return 1;
}
static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
? ? ? ? unsigned int work_to_do)
{
? ? ? ? struct rx *rx;
? ? ? ? int restart_required = 0;
? ? ? ? struct rx *rx_to_start = NULL;

? ? ? ? /* are we already rnr? then pay attention!!! this ensures that
? ? ? ???* the state machine progression never allows a start with a
? ? ? ???* partially cleaned list, avoiding a race between hardware
? ? ? ???* and rx_to_clean when in NAPI mode */
? ? ? ? if(RU_SUSPENDED == nic->ru_running)
? ? ? ? ? ? ? ? restart_required = 1;

? ? ? ? /* Indicate newly arrived packets */
? ? ? ? for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
? ? ? ? ? ? ? ? int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
? ? ? ? ? ? ? ? if(-EAGAIN == err) {
? ? ? ? ? ? ? ? ? ? ? ? /* hit quota so have more work to do, restart once
? ? ? ? ? ? ? ? ? ? ? ???* cleanup is complete */
? ? ? ? ? ? ? ? ? ? ? ? restart_required = 0;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? } else if(-ENODATA == err)
? ? ? ? ? ? ? ? ? ? ? ? break; /* No more to clean */
? ? ? ? }

? ? ? ? /* save our starting point as the place we'll restart the receiver */
? ? ? ? if(restart_required)
? ? ? ? ? ? ? ? rx_to_start = nic->rx_to_clean;

? ? ? ? /* Alloc new skbs to refill list */
? ? ? ? for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {
? ? ? ? ? ? ? ? if(unlikely(e100_rx_alloc_skb(nic, rx)))
? ? ? ? ? ? ? ? ? ? ? ? break; /* Better luck next time (see watchdog) */
? ? ? ? }

? ? ? ? if(restart_required) {
? ? ? ? ? ? ? ? // ack the rnr?
? ? ? ? ? ? ? ? writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);
? ? ? ? ? ? ? ? e100_start_receiver(nic, rx_to_start);
? ? ? ? ? ? ? ? if(work_done)
? ? ? ? ? ? ? ? ? ? ? ? (*work_done)++;
? ? ? ? }
}

四、網卡的數據接收

內核如何從網卡接受數據,傳統的經典過程:
1、數據到達網卡;
2、網卡產生一個中斷給內核;
3、內核使用I/O指令,從網卡I/O區域中去讀取數據;


我們在許多網卡驅動中,都可以在網卡的中斷函數中見到這一過程。

但是,這一種方法,有一種重要的問題,就是大流量的數據來到,網卡會產生大量的中斷,內核在中斷上下文中,會浪費大量的資源來處理中斷本身。所以,一個問 題是,“可不可以不使用中斷”,這就是輪詢技術,所謂NAPI技術,說來也不神秘,就是說,內核屏蔽中斷,然后隔一會兒就去問網卡,“你有沒有數據 啊?”……

從這個描述本身可以看到,哪果數據量少,輪詢同樣占用大量的不必要的CPU資源,大家各有所長吧,呵呵……

OK,另一個問題,就是從網卡的I/O區域,包括I/O寄存器或I/O內存中去讀取數據,這都要CPU去讀,也要占用CPU資源,“CPU從I/O區域 讀,然后把它放到內存(這個內存指的是系統本身的物理內存,跟外設的內存不相干,也叫主內存)中”。于是自然地,就想到了DMA技術——讓網卡直接從主內 存之間讀寫它們的I/O數據,CPU,這兒不干你事,自己找樂子去:
1、首先,內核在主內存中為收發數據建立一個環形的緩沖隊列(通常叫DMA環形緩沖區)。
2、內核將這個緩沖區通過DMA映射,把這個隊列交給網卡;
3、網卡收到數據,就直接放進這個環形緩沖區了——也就是直接放進主內存了;然后,向系統產生一個中斷;
4、內核收到這個中斷,就取消DMA映射,這樣,內核就直接從主內存中讀取數據;


——呵呵,這一個過程比傳統的過程少了不少工作,因為設備直接把數據放進了主內存,不需要CPU的干預,效率是不是提高不少?

對應以上4步,來看它的具體實現:
1、分配環形DMA緩沖區
Linux內核中,用skb來描述一個緩存,所謂分配,就是建立一定數量的skb,然后把它們組織成一個雙向鏈表;

2、建立DMA映射
內核通過調用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射關系。
struct device *dev,描述一個設備;
buffer:把哪個地址映射給設備;也就是某一個skb——要映射全部,當然是做一個雙向鏈表的循環即可;
size:緩存大小;
direction:映射方向——誰傳給誰:一般來說,是“雙向”映射,數據在設備和內存之間雙向流動;

對于PCI設備而言(網卡一般是PCI的),通過另一個包裹函數pci_map_single,這樣,就把buffer交給設備了!設備可以直接從里邊讀/取數據。

3、這一步由硬件完成;

4、取消映射
dma_unmap_single,對PCI而言,大多調用它的包裹函數pci_unmap_single,不取消的話,緩存控制權還在設備手里,要調用它,把主動權掌握在CPU手里——因為我們已經接收到數據了,應該由CPU把數據交給上層網絡棧;

當然,不取消之前,通常要讀一些狀態位信息,諸如此類,一般是調用
dma_sync_single_for_cpu()
讓CPU在取消映射前,就可以訪問DMA緩沖區中的內容。

關于DMA映射的更多內容,可以參考《Linux設備驅動程序》“內存映射和DMA”章節相關內容!

OK,有了這些知識,我們就可以來看e100的代碼了,它跟上面講的步驟基本上一樣的——繞了這么多圈子,就是想繞到e100上面了,呵呵!


在e100_open函數中,調用e100_up,我們前面分析它時,略過了一個重要的東東,就是環形緩沖區的建立,這一步,是通過
e100_rx_alloc_list函數調用完成的:

[Copy to clipboard] CODE: static int e100_rx_alloc_list(struct nic *nic)
{
? ? ? ? struct rx *rx;
? ? ? ? unsigned int i, count = nic->params.rfds.count;

? ? ? ? nic->rx_to_use = nic->rx_to_clean = NULL;
? ? ? ? nic->ru_running = RU_UNINITIALIZED;

? ? ? ? /*結構struct rx用來描述一個緩沖區節點,這里分配了count個*/
? ? ? ? if(!(nic->rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))
? ? ? ? ? ? ? ? return -ENOMEM;
? ? ? ? memset(nic->rxs, 0, sizeof(struct rx) * count);

? ? ? ? /*雖然是連續分配的,不過還是遍歷它,建立雙向鏈表,然后為每一個rx的skb指針分員分配空間
? ? ? ? skb用來描述內核中的一個數據包,呵呵,說到重點了*/
? ? ? ? for(rx = nic->rxs, i = 0; i < count; rx++, i++) {
? ? ? ? ? ? ? ? rx->next = (i + 1 < count) ? rx + 1 : nic->rxs;
? ? ? ? ? ? ? ? rx->prev = (i == 0) ? nic->rxs + count - 1 : rx - 1;
? ? ? ? ? ? ? ? if(e100_rx_alloc_skb(nic, rx)) {? ? ? ? ? ? ? ? /*分配緩存*/
? ? ? ? ? ? ? ? ? ? ? ? e100_rx_clean_list(nic);
? ? ? ? ? ? ? ? ? ? ? ? return -ENOMEM;
? ? ? ? ? ? ? ? }
? ? ? ? }

? ? ? ? nic->rx_to_use = nic->rx_to_clean = nic->rxs;
? ? ? ? nic->ru_running = RU_SUSPENDED;

? ? ? ? return 0;
}


[Copy to clipboard] CODE: #define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)
{
? ? ? ? /*skb緩存的分配,是通過調用系統函數dev_alloc_skb來完成的,它同內核棧中通常調用alloc_skb的區別在于,
? ? ? ? 它是原子的,所以,通常在中斷上下文中使用*/
? ? ? ? if(!(rx->skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))
? ? ? ? ? ? ? ? return -ENOMEM;

? ? ? ? /*初始化必要的成員 */
? ? ? ? rx->skb->dev = nic->netdev;
? ? ? ? skb_reserve(rx->skb, NET_IP_ALIGN);
? ? ? ? /*這里在數據區之前,留了一塊sizeof(struct rfd) 這么大的空間,該結構的
? ? ? ? 一個重要作用,用來保存一些狀態信息,比如,在接收數據之前,可以先通過
? ? ? ? 它,來判斷是否真有數據到達等,諸如此類*/
? ? ? ? memcpy(rx->skb->data, &nic->blank_rfd, sizeof(struct rfd));
? ? ? ? /*這是最關鍵的一步,建立DMA映射,把每一個緩沖區rx->skb->data都映射給了設備,緩存區節點
? ? ? ? rx利用dma_addr保存了每一次映射的地址,這個地址后面會被用到*/
? ? ? ? rx->dma_addr = pci_map_single(nic->pdev, rx->skb->data,
? ? ? ? ? ? ? ? RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);

? ? ? ? if(pci_dma_mapping_error(rx->dma_addr)) {
? ? ? ? ? ? ? ? dev_kfree_skb_any(rx->skb);
? ? ? ? ? ? ? ? rx->skb = 0;
? ? ? ? ? ? ? ? rx->dma_addr = 0;
? ? ? ? ? ? ? ? return -ENOMEM;
? ? ? ? }

? ? ? ? /* Link the RFD to end of RFA by linking previous RFD to
? ? ? ???* this one, and clearing EL bit of previous.??*/
? ? ? ? if(rx->prev->skb) {
? ? ? ? ? ? ? ? struct rfd *prev_rfd = (struct rfd *)rx->prev->skb->data;
? ? ? ? ? ? ? ? /*put_unaligned(val,ptr);用到把var放到ptr指針的地方,它能處理處理內存對齊的問題
? ? ? ? ? ? ? ? prev_rfd是在緩沖區開始處保存的一點空間,它的link成員,也保存了映射后的地址*/
? ? ? ? ? ? ? ? put_unaligned(cpu_to_le32(rx->dma_addr),
? ? ? ? ? ? ? ? ? ? ? ? (u32 *)&prev_rfd->link);
? ? ? ? ? ? ? ? wmb();
? ? ? ? ? ? ? ? prev_rfd->command &= ~cpu_to_le16(cb_el);
? ? ? ? ? ? ? ? pci_dma_sync_single_for_device(nic->pdev, rx->prev->dma_addr,
? ? ? ? ? ? ? ? ? ? ? ? sizeof(struct rfd), PCI_DMA_TODEVICE);
? ? ? ? }

? ? ? ? return 0;
}
e100_rx_alloc_list函數在一個循環中,建立了環形緩沖區,并調用e100_rx_alloc_skb為每個緩沖區分配了空間,并做了
DMA映射。這樣,我們就可以來看接收數據的過程了。

前面我們講過,中斷函數中,調用netif_rx_schedule,表明使用輪詢技術,系統會在未來某一時刻,調用設備的poll函數:

[Copy to clipboard] CODE: static int e100_poll(struct net_device *netdev, int *budget)
{
? ? ? ? struct nic *nic = netdev_priv(netdev);
? ? ? ? unsigned int work_to_do = min(netdev->quota, *budget);
? ? ? ? unsigned int work_done = 0;
? ? ? ? int tx_cleaned;

? ? ? ? e100_rx_clean(nic, &work_done, work_to_do);
? ? ? ? tx_cleaned = e100_tx_clean(nic);

? ? ? ? /* If no Rx and Tx cleanup work was done, exit polling mode. */
? ? ? ? if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {
? ? ? ? ? ? ? ? netif_rx_complete(netdev);
? ? ? ? ? ? ? ? e100_enable_irq(nic);
? ? ? ? ? ? ? ? return 0;
? ? ? ? }

? ? ? ? *budget -= work_done;
? ? ? ? netdev->quota -= work_done;

? ? ? ? return 1;
}
目前,我們只關心rx,所以,e100_rx_clean函數就成了我們關注的對像,它用來從緩沖隊列中接收全部數據(這或許是取名為clean的原因吧!):

[Copy to clipboard] CODE: static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
? ? ? ? unsigned int work_to_do)
{
? ? ? ? struct rx *rx;
? ? ? ? int restart_required = 0;
? ? ? ? struct rx *rx_to_start = NULL;

? ? ? ? /* are we already rnr? then pay attention!!! this ensures that
? ? ? ???* the state machine progression never allows a start with a
? ? ? ???* partially cleaned list, avoiding a race between hardware
? ? ? ???* and rx_to_clean when in NAPI mode */
? ? ? ? if(RU_SUSPENDED == nic->ru_running)
? ? ? ? ? ? ? ? restart_required = 1;

? ? ? ? /* 函數最重要的工作,就是遍歷環形緩沖區,接收數據*/
? ? ? ? for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
? ? ? ? ? ? ? ? int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
? ? ? ? ? ? ? ? if(-EAGAIN == err) {
? ? ? ? ? ? ? ? ? ? ? ? /* hit quota so have more work to do, restart once
? ? ? ? ? ? ? ? ? ? ? ???* cleanup is complete */
? ? ? ? ? ? ? ? ? ? ? ? restart_required = 0;
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? } else if(-ENODATA == err)
? ? ? ? ? ? ? ? ? ? ? ? break; /* No more to clean */
? ? ? ? }

? ? ? ? /* save our starting point as the place we'll restart the receiver */
? ? ? ? if(restart_required)
? ? ? ? ? ? ? ? rx_to_start = nic->rx_to_clean;

? ? ? ? /* Alloc new skbs to refill list */
? ? ? ? for(rx = nic->rx_to_use; !rx->skb; rx = nic->rx_to_use = rx->next) {
? ? ? ? ? ? ? ? if(unlikely(e100_rx_alloc_skb(nic, rx)))
? ? ? ? ? ? ? ? ? ? ? ? break; /* Better luck next time (see watchdog) */
? ? ? ? }

? ? ? ? if(restart_required) {
? ? ? ? ? ? ? ? // ack the rnr?
? ? ? ? ? ? ? ? writeb(stat_ack_rnr, &nic->csr->scb.stat_ack);
? ? ? ? ? ? ? ? e100_start_receiver(nic, rx_to_start);
? ? ? ? ? ? ? ? if(work_done)
? ? ? ? ? ? ? ? ? ? ? ? (*work_done)++;
? ? ? ? }
}


[Copy to clipboard] CODE: static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,
? ? ? ? unsigned int *work_done, unsigned int work_to_do)
{
? ? ? ? struct sk_buff *skb = rx->skb;
? ? ? ? struct rfd *rfd = (struct rfd *)skb->data;
? ? ? ? u16 rfd_status, actual_size;

? ? ? ? if(unlikely(work_done && *work_done >= work_to_do))
? ? ? ? ? ? ? ? return -EAGAIN;

? ? ? ? /* 讀取數據之前,也就是取消DMA映射之前,需要先讀取cb_complete 狀態位,
? ? ? ? 以確定數據是否真的準備好了,并且,rfd的actual_size中,也包含了真實的數據大小
? ? ? ? pci_dma_sync_single_for_cpu函數前面已經介紹過,它讓CPU在取消DMA映射之前,具備
? ? ? ? 訪問DMA緩存的能力*/
? ? ? ? pci_dma_sync_single_for_cpu(nic->pdev, rx->dma_addr,
? ? ? ? ? ? ? ? sizeof(struct rfd), PCI_DMA_FROMDEVICE);
? ? ? ? rfd_status = le16_to_cpu(rfd->status);

? ? ? ? DPRINTK(RX_STATUS, DEBUG, "status=0x%04X/n", rfd_status);

? ? ? ? /* If data isn't ready, nothing to indicate */
? ? ? ? if(unlikely(!(rfd_status & cb_complete)))
? ? ? ? ? ? ? ? return -ENODATA;

? ? ? ? /* Get actual data size */
? ? ? ? actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;
? ? ? ? if(unlikely(actual_size > RFD_BUF_LEN - sizeof(struct rfd)))
? ? ? ? ? ? ? ? actual_size = RFD_BUF_LEN - sizeof(struct rfd);

? ? ? ? /* 取消映射,因為通過DMA,網卡已經把數據放在了主內存中,這里一取消,也就意味著,
? ? ? ? CPU可以處理主內存中的數據了 */
? ? ? ? pci_unmap_single(nic->pdev, rx->dma_addr,
? ? ? ? ? ? ? ? RFD_BUF_LEN, PCI_DMA_FROMDEVICE);

? ? ? ? /* this allows for a fast restart without re-enabling interrupts */
? ? ? ? if(le16_to_cpu(rfd->command) & cb_el)
? ? ? ? ? ? ? ? nic->ru_running = RU_SUSPENDED;
? ? ? ?
? ? ? ? /*正確地設置data指針,因為最前面有一個sizeof(struct rfd)大小區域,跳過它*/
? ? ? ? skb_reserve(skb, sizeof(struct rfd));
? ? ? ? /*更新skb的tail和len指針,也是就更新接收到這么多數據的長度*/
? ? ? ? skb_put(skb, actual_size);
? ? ? ? /*設置協議位*/
? ? ? ? skb->protocol = eth_type_trans(skb, nic->netdev);

? ? ? ? if(unlikely(!(rfd_status & cb_ok))) {
? ? ? ? ? ? ? ? /* Don't indicate if hardware indicates errors */
? ? ? ? ? ? ? ? nic->net_stats.rx_dropped++;
? ? ? ? ? ? ? ? dev_kfree_skb_any(skb);
? ? ? ? } else if(actual_size > nic->netdev->mtu + VLAN_ETH_HLEN) {
? ? ? ? ? ? ? ? /* Don't indicate oversized frames */
? ? ? ? ? ? ? ? nic->rx_over_length_errors++;
? ? ? ? ? ? ? ? nic->net_stats.rx_dropped++;
? ? ? ? ? ? ? ? dev_kfree_skb_any(skb);
? ? ? ? } else {
? ? ? ? ? ? ? ? /*網卡驅動要做的最后一步,就是統計接收計數器,設置接收時間戳,然后調用netif_receive_skb,
? ? ? ? ? ? ? ? 把數據包交給上層協議棧,自己的光榮始命也就完成了*/
? ? ? ? ? ? ? ? nic->net_stats.rx_packets++;
? ? ? ? ? ? ? ? nic->net_stats.rx_bytes += actual_size;
? ? ? ? ? ? ? ? nic->netdev->last_rx = jiffies;
? ? ? ? ? ? ? ? netif_receive_skb(skb);
? ? ? ? ? ? ? ? if(work_done)
? ? ? ? ? ? ? ? ? ? ? ? (*work_done)++;
? ? ? ? }

? ? ? ? rx->skb = NULL;

? ? ? ? return 0;
}
網卡驅動執行到這里,數據接收的工作,也就處理完成了。但是,使用這一種方法的驅動,省去了網絡棧中一個重要的內容,就是
“隊列層”,讓我們來看看,傳統中斷接收數據包模式下,使用netif_rx函數調用,又會發生什么。

PS:九賤沒有去研究過所謂的“零拷貝”技術,不太清楚,它同這種DMA直取方式有何不同?難道是把網卡中的I/O內存直接映射到主內存中,這樣CPU就 可以像讀取主內存一樣,讀取網卡的內存,但是這要求設備要有好大的I/O內存來做緩沖呀!!^o^,外行了……希望哪位DX提點!

五、隊列層

1、軟中斷與下半部
當用中斷處理的時候,為了減少中斷處理的工作量,比如,一般中斷處理時,需要屏蔽其它中斷,如果中斷處理時間過長,那么其它中斷
有可能得不到及時處理,也以,有一種機制,就是把“不必馬上處理”的工作,推遲一點,讓它在中斷處理后的某一個時刻得到處理。這就
是下半部。

下半部只是一個機制,它在Linux中,有多種實現方式,其中一種對時間要求最嚴格的實現方式,叫“軟中斷”,可以使用:

open_softirq()

來向內核注冊一個軟中斷,
然后,在合適的時候,調用

raise_softirq_irqoff()

觸發它。

如果采用中斷方式接收數據(這一節就是在說中斷方式接收,后面,就不用這種假設了),同樣也需要軟中斷,可以調用

open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

向內核注冊一個名為NET_RX_SOFTIR的軟中斷,net_rx_action是軟中斷的處理函數。

然后,在驅動中斷處理完后的某一個時刻,調用

raise_softirq_irqoff(NET_RX_SOFTIRQ);

觸發它,這樣net_rx_action將得到執行。

2、隊列層
什么是隊列層?通常,在網卡收發數據的時候,需要維護一個緩沖區隊列,來緩存可能存在的突發數據,類似于前面的DMA環形緩沖區。
隊列層中,包含了一個叫做struct softnet_data:

[Copy to clipboard] CODE: struct softnet_data
{
? ? ? ? /*throttle 用于擁塞控制,當擁塞發生時,throttle將被設置,后續進入的數據包將被丟棄*/
? ? ? ? int? ? ? ? ? ? ? ? ? ? ? ? throttle;
? ? ? ? /*netif_rx函數返回的擁塞級別*/
? ? ? ? int? ? ? ? ? ? ? ? ? ? ? ? cng_level;
? ? ? ? int? ? ? ? ? ? ? ? ? ? ? ? avg_blog;
? ? ? ? /*softnet_data 結構包含一個指向接收和傳輸隊列的指針,input_pkt_queue成員指向準備傳送
? ? ? ? 給網絡層的sk_buffs包鏈表的首部的指針,這個隊列中的包是由netif_rx函數遞交的*/
? ? ? ? struct sk_buff_head? ? ? ? input_pkt_queue;
? ? ? ?
? ? ? ? struct list_head? ? ? ? poll_list;
? ? ? ? struct net_device? ? ? ? *output_queue;
? ? ? ? struct sk_buff? ? ? ? ? ? ? ? *completion_queue;

? ? ? ? struct net_device? ? ? ? backlog_dev;? ? ? ? /* Sorry. 8) */
};
內核使用了一個同名的變量softnet_data,它是一個Per-CPU變量,每個CPU都有一個。

net/core/dev.c

[Copy to clipboard] CODE: DECLARE_PER_CPU(struct softnet_data,softnet_data);


[Copy to clipboard] CODE: /*
? *? ?? ? 網絡模塊的核心處理模塊.
? */
static int __init net_dev_init(void)
{
? ? ? ? int i, rc = -ENOMEM;

? ? ? ? BUG_ON(!dev_boot_phase);

? ? ? ? net_random_init();

? ? ? ? if (dev_proc_init())? ? ? ? ? ? ? ? /*初始化proc文件系統*/
? ? ? ? ? ? ? ? goto out;

? ? ? ? if (netdev_sysfs_init())? ? ? ? /*初始化sysfs文件系統*/
? ? ? ? ? ? ? ? goto out;

? ? ? ? /*ptype_all和ptype_base是重點,后面會詳細分析,它們都是
? ? ? ? struct list_head類型變量,這里初始化鏈表成員*/
? ? ? ? INIT_LIST_HEAD(&ptype_all);
? ? ? ? for (i = 0; i < 16; i++)
? ? ? ? ? ? ? ? INIT_LIST_HEAD(&ptype_base[i]);

? ? ? ? for (i = 0; i < ARRAY_SIZE(dev_name_head); i++)
? ? ? ? ? ? ? ? INIT_HLIST_HEAD(&dev_name_head[i]);

? ? ? ? for (i = 0; i < ARRAY_SIZE(dev_index_head); i++)
? ? ? ? ? ? ? ? INIT_HLIST_HEAD(&dev_index_head[i]);

? ? ? ? /*
? ? ? ???*? ? ? ? 初始化包接收隊列,這里我們的重點了.
? ? ? ???*/

? ? ? ? /*遍歷每一個CPU,取得它的softnet_data,我們說過,它是一個struct softnet_data的Per-CPU變量*/
? ? ? ? for (i = 0; i < NR_CPUS; i++) {
? ? ? ? ? ? ? ? struct softnet_data *queue;
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? /*取得第i個CPU的softnet_data,因為隊列是包含在它里邊的,所以,我會直接說,“取得隊列”*/
? ? ? ? ? ? ? ? queue = &per_cpu(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->backlog_dev.state);
? ? ? ? ? ? ? ? queue->backlog_dev.weight = weight_p;
? ? ? ? ? ? ? ? /*這里,隊列中backlog_dev設備,它是一個偽網絡設備,不對應任何物理設備,它的poll函數,指向了
? ? ? ? ? ? ? ? process_backlog,后面我們會詳細分析*/
? ? ? ? ? ? ? ? queue->backlog_dev.poll = process_backlog;
? ? ? ? ? ? ? ? atomic_set(&queue->backlog_dev.refcnt, 1);
? ? ? ? }

#ifdef OFFLINE_SAMPLE
? ? ? ? samp_timer.expires = jiffies + (10 * HZ);
? ? ? ? add_timer(&samp_timer);
#endif

? ? ? ? dev_boot_phase = 0;
? ? ? ?
? ? ? ? /*注冊收/發軟中斷*/
? ? ? ? open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
? ? ? ? open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

? ? ? ? hotcpu_notifier(dev_cpu_callback, 0);
? ? ? ? dst_init();
? ? ? ? dev_mcast_init();
? ? ? ? rc = 0;
out:
? ? ? ? return rc;
}
這樣,初始化完成后,在驅動程序中,在中斷處理函數中,會調用netif_rx將數據交上來,這與采用輪詢技術,有本質的不同:

[Copy to clipboard] CODE: int netif_rx(struct sk_buff *skb)
{
? ? ? ? int this_cpu;
? ? ? ? struct softnet_data *queue;
? ? ? ? unsigned long flags;

? ? ? ? /* if netpoll wants it, pretend we never saw it */
? ? ? ? if (netpoll_rx(skb))
? ? ? ? ? ? ? ? return NET_RX_DROP;

? ? ? ? /*接收時間戳未設置,設置之*/
? ? ? ? if (!skb->stamp.tv_sec)
? ? ? ? ? ? ? ? net_timestamp(&skb->stamp);

? ? ? ? /*
? ? ? ???* 這里準備將數據包放入接收隊列,需要禁止本地中斷,在入隊操作完成后,再打開中斷.
? ? ? ???*/
? ? ? ? local_irq_save(flags);
? ? ? ? /*獲取當前CPU對應的softnet_data變量*/
? ? ? ? this_cpu = smp_processor_id();
? ? ? ? queue = &__get_cpu_var(softnet_data);

? ? ? ? /*接收計數器累加*/
? ? ? ? __get_cpu_var(netdev_rx_stat).total++;
? ? ? ?
? ? ? ? /*接收隊列是否已滿*/
? ? ? ? if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
? ? ? ? ? ? ? ? if (queue->input_pkt_queue.qlen) {
? ? ? ? ? ? ? ? ? ? ? ? if (queue->throttle)? ? ? ? ? ? ? ? ? ? ? ? /*擁塞發生了,丟棄數據包*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? goto drop;
? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? /*數據包入隊操作*/
enqueue:
? ? ? ? ? ? ? ? ? ? ? ? dev_hold(skb->dev);? ? ? ? ? ? ? ? ? ? ? ? /*累加設備引入計數器*/
? ? ? ? ? ? ? ? ? ? ? ? __skb_queue_tail(&queue->input_pkt_queue, skb);? ? ? ? ? ? ? ? /*將數據包加入接收隊列*/
#ifndef OFFLINE_SAMPLE
? ? ? ? ? ? ? ? ? ? ? ? get_sample_stats(this_cpu);
#endif
? ? ? ? ? ? ? ? ? ? ? ? local_irq_restore(flags);
? ? ? ? ? ? ? ? ? ? ? ? return queue->cng_level;
? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? /*
? ? ? ? ? ? ? ???* 驅動程序不斷地調用net_rx函數,實現接收數據包的入隊操作,當qlen == 0時, 則進入這段代碼,這里,如果已經被設置擁塞標志的話,則清除它,因為這里將要調用軟中斷,開始將數據包交給 上層了,即上層協議的接收函數將執行出隊操作,擁塞自然而然也就不存在了。 */
? ? ? ? ? ? ? ? if (queue->throttle)
? ? ? ? ? ? ? ? ? ? ? ? queue->throttle = 0;

? ? ? ? ? ? ? ? /*
? ? ? ? ? ? ? ???* netif_rx_schedule函數完成兩件重要的工作:
? ? ? ? ? ? ? ???* 1、將bakclog_dev設備加入“處理數據包的設備”的鏈表當中;
? ? ? ? ? ? ? ???* 2、觸發軟中斷函數,進行數據包接收處理;
? ? ? ? ? ? ? ???*/
? ? ? ? ? ? ? ? netif_rx_schedule(&queue->backlog_dev);
? ? ? ? ? ? ? ? goto enqueue;
? ? ? ? }

? ? ? ? /*前面判斷了隊列是否已滿,如果已滿而標志未設置,設置之,并累加擁塞計數器*/
? ? ? ? if (!queue->throttle) {
? ? ? ? ? ? ? ? queue->throttle = 1;
? ? ? ? ? ? ? ? __get_cpu_var(netdev_rx_stat).throttled++;
? ? ? ? }

/*擁塞發生,累加丟包計數器,釋放數據包*/
drop:
? ? ? ? __get_cpu_var(netdev_rx_stat).dropped++;
? ? ? ? local_irq_restore(flags);

? ? ? ? kfree_skb(skb);
? ? ? ? return NET_RX_DROP;
}
從 這段代碼的分析中,我們可以看到,當第一個數據包被接收后,因為qlen==0,所以首先會調用netif_rx_schedule觸發軟中斷,然后利用 goto跳轉至入隊。因為軟中斷被觸發后,將執行出隊操作,把數據交往上層處理。而當這個時候,又有數據包進入,即網卡中斷產生,因為它的優先級高過軟中 斷,這樣,出隊操作即被中斷,網卡中斷程序再將被調用,netif_rx函數又再次被執行,如果隊列未滿,就入隊返回。中斷完成后,軟中斷的執行過程被恢 復而繼續執行出隊——如此生產者/消費者循環不止,生生不息……

netif_rx調用netif_rx_schedule進一步處理數據包,我們注意到:
1、前面討論過,采用輪詢技術時,同樣地,也是調用netif_rx_schedule,把設備自己傳遞了過去;
2、這里,采用中斷方式,傳遞的是隊列中的一個“偽設備”,并且,這個偽設備的poll函數指針,指向了一個叫做process_backlog的函數;

netif_rx_schedule函數完成兩件重要的工作:
1、將bakclog_dev設備加入“處理數據包的設備”的鏈表當中;
2、觸發軟中斷函數,進行數據包接收處理;

這樣,我們可以猜想,在軟中斷函數中,不論是偽設備bakclog_dev,還是真實的設備(如前面討論過的e100),都會被軟中斷函數以:
dev->poll()
的形式調用,對于e100來說,poll函數的接收過程已經分析了,而對于其它所有沒有采用輪詢技術的網絡設備來說,它們將統統調用
process_backlog函數(我覺得把它改名為pseudo-poll是否更合適一些^o^)。

OK,我想分析到這里,關于中斷處理與輪詢技術的差異,已經基本分析開了……

繼續來看,netif_rx_schedule進一步調用__netif_rx_schedule:

[Copy to clipboard] CODE: /* Try to reschedule poll. Called by irq handler. */

static inline void netif_rx_schedule(struct net_device *dev)
{
? ? ? ? if (netif_rx_schedule_prep(dev))
? ? ? ? ? ? ? ? __netif_rx_schedule(dev);
}


[Copy to clipboard] CODE: /* Add interface to tail of rx poll list. This assumes that _prep has
? * already been called and returned 1.
? */

static inline void __netif_rx_schedule(struct net_device *dev)
{
? ? ? ? unsigned long flags;

? ? ? ? local_irq_save(flags);
? ? ? ? dev_hold(dev);
? ? ? ? /*偽設備也好,真實的設備也罷,都被加入了隊列層的設備列表*/
? ? ? ? list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
? ? ? ? if (dev->quota < 0)
? ? ? ? ? ? ? ? dev->quota += dev->weight;
? ? ? ? else
? ? ? ? ? ? ? ? dev->quota = dev->weight;
? ? ? ? /*觸發軟中斷*/
? ? ? ? __raise_softirq_irqoff(NET_RX_SOFTIRQ);
? ? ? ? local_irq_restore(flags);
}
軟中斷被觸發,注冊的net_rx_action函數將被調用:

[Copy to clipboard] CODE: /*接收的軟中斷處理函數*/
static void net_rx_action(struct softirq_action *h)
{
? ? ? ? struct softnet_data *queue = &__get_cpu_var(softnet_data);
? ? ? ? unsigned long start_time = jiffies;
? ? ? ? int budget = netdev_max_backlog;

? ? ? ?
? ? ? ? local_irq_disable();
? ? ? ?
? ? ? ? /*
? ? ? ???* 遍歷隊列的設備鏈表,如前所述,__netif_rx_schedule已經執行了
? ? ? ???* list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
? ? ? ???* 設備bakclog_dev已經被添加進來了
? ? ? ???*/
? ? ? ? while (!list_empty(&queue->poll_list)) {
? ? ? ? ? ? ? ? struct net_device *dev;

? ? ? ? ? ? ? ? if (budget <= 0 || jiffies - start_time > 1)
? ? ? ? ? ? ? ? ? ? ? ? goto softnet_break;

? ? ? ? ? ? ? ? local_irq_enable();
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? /*取得鏈表中的設備*/
? ? ? ? ? ? ? ? dev = list_entry(queue->poll_list.next,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???struct net_device, poll_list);
? ? ? ? ? ? ? ? netpoll_poll_lock(dev);

? ? ? ? ? ? ? ? /*調用設備的poll函數,處理接收數據包,這樣,采用輪詢技術的網卡,它的真實的poll函數將被調用,
? ? ? ? ? ? ? ? 這就回到我們上一節討論的e100_poll函數去了,而對于采用傳統中斷處理的設備,它們調用的,都將是
? ? ? ? ? ? ? ? bakclog_dev的process_backlog函數*/
? ? ? ? ? ? ? ? if (dev->quota <= 0 || dev->poll(dev, &budget)) {
? ? ? ? ? ? ? ? ? ? ? ? netpoll_poll_unlock(dev);
? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? /*處理完成后,把設備從設備鏈表中刪除,又重置于末尾*/
? ? ? ? ? ? ? ? ? ? ? ? local_irq_disable();
? ? ? ? ? ? ? ? ? ? ? ? list_del(&dev->poll_list);
? ? ? ? ? ? ? ? ? ? ? ? list_add_tail(&dev->poll_list, &queue->poll_list);
? ? ? ? ? ? ? ? ? ? ? ? if (dev->quota < 0)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dev->quota += dev->weight;
? ? ? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dev->quota = dev->weight;
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? netpoll_poll_unlock(dev);
? ? ? ? ? ? ? ? ? ? ? ? dev_put(dev);
? ? ? ? ? ? ? ? ? ? ? ? local_irq_disable();
? ? ? ? ? ? ? ? }
? ? ? ? }
out:
? ? ? ? local_irq_enable();
? ? ? ? return;

softnet_break:
? ? ? ? __get_cpu_var(netdev_rx_stat).time_squeeze++;
? ? ? ? __raise_softirq_irqoff(NET_RX_SOFTIRQ);
? ? ? ? goto out;
}
對于dev->poll(dev, &budget)的調用,一個真實的poll函數的例子,我們已經分析過了,現在來看process_backlog,

[Copy to clipboard] CODE: static int process_backlog(struct net_device *backlog_dev, int *budget)
{
? ? ? ? int work = 0;
? ? ? ? int quota = min(backlog_dev->quota, *budget);
? ? ? ? struct softnet_data *queue = &__get_cpu_var(softnet_data);
? ? ? ? unsigned long start_time = jiffies;

? ? ? ? backlog_dev->weight = weight_p;
? ? ? ?
? ? ? ? /*在這個循環中,執行出隊操作,把數據從隊列中取出來,交給netif_receive_skb,直至隊列為空*/
? ? ? ? for (;;) {
? ? ? ? ? ? ? ? struct sk_buff *skb;
? ? ? ? ? ? ? ? struct net_device *dev;

? ? ? ? ? ? ? ? local_irq_disable();
? ? ? ? ? ? ? ? skb = __skb_dequeue(&queue->input_pkt_queue);
? ? ? ? ? ? ? ? if (!skb)
? ? ? ? ? ? ? ? ? ? ? ? goto job_done;
? ? ? ? ? ? ? ? local_irq_enable();

? ? ? ? ? ? ? ? dev = skb->dev;

? ? ? ? ? ? ? ? netif_receive_skb(skb);

? ? ? ? ? ? ? ? dev_put(dev);

? ? ? ? ? ? ? ? work++;

? ? ? ? ? ? ? ? if (work >= quota || jiffies - start_time > 1)
? ? ? ? ? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? ? ? backlog_dev->quota -= work;
? ? ? ? *budget -= work;
? ? ? ? return -1;

/*當隊列中的數據包被全部處理后,將執行到這里*/
job_done:
? ? ? ? backlog_dev->quota -= work;
? ? ? ? *budget -= work;

? ? ? ? list_del(&backlog_dev->poll_list);
? ? ? ? smp_mb__before_clear_bit();
? ? ? ? netif_poll_enable(backlog_dev);

? ? ? ? if (queue->throttle)
? ? ? ? ? ? ? ? queue->throttle = 0;
? ? ? ? local_irq_enable();
? ? ? ? return 0;
}
這個函數重要的工作,就是出隊,然后調用netif_receive_skb()將數據包交給上層,這與上一節討論的poll是一樣的。這也是為什么,
在網卡驅動的編寫中,采用中斷技術,要調用netif_rx,而采用輪詢技術,要調用netif_receive_skb啦!

到了這里,就處理完數據包與設備相關的部分了,數據包將進入上層協議棧……?

總結

以上是生活随笔為你收集整理的Linux TCP/IP协议栈笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。