IT貓撲網(wǎng):您身邊最放心的安全下載站! 最新更新|軟件分類(lèi)|軟件專(zhuān)題|手機(jī)版|論壇轉(zhuǎn)貼|軟件發(fā)布

您當(dāng)前所在位置: 首頁(yè)操作系統(tǒng)LINUX → Linux網(wǎng)卡驅(qū)動(dòng)程序分析

Linux網(wǎng)卡驅(qū)動(dòng)程序分析

時(shí)間:2015-06-28 00:00:00 來(lái)源:IT貓撲網(wǎng) 作者:網(wǎng)管聯(lián)盟 我要評(píng)論(0)

  學(xué)習(xí)應(yīng)該是一個(gè)先把問(wèn)題簡(jiǎn)單化,再把問(wèn)題復(fù)雜化的過(guò)程。一開(kāi)始就著手處理復(fù)雜的問(wèn)題,難免讓人有心驚膽顫,捉襟見(jiàn)肘的感覺(jué)。讀Linux網(wǎng)卡驅(qū)動(dòng) 也是一樣。那長(zhǎng)長(zhǎng)的源碼夾雜著那些我們陌生的變量和符號(hào),望而生畏便是理所當(dāng)然的了。不要擔(dān)心,事情總有解決的辦法,先把一些我們管不著的代碼切割出去,留下必須的部分,把框架掌握了,那其他的事情自然就水到渠成了,這是筆者的心得。

  一般在使用的Linux網(wǎng)卡驅(qū)動(dòng)代碼動(dòng)輒3000行左右,這個(gè)代碼量以及它所表達(dá)出來(lái)的知識(shí)量無(wú)疑是龐大的,我們有沒(méi)有辦法縮短一下這個(gè)代碼量,使我們的學(xué)習(xí)變的簡(jiǎn)單些呢?經(jīng)過(guò)筆者的不懈努力,在仍然能夠使網(wǎng)絡(luò)設(shè)備正常工作的前提下,把它縮減到了600多行,我們把暫時(shí)還用不上的功能先割出去。這樣一來(lái),事情就簡(jiǎn)單多了,真的就剩下一個(gè)框架了。

  下面我們就來(lái)剖析這個(gè)可以執(zhí)行的框架。

  限于篇幅,以下分析用到的所有涉及到內(nèi)核中的函數(shù)代碼,我都不予列出,但給出在哪個(gè)具體文件中,請(qǐng)讀者自行查閱。

  首先,我們來(lái)看看設(shè)備的初始化。當(dāng)我們正確編譯完我們的程序后,我們就需要把生成的目標(biāo)文件加載到內(nèi)核中去,我們會(huì)先 ifconfig eth0 down和rmmod 8139too來(lái)卸載正在使用的網(wǎng)卡驅(qū)動(dòng),然后insmod 8139too.o把我們的驅(qū)動(dòng)加載進(jìn)去(其中8139too.o是我們編譯生成的目標(biāo)文件)。就像C程序有主函數(shù)main()一樣,模塊也有第一個(gè)執(zhí)行的函數(shù),即 module_init(rtl8139_init_module);在我們的程序中,rtl8139_init_module()在insmod之后首 先執(zhí)行,它的代碼如下:

  static int __init rtl8139_init_module (void)

  {

  return pci_module_init (&rtl8139_pci_driver);

  }

  它直接調(diào)用了pci_module_init(),這個(gè)函數(shù)代碼在Linux/drivers/net/eepro100.c中,并且把 rtl8139_pci_driver(這個(gè)結(jié)構(gòu)是在我們的驅(qū)動(dòng)代碼里定義的,它是驅(qū)動(dòng)程序和PCI設(shè)備聯(lián)系的紐帶)的地址作為參數(shù)傳給了它。 rtl8139_pci_driver定義如下:

  static struct pci_driver rtl8139_pci_driver = {

  name: MODNAME,

  id_table: rtl8139_pci_tbl,

  probe: rtl8139_init_one,

  remove: rtl8139_remove_one,

  };

  pci_module_init()在驅(qū)動(dòng)代碼里沒(méi)有定義,你一定想到了,它是Linux內(nèi)核提供給模塊是一個(gè)標(biāo)準(zhǔn)接口,那么這個(gè)接口都干了些什么?筆者跟蹤了這個(gè)函數(shù),里面調(diào)用了pci_register_driver(),這個(gè)函數(shù)代碼在Linux/drivers/pci/pci.c 中,pci_register_driver做了三件事情。

 ?、偈前褞н^(guò)來(lái)的參數(shù)rtl8139_pci_driver在內(nèi)核中進(jìn)行了注冊(cè)。內(nèi)核中有一個(gè)PCI設(shè)備的大的鏈表,這里負(fù)責(zé)把這個(gè)PCI驅(qū)動(dòng)掛到里面去。

 ?、谑遣榭纯偩€上所有PCI設(shè)備(網(wǎng)卡設(shè)備屬于PCI設(shè)備的一種)的配置空間,如果發(fā)現(xiàn)標(biāo)識(shí)信息與rtl8139_pci_driver中的id_table相同,即rtl8139_pci_tbl,而它的定義如下:

  static struct pci_device_id rtl8139_pci_tbl[] __devinitdata = {

  {0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1},

  {PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0,0 },

  {0,}

  };

  那么就說(shuō)明這個(gè)驅(qū)動(dòng)程序就是用來(lái)驅(qū)動(dòng)這個(gè)設(shè)備的,于是調(diào)用rtl8139_pci_driver中的probe函數(shù)即 rtl8139_init_one,這個(gè)函數(shù)是在我們的驅(qū)動(dòng)程序中定義了的,它是用來(lái)初始化整個(gè)設(shè)備和做一些準(zhǔn)備工作。這里需要注意一下 pci_device_id是內(nèi)核定義的用來(lái)辨別不同PCI設(shè)備的一個(gè)結(jié)構(gòu),例如在我們這里0x10ec代表的是Realtek公司,我們掃描PCI設(shè)備配置空間如果發(fā)現(xiàn)有Realtek公司制造的設(shè)備時(shí),兩者就對(duì)上了。當(dāng)然對(duì)上了公司號(hào)后還得看其他的設(shè)備號(hào)什么的,都對(duì)上了才說(shuō)明這個(gè)驅(qū)動(dòng)是可以為這個(gè)設(shè)備服務(wù)的。

  ③是把這個(gè)rtl8139_pci_driver結(jié)構(gòu)掛在這個(gè)設(shè)備的數(shù)據(jù)結(jié)構(gòu)(pci_dev)上,表示這個(gè)設(shè)備從此就有了自己的驅(qū)動(dòng)了。而驅(qū)動(dòng)也找到了它服務(wù)的對(duì)象了。

  PCI是一個(gè)總線標(biāo)準(zhǔn),PCI總線上的設(shè)備就是PCI設(shè)備,這些設(shè)備有很多類(lèi)型,當(dāng)然也包括網(wǎng)卡設(shè)備,每一個(gè)PCI設(shè)備在內(nèi)核中抽象為一個(gè)數(shù)據(jù)結(jié)構(gòu)pci_dev,它描述了一個(gè)PCI設(shè)備的所有的特性,具體請(qǐng)查詢相關(guān)文檔,本文限于篇幅無(wú)法詳細(xì)描述。但是有幾個(gè)地方和驅(qū)動(dòng)程序的關(guān)系特別大,必須予以說(shuō)明。PCI設(shè)備都遵守PCI標(biāo)準(zhǔn),這個(gè)部分所有的PCI設(shè)備都是一樣的,每個(gè)PCI設(shè)備都有一段寄存器存儲(chǔ)著配置空間,這一部分格式是一樣的,比如第一個(gè)寄存器總是生產(chǎn)商號(hào)碼,如Realtek就是10ec,而Intel則是另一個(gè)數(shù)字,這些都是商家像標(biāo)準(zhǔn)組織申請(qǐng)的,是肯定不同的。我就可以通過(guò)配置空間來(lái)辨別其生產(chǎn)商,設(shè)備號(hào),不論你什么平臺(tái),x86也好,ppc也好,他們都是同一的標(biāo)準(zhǔn)格式。當(dāng)然光有這些PCI配置空間的統(tǒng)一格式還是不夠的,比如 說(shuō)人類(lèi),都有鼻子和眼睛,但并不是所有人的鼻子和眼睛都長(zhǎng)的一樣的。網(wǎng)卡設(shè)備是PCI設(shè)備必須遵守規(guī)則,在設(shè)備里集成了PCI配置空間,但它是一個(gè)網(wǎng)卡就必須同時(shí)集成能控制網(wǎng)卡工作的寄存器。而寄存器的訪問(wèn)就成了一個(gè)問(wèn)題。在Linux里面我們是把這些寄存器映射到主存虛擬空間上的,換句話說(shuō)我們的CPU 訪存指令就可以訪問(wèn)到這些處于外設(shè)中的控制寄存器。總結(jié)一下,PCI設(shè)備主要包括兩類(lèi)空間,一個(gè)是配置空間,它是操作系統(tǒng)或BIOS控制外設(shè)的統(tǒng)一格式的空 間,CPU指令不能訪問(wèn),訪問(wèn)這個(gè)空間要借助BIOS功能,事實(shí)上Linux的訪問(wèn)配置空間的函數(shù)是通過(guò)CPU指令驅(qū)使BIOS來(lái)完成讀寫(xiě)訪問(wèn)的。

  而另一類(lèi)是普通的控制寄存器空間,這一部分映射完后CPU可以訪問(wèn)來(lái)控制設(shè)備工作。

  現(xiàn)在我們回到上面pci_register_driver的第二步,如果找到相關(guān)設(shè)備和我們的pci_device_id結(jié)構(gòu)數(shù)組對(duì)上號(hào)了,說(shuō)明我們找到服務(wù)對(duì)象了,則調(diào)用rtl8139_init_one,它主要做了七件事:

 ?、?建立net_device結(jié)構(gòu),讓它在內(nèi)核中代表這個(gè)網(wǎng)絡(luò)設(shè)備。但是讀者可能會(huì)問(wèn),pci_dev也是代表著這個(gè)設(shè)備,那么兩者有什么區(qū)別 呢,正如我們上面討論的,網(wǎng)卡設(shè)備既要遵循PCI規(guī)范,也要擔(dān)負(fù)起其作為網(wǎng)卡設(shè)備的職責(zé),于是就分了兩塊,pci_dev用來(lái)負(fù)責(zé)網(wǎng)卡的PCI規(guī)范,而這里要說(shuō)的net_device則是負(fù)責(zé)網(wǎng)卡的網(wǎng)絡(luò)設(shè)備這個(gè)職責(zé)。

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

  if (dev == NULL) {

  printk ("unable to alloc new ethernet\n");

  return -ENOMEM;

  }

  tp = dev->priv;

  init_etherdev函數(shù)在Linux/drivers/net/net_init.c中,在這個(gè)函數(shù)中分配了net_device的內(nèi)存并進(jìn)行了初步的初始化。這里值得注意的是net_device中的一個(gè)成員priv,它代表著不同網(wǎng)卡的私有數(shù)據(jù),比如Intel的網(wǎng)卡和Realtek 的網(wǎng)卡在內(nèi)核中都是以net_device來(lái)代表。但是他們是有區(qū)別的,比如Intel和Realtek實(shí)現(xiàn)同一功能的方法不一樣,這些都是靠著priv 來(lái)體現(xiàn)。所以這里把拿出來(lái)同net_device相提并論。分配內(nèi)存時(shí),net_device中除了priv以外的成員都是固定的,而priv的大小是可 以任意的,所以分配時(shí)要把priv的大小傳過(guò)去。

  ②開(kāi)啟這個(gè)設(shè)備(其實(shí)是開(kāi)啟了設(shè)備的寄存器映射到內(nèi)存的功能)

  rc = pci_enable_device (pdev);

  if (rc)

  goto err_out;

  pci_enable_device也是一個(gè)內(nèi)核開(kāi)發(fā)出來(lái)的接口,代碼在drivers/pci/pci.c中,筆者跟蹤發(fā)現(xiàn)這個(gè)函數(shù)主要就是把 PCI配置空間的Command域的0位和1位置成了1,從而達(dá)到了開(kāi)啟設(shè)備的目的,因?yàn)閞tl8139的官方datasheet中,說(shuō)明了這兩位的作用 就是開(kāi)啟內(nèi)存映射和I/O映射,如果不開(kāi)的話,那我們以上討論的把控制寄存器空間映射到內(nèi)存空間的這一功能就被屏蔽了,這對(duì)我們是非常不利的,除此之外,pci_enable_device還做了些中斷開(kāi)啟工作。

 ?、郢@得各項(xiàng)資源

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

  讀者也許疑問(wèn)我們的寄存器被映射到內(nèi)存中的什么地方是什么時(shí)候有誰(shuí)決定的呢。是這樣的,在硬件加電初始化時(shí),BIOS固件同一檢查了所有的PCI 設(shè)備,并統(tǒng)一為他們分配了一個(gè)和其他互不沖突的地址,讓他們的驅(qū)動(dòng)程序可以向這些地址映射他們的寄存器,這些地址被BIOS寫(xiě)進(jìn)了各個(gè)設(shè)備的配置空間,因?yàn)檫@個(gè)活動(dòng)是一個(gè)PCI的標(biāo)準(zhǔn)的活動(dòng),所以自然寫(xiě)到各個(gè)設(shè)備的配置空間里而不是他們風(fēng)格各異的控制寄存器空間里。當(dāng)然只有BIOS可以訪問(wèn)配置空間。當(dāng)操作系統(tǒng)初始化時(shí),他為每個(gè)PCI設(shè)備分配了pci_dev結(jié)構(gòu),并且把BIOS獲得的并寫(xiě)到了配置空間中的地址讀出來(lái)寫(xiě)到了pci_dev中的 resource字段中。這樣以后我們?cè)谧x這些地址就不需要在訪問(wèn)配置空間了,直接跟pci_dev要就可以了,我們這里的四個(gè)函數(shù)就是直接從 pci_dev讀出了相關(guān)數(shù)據(jù),代碼在include/linux/pci.h中。定義如下:

  #define pci_resource_start(dev,bar) ((dev)->resource[(bar)].start)

  #define pci_resource_end(dev,bar) ((dev)->resource[(bar)].end)

  這里需要說(shuō)明一下,每個(gè)PCI設(shè)備有0-5一共6個(gè)地址空間,我們通常只使用前兩個(gè),這里我們把參數(shù)1傳給了bar就是使用內(nèi)存映射的地址空間。

  ④把得到的地址進(jìn)行映射

  ioaddr = ioremap (mmio_start, mmio_len);

  if (ioaddr == NULL) {

  printk ("cannot remap MMIO, aborting\n");

  rc = -EIO;

  goto err_out_free_res;

  }

  ioremap是內(nèi)核提供的用來(lái)映射外設(shè)寄存器到主存的函數(shù),我們要映射的地址已經(jīng)從pci_dev中讀了出來(lái)(上一步)

