1. 程式人生 > >網路協議棧分析——從裝置驅動到鏈路層

網路協議棧分析——從裝置驅動到鏈路層

對應我們上面的網絡卡驅動分析。接收到的資料是存放在data至tail之間的區域。

Skb通常還有常用的幾個函式,一一列舉分析如下:

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)

分配儲存空間為sixe的skb,記憶體分配級別為gfp_mask.注意這裡的儲存空間的含義,即為skb->data至skb->tail的區域

struct sk_buff *skb_clone(struct sk_buff *skb, int priority)

克隆出的skb指向同一個結構,同時會增加skb的引用計數

struct sk_buff *skb_copy(const struct sk_buff *skb, int priority)

複製一個全新的skb

void kfree_skb(struct sk_buff *skb)

當skb的引用計數為1的時候,釋放此skb

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)

使skb的儲存空間擴大len.即使tail指標下移

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

push,即推出一段資料,使data指標下層。

void skb_reserve(struct sk_buff *skb, unsigned int len)

該操作使data指標跟tail指標同時下移,即擴大儲存區域之前的空間

int skb_headroom(const struct sk_buff *skb)

返回data之前可用的空間數量

int skb_tailroom(const struct sk_buff *skb)

返回快取區中可用的空間大小

二:從網絡卡驅動說起。

         以intel 100M 網絡卡驅動為例簡要概述資料包的接收與傳送流程。程式碼見(drivers/net/e100.c)

網絡卡是屬於PCI裝置,它的註冊跟一般的PCI設備註冊沒什麼兩樣。

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);

         }

         //註冊PCI

        return pci_module_init(&e100_driver);

}

其中e100_driver對應為網絡卡的pci_driver.

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

}

當總數探測到PCI裝置符合e100_id_table中的引數時,將會呼叫e100_probe,開始裝置的初始化

在e100_probe中:

static int __devinit e100_probe(struct pci_dev *pdev,

         const struct pci_device_id *ent)

{

         struct net_device *netdev;

         struct nic *nic;

         int err;

         //分配net_device併為其賦值

         //alloc_etherdev為乙太網介面的net_device分配函式。它是alloc_netdev的封裝函式

         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中的函式指標賦初值

         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;

         //支援ethtool工具時有效

         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

         //獲得net_device私有資料區,並對其賦值

         //私有資料大小是由alloc_etherdev()引數中指定的

         nic = netdev_priv(netdev);

         nic->netdev = netdev;

         nic->pdev = pdev;

         nic->msg_enable = (1 << debug) - 1;

         pci_set_drvdata(pdev, netdev);

         //啟動網絡卡.為之後DMA,I/O記憶體對映做準備

//它實際上是對PCI的控制暫存器賦值來實現的

         if((err = pci_enable_device(pdev))) {

                   DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting./n");

                   goto err_out_free_dev;

         }

         //獲取該資源相關聯的標誌

         //如果該裝置存在I/O記憶體,則置IORESOURCE_MEM

         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;

         }

         //對PCI的6個暫存器都會呼叫資源分配函式進行申請

         if((err = pci_request_regions(pdev, DRV_NAME))) {

                   DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting./n");

                   goto err_out_disable_pdev;

         }

         //探制裝置的DMA能力。如果裝置支援DMA。pci_set_dma_mask返回0

         pci_set_master(pdev);

         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;

         spin_lock_init(&nic->cb_lock);

         spin_lock_init(&nic->cmd_lock);

         //設定定時器。

         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;

         //為nic->mem建立線性DMA。只是在支援ethtool的時候才有用

if((err = e100_alloc(nic))) {

                   DPRINTK(PROBE, ERR, "Cannot alloc driver memory, aborting./n");

                   goto err_out_iounmap;

         }

         //對nic成員賦初值

         e100_get_defaults(nic);

         e100_hw_reset(nic);

         e100_phy_init(nic);

         //讀取網絡卡的EEPROM。其中存放著網絡卡的MAC地址。

         //對EEPROM是通過對I/O對映記憶體的操作實現的,即nic->csr

         if((err = e100_eeprom_load(nic)))

                   goto err_out_free;

         //設定netdev->dev_addr

         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;

         pci_enable_wake(pdev, 0, nic->flags & (wol_magic | e100_asf(nic)));

         //註冊網路裝置

         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;

}

<<prison break>>第三季的第五集,終於在翹首企盼中姍姍來遲了,scofid用它驚人的智慧一次次化險為夷,但在邪惡的sona監獄他將如何逃脫呢?這我們不得而知,但我們可以分析Linux網路驅動來得到資料包是怎麼通過物理介面的這一層“prison”束縛來達到通訊目的:-)

         一:預備知識

         關於I/O記憶體對映。

         裝置通過控制匯流排,資料匯流排,狀態匯流排與CPU相連。控制總數傳送控制訊號,例如,網絡卡的啟用。資料匯流排控制資料傳輸,例如,網絡卡傳送資料,狀態總數一般都是讀取裝置的當前狀態,例如讀取網絡卡的MAC地址。

         在傳統的操作中,都是通過讀寫裝置暫存器的值來實現。但是這樣耗費了CPU時鐘。而且每取一次值都要讀取裝置暫存器,造成了效率的低下。在現代作業系統中。引用了I/O記憶體對映。即把暫存器的值映身到主存。對裝置暫存器的操作,轉換為對主存的操作,這樣極大的提高了效率。

         關於DMA

         這是關於裝置資料處理的一種方式。傳統的處理方法為:當裝置接收到資料,向CPU報告中斷。CPU處理中斷,把資料放到記憶體。

         在現代作業系統中引入的DMA是指,裝置接收到資料時,把資料放至DMA記憶體,再向CPU產生中斷。這樣節省了大量的CPU時間

         關於軟中斷與NAPI

         在現代作業系統中,對中斷的處理速度要求越來越高。為了響應中斷,將中斷分為兩部份,即上半部與下半部。上半部將資料推入處理佇列,響應中斷。然後再由下半部排程完成餘下的任務。

         NAPI是2.6新引入的一個概念,它在發生中斷的時候,禁用中斷。然後處理資料。之後,每隔一定的時候,它會主動向裝置詢用是否有資料要處理。

         I/O,DMA在後續程式碼分析中會討論在linux2.6.21中的實現。軟中斷與NAPI的詳細知識將會在分析中斷處理的時候,一一為你道來