linux網卡驅動源碼分析
轉自https://blog.csdn.net/ustc_dylan/article/details/6329375
網絡驅動是一種典型的PCI設備驅動,無論在嵌入式平台還是在PC領域,網絡相關的項目開發有著比較廣闊的前景,因此,分析當前Linux內核中網絡設備的驅動,不但能了解網絡相關的基本原理,而且可以借鑒Linux內核的先進的技術,將其應用到嵌入式或其他領域。本文以Linux內核中的rtl8139網絡驅動為例,對網絡驅動的源碼進行了簡單分析,並對其中涉及的相關概念和技術進行了簡單的介紹。
一、PCI設備驅動模型
rtl8139是典型的PCI設備,Linux內核的PCI核心驅動為PCI驅動開發者提供了方便的係統接口,極大地方便了PCI設備驅動的開發。
1 pci設備驅動相關的數據結構
- pci驅動描述結構體
- struct pci_driver {
- struct list_head node; /*用於連接入pci驅動列表*/
- char *name; /*pci驅動的名稱*/
- const struct pci_device_id *id_table; /* must be non-NULL for probe to be called *//*該驅動支持的pci設備*/
- 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 (*suspend_late) (struct pci_dev *dev, pm_message_t state);
- int (*resume_early) (struct pci_dev *dev);
- int (*resume) (struct pci_dev *dev); /* Device woken up */
- void (*shutdown) (struct pci_dev *dev);
- struct pci_error_handlers *err_handler;
- struct device_driver driver;
- struct pci_dynids dynids;
- };
pci設備描述結構體
- struct pci_dev{
- struct list_head bus_list; /* node in per-bus list */
- struct pci_bus *bus; /* bus this device is on */
- struct pci_bus *subordinate; /* bus this device bridges to */
- void *sysdata; /* hook for sys-specific extension */
- struct proc_dir_entry *procent; /* device entry in /proc/bus/pci */
- struct pci_slot *slot; /* Physical slot this device is in */
- unsigned int devfn; /* encoded device & function index */
- unsigned short vendor;
- unsigned short device;
- unsigned short subsystem_vendor;
- unsigned short subsystem_device;
- unsigned int class; /* 3 bytes: (base,sub,prog-if) */
- u8 revision; /* PCI revision, low byte of class word */
- u8 hdr_type; /* PCI header type (`multi' flag masked out) */
- u8 pcie_cap; /* PCI-E capability offset */
- u8 pcie_type; /* PCI-E device/port type */
- u8 rom_base_reg; /* which config register controls the ROM */
- u8 pin; /* which interrupt pin this device uses */
- struct pci_driver *driver; /* which driver has allocated this device */
- u64 dma_mask; /* Mask of the bits of bus address this
- device implements. Normally this is
- 0xffffffff. You only need to change
- this if your device has broken DMA
- or supports 64-bit transfers. */
- struct device_dma_parameters dma_parms;
- pci_power_t current_state; /* Current operating state. In ACPI-speak,
- this is D0-D3, D0 being fully functional,
- and D3 being off. */
- int pm_cap; /* PM capability offset in the
- configuration space */
- unsigned int pme_support:5; /* Bitmask of states from which PME#
- can be generated */
- unsigned int pme_interrupt:1;
- unsigned int d1_support:1; /* Low power state D1 is supported */
- unsigned int d2_support:1; /* Low power state D2 is supported */
- unsigned int no_d1d2:1; /* Only allow D0 and D3 */
- unsigned int wakeup_prepared:1;
- unsigned int d3_delay; /* D3->D0 transition time in ms */
- #ifdef CONFIG_PCIEASPM
- struct pcie_link_state *link_state; /* ASPM link state. */
- #endif
- pci_channel_state_t error_state; /* current connectivity state */
- struct device dev; /* Generic device interface */
- int cfg_size; /* Size of configuration space */
- /*
- * Instead of touching interrupt line and base address registers
- * directly, use the values stored here. They might be
- */
- unsigned int irq;
- struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */
- resource_size_t fw_addr[DEVICE_COUNT_RESOURCE]; /* FW-assigned addr */
- /* These fields are used by common fixups */
- unsigned int transparent:1; /* Transparent PCI bridge */
- unsigned int multifunction:1;/* Part of multi-function device */
- /* keep track of device state */
- unsigned int is_added:1;
- unsigned int is_busmaster:1; /* device is busmaster */
- unsigned int no_msi:1; /* device may not use msi */
- unsigned int block_ucfg_access:1; /* userspace config space access is blocked */
- unsigned int broken_parity_status:1; /* Device generates false positive parity */
- unsigned int irq_reroute_variant:2; /* device needs IRQ rerouting variant */
- unsigned int msi_enabled:1;
- unsigned int msix_enabled:1;
- unsigned int ari_enabled:1; /* ARI forwarding */
- unsigned int is_managed:1;
- unsigned int is_pcie:1; /* Obsolete. Will be removed.
- Use pci_is_pcie() instead */
- unsigned int needs_freset:1; /* Dev requires fundamental reset */
- unsigned int state_saved:1;
- unsigned int is_physfn:1;
- unsigned int is_virtfn:1;
- unsigned int reset_fn:1;
- unsigned int is_hotplug_bridge:1;
- unsigned int __aer_firmware_first_valid:1;
- unsigned int __aer_firmware_first:1;
- pci_dev_flags_t dev_flags;
- atomic_t enable_cnt; /* pci_enable_device has been called */
- u32 saved_config_space[16]; /* config space saved at suspend time */
- struct hlist_head saved_cap_space;
- struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
- int rom_attr_enabled; /* has display of the rom attribute been enabled? */
- struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
- struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */
- #ifdef CONFIG_PCI_MSI
- struct list_head msi_list;
- #endif
- struct pci_vpd *vpd;
- #ifdef CONFIG_PCI_IOV
- union {
- struct pci_sriov *sriov; /* SR-IOV capability related */
- struct pci_dev *physfn; /* the PF this VF is associated with */
- };
- struct pci_ats *ats; /* Address Translation Service */
- #endif
- }
驅動開發者要想為某個PCI設備開發驅動就必須定義一個與當前PCI設備相對應的pci_driver數據結構,用來描述將要開發的pci驅動的相關信息,比如驅動的名稱,當前驅動可以支持哪些設備,以及當前驅動支持的一些操作等,類似地,還需要有個結構體來表示PCI設備,描述PCI設備的硬件信息,如廠商ID,設備ID,以及各種資源等,詳見注釋。
二、PCI核心驅動API
Linux內核的PCI驅動為PCI設備驅動的開發提供了方便的結構,下麵列舉幾個常用的接口:
pci_register_driver(struct pci_driver *drv)
功能:注冊PCI驅動,參數為要注冊的pci驅動的結構體。
下麵來詳細的分析以下這個函數,如此,才能更清楚的了解驅動和設備的匹配過程。
- pci_register_driver->driver_register(&drv->driver);->bus_add_driver->driver_attach->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
在這個過程中有涉及到一個更為抽象的結構體struct device_driver,它是pci_driver的更高級的抽象,即下層是pci_driver,其上是device_driver,這符合通常的程序設計邏輯,越往上層抽象級別越高,因為在操作係統看來,它並不需要知道具體是什麼設備,所有的設備對操作係統來說都是相同的,即都用struct device_driver來表示。
在driver_register中先調用driver_find(drv->name, drv->bus),首先在相應的總線上查找drv->name的驅動是否已經被注冊過,如果被注冊過則返回,否則進行注冊過程,即調用bus_add_driver(drv)。
int bus_add_driver(struct device_driver *drv)函數首先判斷當前總線是否支持自動探測,如果執行則執行探測函數driver_attach(drv)。
- if (drv->bus->p->drivers_autoprobe) {
- error = driver_attach(drv);
- if (error)
- goto out_unregister;
- }int driver_attach(struct device_driver *drv)
- {
- return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
- }
這個函數對PCI總線的上所有已經連接的PCI設備與當前的PCI驅動進程一次匹配的過程,即對每一個PCI設備都調用匹配函數__driver_attach。
- static int __driver_attach(struct device *dev, void *data)
- {
- struct device_driver *drv = data;
- /*
- * Lock device and try to bind to it. We drop the error
- * here and always return 0, because we need to keep trying
- * to bind to devices and some drivers will return an error
- * simply if it didn't support the device.
- *
- * driver_probe_device() will spit a warning if there
- * is an error.
- */
- if (!driver_match_device(drv, dev))
- return 0;
- if (dev->parent) /* Needed for USB */
- device_lock(dev->parent);
- device_lock(dev);
- if (!dev->driver)
- driver_probe_device(drv, dev);
- device_unlock(dev);
- if (dev->parent)
- device_unlock(dev->parent);
- return 0;
- }
該函數首先判斷總線提供的match函數是否為空,如果非空則執行總線提供的match函數,在rtl8139網絡驅動中,match非空,參見代碼:
- drv->driver.bus = &pci_bus_type;struct bus_type pci_bus_type = {
- .name = "pci",
- .match = pci_bus_match,
- .uevent = pci_uevent,
- .probe = pci_device_probe,
- .remove = pci_device_remove,
- .shutdown = pci_device_shutdown,
- .dev_attrs = pci_dev_attrs,
- .bus_attrs = pci_bus_attrs,
- .pm = PCI_PM_OPS_PTR,
- };
這裏將match函數即pci_bus_match,最後該函數調用到
- static inline const struct pci_device_id *
- pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
- {
- if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
- (id->device == PCI_ANY_ID || id->device == dev->device) &&
- (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
- (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
- !((id->class ^ dev->class) & id->class_mask))
- return id;
- return NULL;
- }
在這裏進行了pci_driver和pci_dev的匹配,如果匹配成功,則返回pci_device_id。如果匹配不成功,而且當前設備還沒有驅動,則調用driver_probe_device(drv,dev)。
- int driver_probe_device(struct device_driver *drv, struct device *dev)
- {
- int ret = 0;
- if (!device_is_registered(dev))
- return -ENODEV;
- pr_debug("bus: '%s': %s: matched device %s with driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
執行到這裏說明,說明PCI總線沒有提供match函數或者總線提供的match函數返回非空。還需要進行更深層次的探測,至少在總線提供的match函數中僅僅是進行了匹配,並沒有將驅動和設備關聯起來,這些操作就是在下麵的函數中實現的。
- int driver_probe_device(struct device_driver *drv, struct device *dev)
- {
- int ret = 0;
- if (!device_is_registered(dev))
- return -ENODEV;
- pr_debug("bus: '%s': %s: matched device %s with driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- pm_runtime_get_noresume(dev);
- pm_runtime_barrier(dev);
- ret = really_probe(dev, drv);
- pm_runtime_put_sync(dev);
- return ret;
- }
重點看really_probe函數:
- static int really_probe(struct device *dev, struct device_driver *drv)
- {
- int ret = 0;
- atomic_inc(&probe_count);
- pr_debug("bus: '%s': %s: probing driver %s with device %s/n",
- drv->bus->name, __func__, drv->name, dev_name(dev));
- WARN_ON(!list_empty(&dev->devres_head));
- dev->driver = drv;
- if (driver_sysfs_add(dev)) {
- printk(KERN_ERR "%s: driver_sysfs_add(%s) failed/n",
- __func__, dev_name(dev));
- goto probe_failed;
- }
- if (dev->bus->probe) {
- ret = dev->bus->probe(dev);
- if (ret)
- goto probe_failed;
- } else if (drv->probe) {
- ret = drv->probe(dev);
- if (ret)
- goto probe_failed;
- }
- driver_bound(dev);
- ret = 1;
- pr_debug("bus: '%s': %s: bound device %s to driver %s/n",
- drv->bus->name, __func__, dev_name(dev), drv->name);
- goto done;
- probe_failed:
- devres_release_all(dev);
- driver_sysfs_remove(dev);
- dev->driver = NULL;
- if (ret != -ENODEV && ret != -ENXIO) {
- /* driver matched but the probe failed */
- printk(KERN_WARNING
- "%s: probe of %s failed with error %d/n",
- drv->name, dev_name(dev), ret);
- }
- /*
- * Ignore errors returned by ->probe so that the next driver can try
- * its luck.
- */
- ret = 0;
- done:
- atomic_dec(&probe_count);
- wake_up(&probe_waitqueue);
- return ret;
- }
在此函數中,首先將驅動和設備關聯起來,即紅色代碼dev->driver = drv; 指明了當前設備的驅動。按照常規的程序設計思想,驅動和設備關聯後是否還需要做一些其他工作才能是設備在相應的驅動下正常工作呢,這就是probe函數實現的功能了,很明顯設備和驅動獲取都需要做一些工作,因此這裏分別留出設備和驅動的probe函數。其中設備的probe即設備所在總線的probe,這裏暫且不去分析,因為與網絡驅動關係不大,都是PCI總線相關的東西,重點來看驅動的probe,在前麵提到的pci_driver結構體中,對於rtl8139驅動來說,其pci_driver結構體被初始化為:
- static struct pci_driver rtl8139_pci_driver = {
- .name = DRV_NAME,
- .id_table = rtl8139_pci_tbl,
- .probe = rtl8139_init_one,
- .remove = __devexit_p(rtl8139_remove_one),
- #ifdef CONFIG_PM
- .suspend = rtl8139_suspend,
- .resume = rtl8139_resume,
- #endif /* CONFIG_PM */
- };
這裏即調用rtl8139_init_one,經過上麵的逐層分析,我們從pci核心驅動一步一步的走到了rtl8139網絡設備的驅動,豁然開朗了,以後看網絡驅動的時候就不會感到開始的地方有點迷煳了。代碼分析重在代碼之間的過渡,如果銜接不好,很多地方都會產生疑問。
上次講到如何從pci核心驅動一步一步的進入了rtl8139網絡驅動,並且調用的第一個函數是驅動的probe函數,即rtl8139_init_one,本文就從這裏入手,簡單的介紹rtl8139網絡驅動的相關原理和源碼分析。
1 rtl8139_init_one
上文講到當實現了驅動和設備的匹配後,需要設備和驅動做一些相應的工作,如正常使用前的初始化操作等,rtl8139_init_one就實現了一些初始化操作,原則上probe函數應該盡可能的短,盡量避免執行耗時的操作。rtl8139_init_one僅僅實現了兩個結構體struct net_device和struct rtl8139_private的初始化。前一篇文章中也提到了數據結構的抽象層次的問題,在網絡子係統中,所有的網絡設備都用net_device來表示,但是並不是所有的網絡設備都有相同的屬性,因此,對應不同的網絡設備增加一個private數據結構來描述,這裏就是struct rtl8139_private。
rtl8139_init_one主要函數和功能分析
(1)dev = rtl8139_init_board (pdev);
- /* dev and priv zeroed in alloc_etherdev */
- dev = alloc_etherdev (sizeof (*tp));
- if (dev == NULL) {
- dev_err(&pdev->dev, "Unable to alloc new net device/n");
- return ERR_PTR(-ENOMEM);
- }
- SET_NETDEV_DEV(dev, &pdev->dev);
- tp = netdev_priv(dev);
- tp->pci_dev = pdev;
- /* enable device (incl. PCI PM wakeup and hotplug setup) */
- rc = pci_enable_device (pdev);
- if (rc)
- goto err_out;
- pio_start = pci_resource_start (pdev, 0);
- pio_end = pci_resource_end (pdev, 0);
- pio_flags = pci_resource_flags (pdev, 0);
- pio_len = pci_resource_len (pdev, 0);
- mmio_start = pci_resource_start (pdev, 1);
- mmio_end = pci_resource_end (pdev, 1);
- mmio_flags = pci_resource_flags (pdev, 1);
- mmio_len = pci_resource_len (pdev, 1);
- ... ...
- rc = pci_request_regions (pdev, DRV_NAME);
a). dev = alloc_etherdev (sizeof (*tp)); --> 分配struct rtl8139_private數據結構,並進行預初始化,之所以稱之為預初始化是因為隻進行了某些固定數據成員的初始化。
b). 調用pci核心驅動的接口函數:pci_enable_device (),pci_enable_device 也是一個內核開發出來的接口,代碼在drivers/pci/pci.c中,筆者跟蹤發現這個函數主要就是把PCI配置空間的Command域的0位和1 位置成了1,從而達到了開啟設備的目的,因為rtl8139的官方datasheet中,說明了這兩位的作用就是開啟內存映射和I/O映射,如果不開的話,那我們以上討論的把控製寄存器空間映射到內存空間的這一功能就被屏蔽了。
pci_resource_[start|end|flags|len]:在硬件加電初始化時,BIOS固件統一檢查了所有的PCI設備,並統一為他們分配了一個和其他互不衝突的地址,讓他們的驅動程序可以向這些地址映射他們的寄存器,這些地址被BIOS寫進了各個設備的配置空間,因為這個活動是一個PCI的標準的活動,所以自然寫到各個設備的配置空間裏而不是他們風格各異的控製寄存器空間裏。當然隻有BIOS可以訪問配置空間。當操作係統初始化時,他為每個PCI設備分配了pci_dev結構,並且把BIOS獲得的並寫到了配置空間中的地址讀出來寫到了pci_dev中的resource字段中。這樣以後我們在讀這些地址就不需要在訪問配置空間了,直接跟pci_dev要就可以了,我們這裏的四個函數就是直接從pci_dev讀出了相關數據,代碼在include/linux/pci.h中。具體參見PCI配置空間相關的介紹。
c). rc = pci_request_regions (pdev, DRV_NAME);通知內核該設備對應的IO端口和內存資源已經使用,其他的PCI設備不要再使用這個區域
d). 獲得當前pci設備對應的IO端口和IO內存的基址。
2. rtl8139_open
此函數在網絡設備端口被打開時調用,例如執行命令ifconfig eth0 up,就會觸發這個函數,此函數是真正的rtl8139網絡設備的初始化函數。這個函數主要做了三件事。
① 注冊這個設備的中斷處理函數。
- retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
當網卡發送數據完成或者接收到數據時,是用中斷的形式來告知的,比如有數據從網線傳來,中斷也通知了我們,那麼必須要有一個處理這個中斷的函數來完成數據的接收。關於Linux的中斷機製不是我們詳細講解的範疇,但是有個非常重要的資源我們必須注意,那就是中斷號的分配,和內存地址映射一樣,中斷號也是BIOS在初始化階段分配並寫入設備的配置空間的,然後Linux在建立 pci_dev時從配置空間讀出這個中斷號然後寫入pci_dev的irq成員中,所以我們注冊中斷程序需要中斷號就是直接從pci_dev裏取就可以了。
retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);
if (retval) {
return retval;
}
我們注冊的中斷處理函數是rtl8139_interrupt,也就是說當網卡發生中斷(如數據到達)時,中斷控製器8259A把中斷號發給CPU,CPU 根據這個中斷號找到處理程序,這裏就是rtl8139_interrupt,然後執行。rtl8139_interrupt也是在我們的程序中定義好了的,這是驅動程序的一個重要的義務,也是一個基本的功能。request_irq的代碼在arch/i386/kernel/irq.c中。
②分配發送和接收的緩存空間
根據官方文檔,發送一個數據包的過程是這樣的:先從應用程序中把數據包拷貝到一段連續的內存中(這段內存就是我們這裏要分配的緩存),然後把這段內存的地址寫進網卡的數據發送地址寄存器(TSAD)中,這個寄存器的偏移量是TxAddr0 = 0x20。在把這個數據包的長度寫進另一個寄存器(TSD)中,它的偏移量是TxStatus0 = 0x10。然後就把這段內存的數據發送到網卡內部的發送緩衝中(FIFO),最後由這個發送緩衝區把數據發送到網線上。
好了現在創建這麼一個發送和接收緩衝內存的目的已經很顯然了。
- tp->tx_bufs = dma_alloc_coherent(&tp->pci_dev->dev, TX_BUF_TOT_LEN,
- &tp->tx_bufs_dma, GFP_KERNEL);
- tp->rx_ring = dma_alloc_coherent(&tp->pci_dev->dev, RX_BUF_TOT_LEN,
- &tp->rx_ring_dma, GFP_KERNEL);
tp 是net_device的priv的指針,tx_bufs是發送緩衝內存的首地址,rx_ring是接收緩存內存的首地址,他們都是虛擬地址,而最後一個參數tx_bufs_dma和rx_ring_dma均是這一段內存的物理地址。為什麼同一個事物,既用虛擬地址來表示它還要用物理地址呢,是這樣的, CPU執行程序用到這個地址時,用虛擬地址,而網卡設備通過DMA操作向這些內存中存取數據時用的是物理地址(因為網卡相對CPU屬於頭腦比較簡單型的)。 pci_alloc_consistent的代碼在Linux/arch/i386/kernel/pci-dma.c中。
③發送和接收緩衝區初始化和網卡開始工作的操作
RTL8139有4個發送描述符(包括4個發送緩衝區的基地址寄存器(TSAD0-TSAD3)和4個發送狀態寄存器(TSD0-TSD3)。也就是說我們分配的緩衝區要分成四個等分並把這四個空間的地址都寫到相關寄存器裏去,下麵這段代碼完成了這個操作。
- /* Initialize the Rx and Tx rings, along with various 'dev' bits. */
- static void rtl8139_init_ring (struct net_device *dev)
- {
- struct rtl8139_private *tp = netdev_priv(dev);
- int i;
- tp->cur_rx = 0;
- tp->cur_tx = 0;
- tp->dirty_tx = 0;
- for (i = 0; i < NUM_TX_DESC; i++)
- tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];
- }
上麵這段代碼負責把發送緩衝區虛擬空間進行了分割。
- /* init Tx buffer DMA addresses */
- for (i = 0; i < NUM_TX_DESC; i++)
- RTL_W32_F (TxAddr0 + (i * 4), tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs));
上麵這段代碼負責把發送緩衝區物理空間進行了分割,並把它寫到了相關寄存器中,這樣在網卡開始工作後就能夠迅速定位和找到這些內存並存取他們的數據。
- /* init Rx ring buffer DMA address */
- RTL_W32_F (RxBuf, tp->rx_ring_dma);
上麵這行代碼是把接收緩衝區的物理地址寫到了相關寄存器中,這樣網卡接收到數據後就能準確的把數據從網卡中搬運到這些內存空間中,等待CPU來領走他們。
/* make sure RxTx has started */
tmp = RTL_R8 (ChipCmd);
if ((!(tmp & CmdRxEnb)) || (!(tmp & CmdTxEnb)))
RTL_W8 (ChipCmd, CmdRxEnb | CmdTxEnb);
重新RESET設備後,我們要激活設備的發送和接收的功能,上麵這行代碼就是向相關寄存器中寫入相應值,激活了設備的這些功能。
static const unsigned int rtl8139_tx_config =
TxIFG96 | (TX_DMA_BURST << TxDMAShift) | (TX_RETRY << TxRetryShift);RTL_W32 (TxConfig, rtl8139_tx_config);
上麵這行代碼是向網卡的TxConfig(位移是0x44)寄存器中寫入TX_DMA_BURST << TxDMAShift這個值,翻譯過來就是6<<8,就是把第8到第10這三位置成110,查閱管法文檔發現6就是110代表著一次DMA的數據量為1024字節。
3. 網絡數據包的收發過程
當一個網絡應用程序要向網絡發送數據時,它要利用Linux的網絡協議棧來解決一係列問題,找到網卡設備的代表net_device,由這個結構來找到並控製這個網卡設備來完成數據包的發送,具體是調用net_device的hard_start_xmit成員函數,這是一個函數指針,在我們的驅動程序裏它指向的是rtl8139_start_xmit,正是由它來完成我們的發送工作的,下麵我們就來剖析這個函數。它一共做了四件事。
①檢查這個要發送的數據包的長度,如果它達不到以太網幀的長度,必須采取措施進行填充。
- /* Calculate the next Tx descriptor entry. */
- entry = tp->cur_tx % NUM_TX_DESC;
- /* Note: the chip doesn't have auto-pad! */
- if (likely(len < TX_BUF_SIZE)) { //TX_BUF_SIZE = 1536
- if (len < ETH_ZLEN) //ETH_ZLEN = 60
- memset(tp->tx_buf[entry], 0, ETH_ZLEN);
- skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);
- dev_kfree_skb(skb);
- } else {
- dev_kfree_skb(skb);
- dev->stats.tx_dropped++;
- return NETDEV_TX_OK;
- }
②把包的數據拷貝到我們已經建立好的發送緩存中。主要實現了把skb結構中的數據拷貝到tp->tx_buf[entry]指向的發送緩衝區中。
- void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
- {
- __wsum csum;
- long csstart;
- /*首先計算skb->data的長度*/
- if (skb->ip_summed == CHECKSUM_PARTIAL)
- csstart = skb->csum_start - skb_headroom(skb);
- else
- csstart = skb_headlen(skb);
- BUG_ON(csstart > skb_headlen(skb));
- skb_copy_from_linear_data(skb, to, csstart);
- csum = 0;
- if (csstart != skb->len)
- csum = skb_copy_and_csum_bits(skb, csstart, to + csstart,
- skb->len - csstart, 0);
- if (skb->ip_summed == CHECKSUM_PARTIAL) {
- long csstuff = csstart + skb->csum_offset;
- *((__sum16 *)(to + csstuff)) = csum_fold(csum);
- }
- }
在拷貝函數中需要注意幾個問題:
a. 如何計算要拷貝的skb的數據的長度,即這裏的csstart的計算,這裏參考下麵的公式:
If skb is linear (i.e., skb->data_len == 0), the length of skb->data is skb->len.
If skb is not linear (i.e., skb->data_len != 0), the length of skb->data is (skb->len) - (skb->data_len) for the head ONLY. The rest must see struct skb_shared_info->frags[i].size and struct skb_shared_info->frag_list, which contains a linked-list of struct sk_buff because, deducing from [2],
skb->data_len = struct skb_shared_info->frags[0...struct skb_shared_info->nr_frags].size + size of data in struct skb_shared_info->frag_listThe rest of the data is not stored as a separate skb if the length of the data permits, but as an array of struct skb_frag_struct in struct skb_shared_info ([4]: To allow 64K frame to be packed as single skb without frag_list). struct skb_frag_struct contains struct page * to point to the true data. If the length of the data is longer than that that can be contained in the array, struct skb_shared_info->frag_list will be used to contain a linked-list of struct sk_buff (i.e., the data undergo fragmentation because, according to [1], the frag_list is used to maintain a chain of SKBs organized for fragmentation purposes, it is not used for maintaining paged data.)
As an additional information, skb->truesize = skb->len + sizeof(struct sk_buff). Don't forget that skb->len contains the length of the total data space that the skb refers to taking into account SKB_DATA_ALIGN() and non-linear condition.
skb->len is modified when doing skb_pull(), skb_push() or skb_put().
③光有了地址和數據還不行,我們要讓網卡知道這個包的長度,才能保證數據不多不少精確的從緩存中截取出來搬運到網卡中去,這是靠寫發送狀態寄存器(TSD)來完成的。
- RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
- tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
我們把這個包的長度和一些控製信息一起寫進了狀態寄存器,使網卡的工作有了依據。
④判斷發送緩存是否已經滿了,如果滿了在發就覆蓋數據了,要停發。
- if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
- netif_stop_queue (dev);
談完了發送,我們開始談接收,當有數據從網線上過來時,網卡產生一個中斷,調用的中斷服務程序是rtl8139_interrupt,它主要做了三件事。
①從網卡的中斷狀態寄存器中讀出狀態值進行分析,status = RTL_R16 (IntrStatus);if ((status &(PCIErr | PCSTimeout | RxUnderrun | RxOverflow |
RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0)
goto out;
上麵代碼說明如果上麵這9種情況均沒有的表示沒什麼好處理的了,退出。
② NAPI接收機製
- /* Receive packets are processed by poll routine.
- If not running start it now. */
- if (status & RxAckBits){
- if (napi_schedule_prep(&tp->napi)) {
- RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
- __napi_schedule(&tp->napi);
- }
- }
napi_schedule_prep(&tp->napi)判斷以下當前驅動是否支持NAPI或者NAPI需要的前提條件是否滿足,如果滿足,設置中斷屏蔽字,屏蔽之後產生的中斷,然後激活一個軟中斷,具體代碼如下:(至於list_add_tail將會稍後分析)
- static inline void ____napi_schedule(struct softnet_data *sd,
- struct napi_struct *napi)
- {
- list_add_tail(&napi->poll_list, &sd->poll_list);
- __raise_softirq_irqoff(NET_RX_SOFTIRQ);
- }
在軟中斷注冊的輪詢函數中完成網絡數據包的接收操作。
③發送中斷處理