亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 1133 | 回復(fù): 0
打印 上一主題 下一主題

Linux內(nèi)核學(xué)習(xí)筆記之網(wǎng)卡驅(qū)動(dòng)的詳細(xì)分析(轉(zhuǎn)) [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2009-02-19 01:28 |只看該作者 |倒序?yàn)g覽

   學(xué)習(xí)應(yīng)該是一個(gè)先把問題簡單化,在把問題復(fù)雜化的過程。一開始就著手處理復(fù)雜的問題,難免讓 人有心驚膽顫,捉襟見肘的感覺。讀Linux網(wǎng)卡驅(qū)動(dòng)也是一樣。那長長的源碼夾雜著那些我們陌生的變量和符號(hào),望而生畏便是理所當(dāng)然的了。不要擔(dān)心,事情 總有解決的辦法,先把一些我們管不著的代碼切割出去,留下必須的部分,把框架掌握了,哪其他的事情自然就水到渠成了,這是筆者的心得。
一般在使用的Linux網(wǎng)卡驅(qū)動(dòng)代碼動(dòng)輒3000行左右,這個(gè)代碼量以及它所表達(dá)出來的知識(shí)量無疑是龐大的,我們有沒有辦法縮短一下這個(gè)代碼量,使我們的 學(xué)習(xí)變的簡單些呢,經(jīng)過筆者的不懈努力,在仍然能夠使網(wǎng)絡(luò)設(shè)備正常工作的前提下,把它縮減到了600多行,我們把暫時(shí)還用不上的功能先割出去。這樣一來, 事情就簡單多了,真的就剩下一個(gè)框架了。下面我們就來剖析這個(gè)可以執(zhí)行的框架。
限于篇幅,以下分析用到的所有涉及到內(nèi)核中的函數(shù)代碼,我都不予列出,但給出在哪個(gè)具體文件中,請(qǐng)讀者自行查閱。
首先,我們來看看設(shè)備的初始化。當(dāng)我們正確編譯完我們的程序后,我們就需要把生成的目標(biāo)文件加載到內(nèi)核中去,我們會(huì)先ifconfig eth0 down和rmmod 8139too來卸載正在使用的網(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)代碼里沒有定義,你一定想到了,它是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做了三件事情。
①是把帶過來的參數(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,}
};
那么就說明這個(gè)驅(qū)動(dòng)程序就是用來驅(qū)動(dòng)這個(gè)設(shè)備的,于是調(diào)用rtl8139_pci_driver中的probe函數(shù)即rtl8139_init_one, 這個(gè)函數(shù)是在我們的驅(qū)動(dòng)程序中定義了的,它是用來初始化整個(gè)設(shè)備和做一些準(zhǔn)備工作。這里需要注意一下pci_device_id是內(nè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ì)上了才說明這個(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è)備有很多類型,當(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)文檔,本文限于篇幅無法詳細(xì)描述。但是有幾個(gè)地方和驅(qū)動(dòng)程序的關(guān)系特別大,必須予以 說明。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)的,是肯定不同的。我就可以通過配置空 間來辨別其生產(chǎn)商,設(shè)備號(hào),不論你什么平臺(tái),x86也好,ppc也好,他們都是同一的標(biāo)準(zhǔn)格式。當(dāng)然光有這些PCI配置空間的統(tǒng)一格式還是不夠的,比如說 人類,都有鼻子和眼睛,但并不是所有人的鼻子和眼睛都長的一樣的。網(wǎng)卡設(shè)備是PCI設(shè)備必須遵守規(guī)則,在設(shè)備里集成了PCI配置空間,但它是一個(gè)網(wǎng)卡就必 須同時(shí)集成能控制網(wǎng)卡工作的寄存器。而寄存器的訪問就成了一個(gè)問題。在Linux里面我們是把這些寄存器映射到主存虛擬空間上的,換句話說我們的CPU訪 存指令就可以訪問到這些處于外設(shè)中的控制寄存器。總結(jié)一下PCI設(shè)備主要包括兩類空間,一個(gè)是配置空間,它是操作系統(tǒng)或BIOS控制外設(shè)的統(tǒng)一格式的空 間,CPU指令不能訪問,訪問這個(gè)空間要借助BIOS功能,事實(shí)上Linux的訪問配置空間的函數(shù)是通過CPU指令驅(qū)使BIOS來完成讀寫訪問的。而另一 類是普通的控制寄存器空間,這一部分映射完后CPU可以訪問來控制設(shè)備工作。
現(xiàn)在我們回到上面pci_register_driver的第二步,如果找到相關(guān)設(shè)備和我們的pci_device_id結(jié)構(gòu)數(shù)組對(duì)上號(hào)了,說明我們找到服務(wù)對(duì)象了,則調(diào)用rtl8139_init_one,它主要做了七件事:
① 建立net_device結(jié)構(gòu),讓它在內(nèi)核中代表這個(gè)網(wǎng)絡(luò)設(shè)備。但是讀者可能會(huì)問,pci_dev也是代表著這個(gè)設(shè)備,那么兩者有什么區(qū)別呢,正如我們上 面討論的,網(wǎng)卡設(shè)備既要遵循PCI規(guī)范,也要擔(dān)負(fù)起其作為網(wǎng)卡設(shè)備的職責(zé),于是就分了兩塊,pci_dev用來負(fù)責(zé)網(wǎng)卡的PCI規(guī)范,而這里要說的 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來代表。但是他們是有區(qū)別的,比如Intel和Realtek實(shí)現(xiàn)同一功能的方法不一樣,這些都是靠著priv來體現(xiàn)。 所以這里把拿出來同net_device相提并論。分配內(nèi)存時(shí),net_device中除了priv以外的成員都是固定的,而priv的大小是可以任意 的,所以分配時(shí)要把priv的大小傳過去。
②開啟這個(gè)設(shè)備(其實(shí)是開啟了設(shè)備的寄存器映射到內(nèi)存的功能)
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
pci_enable_device 也是一個(gè)內(nèi)核開發(fā)出來的接口,代碼在drivers/pci/pci.c中,筆者跟蹤發(fā)現(xiàn)這個(gè)函數(shù)主要就是把PCI配置空間的Command域的0位和1 位置成了1,從而達(dá)到了開啟設(shè)備的目的,因?yàn)閞tl8139的官方datasheet中,說明了這兩位的作用就是開啟內(nèi)存映射和I/O映射,如果不開的 話,那我們以上討論的把控制寄存器空間映射到內(nèi)存空間的這一功能就被屏蔽了,這對(duì)我們是非常不利的,除此之外,pci_enable_device還做了 些中斷開啟工作。
③獲得各項(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);
讀者也許疑問我們的寄存器被映射到內(nèi)存中的什么地方是什么時(shí)候有誰決定的呢。是這樣的,在硬件加電初始化時(shí),BIOS固件同統(tǒng)一檢查了所有的PCI設(shè)備, 并統(tǒng)一為他們分配了一個(gè)和其他互不沖突的地址,讓他們的驅(qū)動(dòng)程序可以向這些地址映射他們的寄存器,這些地址被BIOS寫進(jìn)了各個(gè)設(shè)備的配置空間,因?yàn)檫@個(gè) 活動(dòng)是一個(gè)PCI的標(biāo)準(zhǔn)的活動(dòng),所以自然寫到各個(gè)設(shè)備的配置空間里而不是他們風(fēng)格各異的控制寄存器空間里。當(dāng)然只有BIOS可以訪問配置空間。當(dāng)操作系統(tǒng) 初始化時(shí),他為每個(gè)PCI設(shè)備分配了pci_dev結(jié)構(gòu),并且把BIOS獲得的并寫到了配置空間中的地址讀出來寫到了pci_dev中的resource 字段中。這樣以后我們?cè)谧x這些地址就不需要在訪問配置空間了,直接跟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)
這里需要說明一下,每個(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)核提供的用來映射外設(shè)寄存器到主存的函數(shù),我們要映射的地址已經(jīng)從pci_dev中讀了出來(上一步),這樣就水到渠成的成功映射了而 不會(huì)和其他地址有沖突。映射完了有什么效果呢,我舉個(gè)例子,比如某個(gè)網(wǎng)卡有100 個(gè)寄存器,他們都是連在一塊的,位置是固定的,加入每個(gè)寄存器占4個(gè)字節(jié),那么一共400個(gè)字節(jié)的空間被映射到內(nèi)存成功后,ioaddr就是這段地址的開 頭(注意ioaddr是虛擬地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保護(hù)模式下CPU不認(rèn)物理地址,只認(rèn)虛擬地 址),ioaddr+0就是第一個(gè)寄存器的地址,ioaddr+4就是第二個(gè)寄存器地址(每個(gè)寄存器占4個(gè)字節(jié)),以此類推,我們就能夠在內(nèi)存中訪問到所 有的寄存器進(jìn)而操控他們了。
⑤重啟網(wǎng)卡設(shè)備
重啟網(wǎng)卡設(shè)備是初始化網(wǎng)卡設(shè)備的一個(gè)重要部分,它的原理就是向寄存器中寫入命令就可以了(注意這里寫寄存器,而不是配置空間,因?yàn)楦鶳CI沒有什么關(guān)系),代碼如下:
writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
是我們看到第二參數(shù)ioaddr+ChipCmd,ChipCmd是一個(gè)位移,使地址剛好對(duì)應(yīng)的就是ChipCmd哪個(gè)寄存器,讀者可以查閱官方 datasheet得到這個(gè)位移量,我們?cè)诔绦蛑卸x的這個(gè)值為:ChipCmd = 0x37;與datasheet是吻合的。我們把這個(gè)命令寄存器中相應(yīng)位(RESET)置1就可以完成操作。
⑥獲得MAC地址,并把它存儲(chǔ)到net_device中。
for(i = 0; i dev_addr = readb(ioaddr+i);
dev->broadcast = 0xff;
}
我們可以看到讀的地址是ioaddr+0到ioaddr+5,讀者查看官方datasheet會(huì)發(fā)現(xiàn)寄存器地址空間的開頭6個(gè)字節(jié)正好存的是這個(gè)網(wǎng)卡設(shè)備的MAC地址,MAC地址是網(wǎng)絡(luò)中標(biāo)識(shí)網(wǎng)卡的物理地址,這個(gè)地址在今后的收發(fā)數(shù)據(jù)包時(shí)會(huì)用的上。
⑦向net_device中登記一些主要的函數(shù)
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
由于dev(net_device)代表著設(shè)備,把這些函數(shù)注冊(cè)完后,rtl8139_open就是用于打開這個(gè)設(shè)備, rtl8139_start_xmit就是當(dāng)應(yīng)用程序要通過這個(gè)設(shè)備往外面發(fā)數(shù)據(jù)時(shí)被調(diào)用,具體的其實(shí)這個(gè)函數(shù)是在網(wǎng)絡(luò)協(xié)議層中調(diào)用的,這就涉及到 Linux網(wǎng)絡(luò)協(xié)議棧的內(nèi)容,不再我們討論之列,我們只是負(fù)責(zé)實(shí)現(xiàn)它。rtl8139_close用來關(guān)掉這個(gè)設(shè)備。
好了,到此我們把rtl8139_init_one函數(shù)介紹完了,初始化個(gè)設(shè)備完了之后呢,我們通過ifconfig eth0 up命令來把我們的設(shè)備激活。這個(gè)命令直接導(dǎo)致了我們剛剛注冊(cè)的rtl8139_open的調(diào)用。這個(gè)函數(shù)激活了設(shè)備。這個(gè)函數(shù)主要做了三件事。
①注冊(cè)這個(gè)設(shè)備的中斷處理函數(shù)。當(dāng)網(wǎng)卡發(fā)送數(shù)據(jù)完成或者接收到數(shù)據(jù)時(shí),是用中斷的形式來告知的,比如有數(shù)據(jù)從網(wǎng)線傳來,中斷也通知了我們,那么必須要有一 個(gè)處理這個(gè)中斷的函數(shù)來完成數(shù)據(jù)的接收。關(guān)于Linux的中斷機(jī)制不是我們?cè)敿?xì)講解的范疇,有興趣的可以參考《Linux內(nèi)核源代碼情景分析》,但是有個(gè) 非常重要的資源我們必須注意,那就是中斷號(hào)的分配,和內(nèi)存地址映射一樣,中斷號(hào)也是BIOS在初始化階段分配并寫入設(shè)備的配置空間的,然后Linux在建 立pci_dev時(shí)從配置空間讀出這個(gè)中斷號(hào)然后寫入pci_dev的irq成員中,所以我們注冊(cè)中斷程序需要中斷號(hào)就是直接從pci_dev里取就可以 了。
retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);
if (retval) {
return retval;
}
我們注冊(cè)的中斷處理函數(shù)是rtl8139_interrupt,也就是說當(dāng)網(wǎng)卡發(fā)生中斷(如數(shù)據(jù)到達(dá))時(shí),中斷控制器8259A把中斷號(hào)發(fā)給CPU, CPU根據(jù)這個(gè)中斷號(hào)找到處理程序,這里就是rtl8139_interrupt,然后執(zhí)行。rtl8139_interrupt也是在我們的程序中定義 好了的,這是驅(qū)動(dòng)程序的一個(gè)重要的義務(wù),也是一個(gè)基本的功能。request_irq 的代碼在arch/i386/kernel/irq.c中。
②分配發(fā)送和接收的緩存空間
根據(jù)官方文檔,發(fā)送一個(gè)數(shù)據(jù)包的過程是這樣的:先從應(yīng)用程序中把數(shù)據(jù)包拷貝到一段連續(xù)的內(nèi)存中(這段內(nèi)存就是我們這里要分配的緩存),然后把這段內(nèi)存的地 址寫進(jìn)網(wǎng)卡的數(shù)據(jù)發(fā)送地址寄存器(TSAD)中,這個(gè)寄存器的偏移量是TxAddr0 = 0x20。在把這個(gè)數(shù)據(jù)包的長度寫進(jìn)另一個(gè)寄存器(TSD)中,它的偏移量是TxStatus0 = 0x10。然后就把這段內(nèi)存的數(shù)據(jù)發(fā)送到網(wǎng)卡內(nèi)部的發(fā)送緩沖中(FIFO),最后由這個(gè)發(fā)送緩沖區(qū)把數(shù)據(jù)發(fā)送到網(wǎng)線上。
好了現(xiàn)在創(chuàng)建這么一個(gè)發(fā)送和接收緩沖內(nèi)存的目的已經(jīng)很顯然了。
tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
&tp->tx_bufs_dma);
tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
&tp->rx_ring_dma);
tp 是net_device的priv的指針,tx_bufs是發(fā)送緩沖內(nèi)存的首地址,rx_ring是接收緩存內(nèi)存的首地址,他們都是虛擬地址,而最后一個(gè) 參數(shù)tx_bufs_dma和rx_ring_dma均是這一段內(nèi)存的物理地址。為什么同一個(gè)事物,既用虛擬地址來表示它還要用物理地址呢,是這樣的, CPU執(zhí)行程序用到這個(gè)地址時(shí),用虛擬地址,而網(wǎng)卡設(shè)備向這些內(nèi)存中存取數(shù)據(jù)時(shí)用的是物理地址(因?yàn)榫W(wǎng)卡相對(duì)CPU屬于頭腦比較簡單型的)。 pci_alloc_consistent的代碼在Linux/arch/i386/kernel/pci-dma.c中。
③發(fā)送和接收緩沖區(qū)初始化和網(wǎng)卡開始工作的操作
RTL8139有4個(gè)發(fā)送描述符(包括4個(gè)發(fā)送緩沖區(qū)的基地址寄存器(TSAD0-TSAD3)和4個(gè)發(fā)送狀態(tài)寄存器(TSD0-TSD3)。也就是說我 們分配的緩沖區(qū)要分成四個(gè)等分并把這四個(gè)空間的地址都寫到相關(guān)寄存器里去,下面這段代碼完成了這個(gè)操作。
for (i = 0; i priv)->tx_buf =
&((struct rtl8139_private*)dev->priv)->tx_bufs[i * TX_BUF_SIZE];
上面這段代碼負(fù)責(zé)把發(fā)送緩沖區(qū)虛擬空間進(jìn)行了分割。
for (i = 0; i tx_bufs_dma+(tp->tx_buftp->tx_bufs),ioaddr+TxAddr0+(i*4));
readl(ioaddr+TxAddr0+(i * 4));
}
上面這段代碼負(fù)責(zé)把發(fā)送緩沖區(qū)物理空間進(jìn)行了分割,并把它寫到了相關(guān)寄存器中,這樣在網(wǎng)卡開始工作后就能夠迅速定位和找到這些內(nèi)存并存取他們的數(shù)據(jù)。
writel(tp->rx_ring_dma,ioaddr+RxBuf);
上面這行代碼是把接收緩沖區(qū)的物理地址寫到了相關(guān)寄存器中,這樣網(wǎng)卡接收到數(shù)據(jù)后就能準(zhǔn)確的把數(shù)據(jù)從網(wǎng)卡中搬運(yùn)到這些內(nèi)存空間中,等待CPU來領(lǐng)走他們。
writeb((readb(ioaddr+ChipCmd) & ChipCmdClear) |
CmdRxEnb | CmdTxEnb,ioaddr+ChipCmd);
重新RESET設(shè)備后,我們要激活設(shè)備的發(fā)送和接收的功能,上面這行代碼就是向相關(guān)寄存器中寫入相應(yīng)值,激活了設(shè)備的這些功能。
writel ((TX_DMA_BURST
上面這行代碼是向網(wǎng)卡的TxConfig (位移是0x44)寄存器中寫入TX_DMA_BURST
另外在這個(gè)階段設(shè)置了接收數(shù)據(jù)的模式,和開啟中斷等等,限于篇幅由讀者自行研究。
下面進(jìn)入數(shù)據(jù)收發(fā)階段:
當(dāng)一個(gè)網(wǎng)絡(luò)應(yīng)用程序要向網(wǎng)絡(luò)發(fā)送數(shù)據(jù)時(shí),它要利用Linux的網(wǎng)絡(luò)協(xié)議棧來解決一系列問題,找到網(wǎng)卡設(shè)備的代表net_device,由這個(gè)結(jié)構(gòu)來找到并 控制這個(gè)網(wǎng)卡設(shè)備來完成數(shù)據(jù)包的發(fā)送,具體是調(diào)用net_device的hard_start_xmit成員函數(shù),這是一個(gè)函數(shù)指針,在我們的驅(qū)動(dòng)程序里 它指向的是 rtl8139_start_xmit,正是由它來完成我們的發(fā)送工作的,下面我們就來剖析這個(gè)函數(shù)。它一共做了四件事。
①檢查這個(gè)要發(fā)送的數(shù)據(jù)包的長度,如果它達(dá)不到以太網(wǎng)幀的長度,必須采取措施進(jìn)行填充。
if( skb->len data + ETH_ZLEN) end ){
memset( skb->data + skb->len, 0x20, (ETH_ZLEN - skb->len) );
skb->len = (skb->len >= ETH_ZLEN) ? skb->len : ETH_ZLEN;}
else{
printk("%s:(skb->data+ETH_ZLEN) > skb->end\n",__FUNCTION__);
}
}
skb->data和skb->end就決定了這個(gè)包的內(nèi)容,如果這個(gè)包本身總共的長度(skb->end- skb->data)都達(dá)不到要求,那么想填也沒地方填,就出錯(cuò)返回了,否則的話就填上。
②把包的數(shù)據(jù)拷貝到我們已經(jīng)建立好的發(fā)送緩存中。
memcpy (tp->tx_buf[entry], skb->data, skb->len);
其中skb->data就是數(shù)據(jù)包數(shù)據(jù)的地址,而tp->tx_buf[entry]就是我們的發(fā)送緩存地址,這樣就完成了拷貝,忘記了這些內(nèi)容的回頭看看前面的介紹。
③光有了地址和數(shù)據(jù)還不行,我們要讓網(wǎng)卡知道這個(gè)包的長度,才能保證數(shù)據(jù)不多不少精確的從緩存中截取出來搬運(yùn)到網(wǎng)卡中去,這是靠寫發(fā)送狀態(tài)寄存器(TSD)來完成的。
writel(tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN),ioaddr+TxStatus0+(entry * 4));
我們把這個(gè)包的長度和一些控制信息一起寫進(jìn)了狀態(tài)寄存器,使網(wǎng)卡的工作有了依據(jù)。
④判斷發(fā)送緩存是否已經(jīng)滿了,如果滿了在發(fā)就覆蓋數(shù)據(jù)了,要停發(fā)。
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);
談完了發(fā)送,我們開始談接收,當(dāng)有數(shù)據(jù)從網(wǎng)線上過來時(shí),網(wǎng)卡產(chǎn)生一個(gè)中斷,調(diào)用的中斷服務(wù)程序是rtl8139_interrupt,它主要做了三件事。
①從網(wǎng)卡的中斷狀態(tài)寄存器中讀出狀態(tài)值進(jìn)行分析,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種情況,屬于接收信號(hào),調(diào)用rtl8139_rx_interrupt進(jìn)行接收處理。
③ if (status & (TxOK | TxErr)) {
spin_lock (&tp->lock);
rtl8139_tx_interrupt (dev, tp, ioaddr);
spin_unlock (&tp->lock);
}
如果是傳輸完成的信號(hào),就調(diào)用rtl8139_tx_interrupt進(jìn)行發(fā)送善后處理。
下面我們先來看看接收中斷處理函數(shù)rtl8139_rx_interrupt,在這個(gè)函數(shù)中主要做了下面四件事
①這個(gè)函數(shù)是一個(gè)大循環(huán),循環(huán)條件是只要接收緩存不為空就還可以繼續(xù)讀取數(shù)據(jù),循環(huán)不會(huì)停止,讀空了之后就跳出。
int ring_offset = cur_rx % RX_BUF_LEN;
rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
上面三行代碼是計(jì)算出要接收的包的長度。
②根據(jù)這個(gè)長度來分配包的數(shù)據(jù)結(jié)構(gòu)
skb = dev_alloc_skb (pkt_size + 2);
③如果分配成功就把數(shù)據(jù)從接收緩存中拷貝到這個(gè)包中
eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
這個(gè)函數(shù)在include/linux/etherdevice.h中,實(shí)質(zhì)還是調(diào)用了memcpy()。
static inline void eth_copy_and_sum(struct sk_buff*dest, unsigned char *src, int len, int base)
{
memcpy(dest->data, src, len);
}
現(xiàn)在我們已經(jīng)熟知,&rx_ring[ring_offset + 4]就是接收緩存,也是源地址,而skb->data就是包的數(shù)據(jù)地址,也是目的地址,一目了然。
④把這個(gè)包送到Linux協(xié)議棧去進(jìn)行下一步處理
skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
在netif_rx()函數(shù)執(zhí)行完后,這個(gè)包的數(shù)據(jù)就脫離了網(wǎng)卡驅(qū)動(dòng)范疇,而進(jìn)入了Linux網(wǎng)絡(luò)協(xié)議棧里面,把這些數(shù)據(jù)包的以太網(wǎng)幀頭,IP頭,TCP 頭都脫下來,最后把數(shù)據(jù)送給了應(yīng)用程序,不過協(xié)議棧不再本文討論范圍內(nèi)。netif_rx函數(shù)在net/core/dev.c,中。
而rtl8139_remove_one則基本是rtl8139_init_one的逆過程。
轉(zhuǎn)帖地址:
http://www.linuxidc.com/Linux/2008-02/11220p2.htm


本文來自ChinaUnix博客,如果查看原文請(qǐng)點(diǎn):http://blog.chinaunix.net/u3/92401/showart_1833392.html
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP