Linux網絡卡驅動程式分析

2021-05-27 05:46:56 字數 4992 閱讀 8059

學習應該是乙個先把問題簡單化,再把問題複雜化的過程。一開始就著手處理複雜的問題,難免讓人有心驚膽顫,捉襟見肘的感覺。讀linux網絡卡驅動 也是一樣。那長長的原始碼夾雜著那些我們陌生的變數和符號,望而生畏便是理所當然的了。不要擔心,事情總有解決的辦法,先把一些我們管不著的**切割出去,留下必須的部分,把框架掌握了,那其他的事情自然就水到渠成了,這是筆者的心得。

一般在使用的linux網絡卡驅動**動輒3000行左右,這個**量以及它所表達出來的知識量無疑是龐大的,我們有沒有辦法縮短一下這個**量,使我們的學習變的簡單些呢?經過筆者的不懈努力,在仍然能夠使網路裝置正常工作的前提下,把它縮減到了600多行,我們把暫時還用不上的功能先割出去。這樣一來,事情就簡單多了,真的就剩下乙個框架了。

下面我們就來剖析這個可以執行的框架。

限於篇幅,以下分析用到的所有涉及到核心中的函式**,我都不予列出,但給出在哪個具體檔案中,請讀者自行查閱。

首先,我們來看看裝置的初始化。當我們正確編譯完我們的程式後,我們就需要把生成的目標檔案載入到核心中去,我們會先 ifconfig eth0 down和rmmod 8139too來解除安裝正在使用的網絡卡驅動,然後insmod 8139too.o把我們的驅動載入進去(其中8139too.o是我們編譯生成的目標檔案)。就像c程式有主函式main()一樣,模組也有第乙個執行的函式,即 module_init(rtl8139_init_module);在我們的程式中,rtl8139_init_module()在insmod之後首 先執行,它的**如下:

static int __init rtl8139_init_module (void)

return pci_module_init (&rtl8139_pci_driver);

它直接呼叫了pci_module_init(),這個函式**在linux/drivers/net/eepro100.c中,並且把 rtl8139_pci_driver(這個結構是在我們的驅動**裡定義的,它是驅動程式和pci裝置聯絡的紐帶)的位址作為引數傳給了它。 rtl8139_pci_driver定義如下:

static struct pci_driver rtl8139_pci_driver = ,

,那麼就說明這個驅動程式就是用來驅動這個裝置的,於是呼叫rtl8139_pci_driver中的probe函式即 rtl8139_init_one,這個函式是在我們的驅動程式中定義了的,它是用來初始化整個裝置和做一些準備工作。這裡需要注意一下 pci_device_id是核心定義的用來辨別不同pci裝置的乙個結構,例如在我們這裡0x10ec代表的是realtek公司,我們掃瞄pci裝置配置空間如果發現有realtek公司製造的裝置時,兩者就對上了。當然對上了公司號後還得看其他的裝置號什麼的,都對上了才說明這個驅動是可以為這個裝置服務的。

③是把這個rtl8139_pci_driver結構掛在這個裝置的資料結構(pci_dev)上,表示這個裝置從此就有了自己的驅動了。而驅動也找到了它服務的物件了。

pci是乙個匯流排標準,pci匯流排上的裝置就是pci裝置,這些裝置有很多態別,當然也包括網絡卡裝置,每乙個pci裝置在核心中抽象為乙個資料結構pci_dev,它描述了乙個pci裝置的所有的特性,具體請查詢相關文件,本文限於篇幅無法詳細描述。但是有幾個地方和驅動程式的關係特別大,必須予以說明。pci裝置都遵守pci標準,這個部分所有的pci裝置都是一樣的,每個pci裝置都有一段暫存器儲存著配置空間,這一部分格式是一樣的,比如第乙個暫存器總是生產商號碼,如realtek就是10ec,而intel則是另乙個數字,這些都是商家像標準組織申請的,是肯定不同的。我就可以通過配置空間來辨別其生產商,裝置號,不論你什麼平台,x86也好,ppc也好,他們都是同一的標準格式。當然光有這些pci配置空間的統一格式還是不夠的,比如 說人類,都有鼻子和眼睛,但並不是所有人的鼻子和眼睛都長的一樣的。網絡卡裝置是pci裝置必須遵守規則,在裝置裡整合了pci配置空間,但它是乙個網絡卡就必須同時整合能控制網絡卡工作的暫存器。而暫存器的訪問就成了乙個問題。在linux裡面我們是把這些暫存器對映到主存虛擬空間上的,換句話說我們的cpu 訪存指令就可以訪問到這些處於外設中的控制暫存器。總結一下,pci裝置主要包括兩類空間,乙個是配置空間,它是作業系統或bios控制外設的統一格式的空 間,cpu指令不能訪問,訪問這個空間要借助bios功能,事實上linux的訪問配置空間的函式是通過cpu指令驅使bios來完成讀寫訪問的。

而另一類是普通的控制暫存器空間,這一部分對映完後cpu可以訪問來控制裝置工作。

現在我們回到上面pci_register_driver的第二步,如果找到相關裝置和我們的pci_device_id結構陣列對上號了,說明我們找到服務物件了,則呼叫rtl8139_init_one,它主要做了七件事:

① 建立net_device結構,讓它在核心中代表這個網路裝置。但是讀者可能會問,pci_dev也是代表著這個裝置,那麼兩者有什麼區別 呢,正如我們上面討論的,網絡卡裝置既要遵循pci規範,也要擔負起其作為網絡卡裝置的職責,於是就分了兩塊,pci_dev用來負責網絡卡的pci規範,而這裡要說的net_device則是負責網絡卡的網路裝置這個職責。