關(guān)鍵詞標(biāo)簽:Linux,網(wǎng)卡驅(qū)動(dòng)

相關(guān)閱讀

文章評(píng)論
發(fā)表評(píng)論

熱門(mén)文章 安裝紅帽子RedHat Linux9.0操作系統(tǒng)教程 安裝紅帽子RedHat Linux9.0操作系統(tǒng)教程 Tomcat9.0如何安裝_Tomcat9.0環(huán)境變量配置方法 Tomcat9.0如何安裝_Tomcat9.0環(huán)境變量配置方法 多種操作系統(tǒng)NTP客戶端配置 多種操作系統(tǒng)NTP客戶端配置 Linux操作系統(tǒng)修改IP Linux操作系統(tǒng)修改IP

相關(guān)下載

    人氣排行 Linux下獲取CPUID、硬盤(pán)序列號(hào)與MAC地址 dmidecode命令查看內(nèi)存型號(hào) linux tc實(shí)現(xiàn)ip流量限制 安裝紅帽子RedHat Linux9.0操作系統(tǒng)教程 linux下解壓rar文件 lcx.exe、nc.exe、sc.exe入侵中的使用方法 Ubuntu linux 關(guān)機(jī)、重啟、注銷(xiāo) 命令 查看linux服務(wù)器硬盤(pán)IO讀寫(xiě)負(fù)載