網路協議棧分析——從裝置驅動到鏈路層
對應我們上面的網絡卡驅動分析。接收到的資料是存放在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的詳細知識將會在分析中斷處理的時候,一一為你道來