dev = init_etherdev (null, sizeof (*tp));

if (dev == null)

else{

printk("%s:(skb->data+eth_zlen) > skb->end\n",__function__);

skb->data和skb->end就決定了這個包的內容,如果這個包本身總共的長度(skb->end- skb->data)都達不到要求,那麼想填也沒地方填,就出錯返回了,否則的話就填上。

②把包的資料拷貝到我們已經建立好的傳送快取中。

memcpy (tp->tx_buf[entry], skb->data, skb->len);

其中skb->data就是資料報資料的位址,而tp->tx_buf[entry]就是我們的傳送快取位址,這樣就完成了拷貝,忘記了這些內容的回頭看看前面的介紹。

③光有了位址和資料還不行,我們要讓網絡卡知道這個包的長度,才能保證資料不多不少精確的從快取中擷取出來搬運到網絡卡中去,這是靠寫傳送狀態暫存器(tsd)來完成的。

writel(tp->tx_flag | (skb->len >= eth_zlen ? skb->len : eth_zlen),ioaddr+txstatus0+(entry * 4));

我們把這個包的長度和一些控制資訊一起寫進了狀態暫存器,使網絡卡的工作有了依據。

④判斷傳送快取是否已經滿了,如果滿了在發就覆蓋資料了,要停發。

if ((tp->cur_tx - num_tx_desc) == tp->dirty_tx)

netif_stop_queue (dev);

談完了傳送,我們開始談接收,當有資料從網線上過來時,網絡卡產生乙個中斷,呼叫的中斷服務程式是rtl8139_interrupt,它主要做了三件事。

①從網絡卡的中斷狀態暫存器中讀出狀態值進行分析,status = readw(ioaddr+intrstatus);

if ((status &(pcierr | pcstimeout | rxunderrun | rxoverflow | rxfifoover | txerr | txok | rxerr | rxok)) == 0)

goto out;

上面**說明如果上面這9種情況均沒有的表示沒什麼好處理的了,退出。

② if (status & (rxok | rxunderrun | rxoverflow | rxfifoover))/* rx interrupt */

rtl8139_rx_interrupt (dev, tp, ioaddr);

如果是以上4種情況,屬於接收訊號,呼叫rtl8139_rx_interrupt進行接收處理。

③ if (status & (txok | txerr)) {

spin_lock (&tp->lock);

rtl8139_tx_interrupt (dev, tp, ioaddr);

spin_unlock (&tp->lock);

如果是傳輸完成的訊號,就呼叫rtl8139_tx_interrupt進行傳送善後處理。

下面我們先來看看接收中斷處理函式rtl8139_rx_interrupt,在這個函式中主要做了下面四件事

①這個函式是乙個大迴圈,迴圈條件是只要接收快取不為空就還可以繼續讀取資料,迴圈不會停止,讀空了之後就跳出。

int ring_offset = cur_rx % rx_buf_len;

rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));

rx_size = rx_status >> 16;

上面三行**是計算出要接收的包的長度。

②根據這個長度來分配包的資料結構

skb = dev_alloc_skb (pkt_size + 2);

③如果分配成功就把資料從接收快取中拷貝到這個包中

eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);

這個函式在include/linux/etherdevice.h中,實質還是呼叫了memcpy()。

static inline void eth_copy_and_sum(struct sk_buff*dest, unsigned char *src, int len, int base)

memcpy(dest->data, src, len);

現在我們已經熟知,&rx_ring[ring_offset + 4]就是接收快取,也是源位址,而skb->data就是包的資料位址,也是目的位址,一目了然。

④把這個包送到linux協議棧去進行下一步處理

skb->protocol = eth_type_trans (skb, dev);

netif_rx (skb);

在netif_rx()函式執行完後,這個包的資料就脫離了網絡卡驅動範疇,而進入了linux網路協議棧裡面,把這些資料報的乙太網幀頭,ip 頭,tcp頭都脫下來,最後把資料送給了應用程式,不過協議棧不再本文討論範圍內。netif_rx函式在net/core/dev.c,中。

而rtl8139_remove_one則基本是rtl8139_init_one的逆過程。

到此,本文已經將linux驅動程式的框架勾勒了出來。

網絡卡驅動程式

1 網路子系統 如下的核心空間的幾層 使用者空間 應用層 核心空間 系統呼叫 通過socket訪問網路子系統 核心空間 協議無關介面 一組通用函式通過socket訪問不同協議 核心空間 網路協議層 各種傳輸層網路層協議tcp udp ip 核心空間 裝置無關介面 核心空間 裝置驅動 硬體裝置 2 網...

網絡卡驅動程式

我們這裡說的是網絡卡驅動程式,不是網路驅動程式,網路有七層,我們寫的只是最底層的東西,網路這麼多層,但是最終你還是要操作硬體啊 所以上面肯定有個硬體相關層,我們要寫的就是硬體相關的驅動程式這一小塊。網絡卡你不需要開啟什麼裝置,你只需要socket程式設計就行了 怎麼寫1 分配某個結構體 2 設定 3...

linux網絡卡驅動程式詳解

當網路上一台計算機準備傳送資料時,他的網絡卡開始工作了,首先網絡卡的晶元偵聽在網路上是否有資料在 流動,如果沒有,他就把資料傳送到網路上,在偵聽和傳送之間有一段極小的時間延遲,在這段時間內,也有 可能在網路上有其他的計算機也準備傳送資料,也偵聽到網路上沒有資料在流動,這就可能兩台甚至多台 的資料一起...