<<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的詳細知識將會在分析中斷處理的時候,一一為你道來
二:從網卡驅動說起。
以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");