lab6是實(shí)現(xiàn)網(wǎng)絡(luò)部分,代碼見 這里
1 QEMU 虛擬網(wǎng)絡(luò)
實(shí)驗(yàn)中將使用到QEMU的用戶模式網(wǎng)絡(luò)棧,因?yàn)樗恍枰芾韱T權(quán)限。JOS中通過更新makefile來啟用QEMU的用戶模式的網(wǎng)絡(luò)棧以及虛擬的E1000網(wǎng)卡。
QEMU默認(rèn)提供了一個(gè)在IP地址10.0.2.2上運(yùn)行的虛擬路由器,它會(huì)為JOS分配一個(gè)IP地址10.0.2.15。為簡單起見,我們將這些默認(rèn)值硬編碼到了 net/ns.h。
// net/ns.h
#define IP "10.0.2.15"
#define MASK "255.255.255.0"
#define DEFAULT "10.0.2.2"
雖然QEMU的虛擬網(wǎng)絡(luò)允許JOS與互聯(lián)網(wǎng)建立任意連接,但是JOS的IP地址10.0.2.15對(duì)于外部網(wǎng)絡(luò)來說并無意義(這是一個(gè)內(nèi)網(wǎng)地址,而QEMU就充當(dāng)了NAT的角色)。因此,我們無法直接連接到運(yùn)行在JOS內(nèi)部的網(wǎng)絡(luò)服務(wù)器,即便是從運(yùn)行QEMU的宿主機(jī)連接。為了解決該問題,我們將QEMU配置為在主機(jī)上的某個(gè)端口上運(yùn)行服務(wù)器,該端口只需連接到JOS中的某個(gè)端口,并在真實(shí)主機(jī)和虛擬網(wǎng)絡(luò)之間傳送數(shù)據(jù)。你將在端口7(echo)和80(http)上運(yùn)行JOS服務(wù)器。要查找QEMU在開發(fā)主機(jī)上轉(zhuǎn)發(fā)的端口,請(qǐng)運(yùn)行make which-ports。
# make which-ports
Local port 26001 forwards to JOS port 7 (echo server)
Local port 26002 forwards to JOS port 80 (web server)
抓包
QEMU的虛擬網(wǎng)絡(luò)棧會(huì)將進(jìn)出的數(shù)據(jù)包記錄到 qemu.pcap 文件中,可以通過tcpdump來查看。
tcpdump -XXnr qemu.pcap
2 網(wǎng)絡(luò)服務(wù)器
從頭開始編寫網(wǎng)絡(luò)堆棧很難。為此,我們將使用lwIP,一種開源輕量級(jí)包含了網(wǎng)絡(luò)棧的 TCP / IP協(xié)議套件。在這個(gè)實(shí)驗(yàn)中,lwIP是一個(gè)黑盒子,它實(shí)現(xiàn)了一個(gè)BSD套接字接口,并有一個(gè)數(shù)據(jù)包輸入和輸出端口。
該網(wǎng)絡(luò)服務(wù)器實(shí)際上是下面四個(gè)進(jìn)程組合,下圖展示了它們之間的關(guān)系。在本實(shí)驗(yàn)中要完成綠色標(biāo)記的四個(gè)部分。
- core network server environment(包括socket 調(diào)用分發(fā)和lwIP)
- input environment
- output environment
- timer environment

2.1 Core Network Server Environment
core network server 進(jìn)程由socket調(diào)用和分發(fā)以及l(fā)wIP本身組成。其中調(diào)用和分發(fā)工作原理類似文件服務(wù)器。用戶進(jìn)程使用stubs(lib/nsipc.c)發(fā)送IPC消息給core network server進(jìn)程,對(duì)于每個(gè)用戶進(jìn)程IPC,網(wǎng)絡(luò)服務(wù)器中的調(diào)度程序都會(huì)調(diào)用lwIP中提供的響應(yīng)的BSD套接字接口函數(shù)。
常規(guī)用戶進(jìn)程并不直接使用nsipc_* 這樣調(diào)用,它們使用 lib/sockets.c 中的函數(shù)。sockets.c中提供了基于文件描述符的套接字API,用戶環(huán)境通過文件描述符引用套接字,就像它們引用磁盤文件一樣。有許多操作(connect, accept)對(duì)于socket的文件描述符是特有的,不過像read,write,close則是跟文件服務(wù)器一樣。
盡管看起來文件服務(wù)器和網(wǎng)絡(luò)服務(wù)器的IPC調(diào)度很相似,但存在一個(gè)關(guān)鍵的區(qū)別:accept和recv這樣的BSD套接字調(diào)用可以無限阻塞。如果調(diào)度器執(zhí)行一個(gè)阻塞式的調(diào)用,則調(diào)度器也會(huì)阻塞,并且整個(gè)系統(tǒng)一次只能有一個(gè)未完成的網(wǎng)絡(luò)調(diào)用,這是不可接受的,因此網(wǎng)絡(luò)服務(wù)器使用用戶級(jí)線程來避免阻塞整個(gè)服務(wù)器。對(duì)于每個(gè)傳入的IPC消息,調(diào)度器都會(huì)創(chuàng)建一個(gè)線程并在新創(chuàng)建的線程中處理該請(qǐng)求。如果線程阻塞,那么只有那個(gè)線程進(jìn)入休眠狀態(tài),而其他線程繼續(xù)運(yùn)行。此外,還有三個(gè)輔助進(jìn)程,下面一一介紹。
2.2 Output Environment
當(dāng)lwIP接收用戶進(jìn)程的socket調(diào)用時(shí),它會(huì)生成用于網(wǎng)卡傳輸?shù)臄?shù)據(jù)包(如TCP/ARP包等)。lwIP使用NSREQ_OUTPUT IPC消息發(fā)送數(shù)據(jù)包到output進(jìn)程(數(shù)據(jù)包通過IPC的頁共享)。output進(jìn)程接收IPC消息,通過我們要實(shí)現(xiàn)的系統(tǒng)調(diào)用 sys_pkt_send 將數(shù)據(jù)包發(fā)送至網(wǎng)卡驅(qū)動(dòng)中。
2.3 Input Environment
網(wǎng)卡接收的數(shù)據(jù)包需要導(dǎo)入到lwIP中。對(duì)網(wǎng)卡接收到的每個(gè)數(shù)據(jù)包,input進(jìn)程將從內(nèi)核空間拉取數(shù)據(jù)包(通過我們實(shí)現(xiàn)的讀取數(shù)據(jù)包的系統(tǒng)調(diào)用 sys_pkt_receive),然后通過NSREQ_INPUT IPC消息將數(shù)據(jù)包發(fā)送到core network server進(jìn)程中。
input進(jìn)程的功能從core network server進(jìn)程中分離出來是因?yàn)橥瑫r(shí)接收IPC以及接收或等待來自設(shè)備驅(qū)動(dòng)的數(shù)據(jù)包對(duì)于JOS是非常困難的,因?yàn)镴OS中沒有select這樣能夠允許進(jìn)程監(jiān)聽多個(gè)輸入源并判斷輸入源是否已經(jīng)準(zhǔn)備就緒。
2.4 Timer Environment
timer進(jìn)程會(huì)定期向 core network server 進(jìn)程發(fā)送 NSREQ_TIMER 的消息通知它某個(gè)計(jì)時(shí)器已經(jīng)過時(shí),它用于實(shí)現(xiàn)各種網(wǎng)絡(luò)超時(shí)。
3 PCI接口、MMIO、DMA
以太網(wǎng)卡中數(shù)據(jù)鏈路層的芯片一般簡稱為MAC控制器,物理層的芯片簡稱為PHY。此外還有DMA,DMA會(huì)用到FIFO buffer,DMA用于提高傳輸效率,不用CPU控制,直接在網(wǎng)卡和主存之間傳輸數(shù)據(jù)。
EEPROM 用于存儲(chǔ)產(chǎn)品配置信息。分為幾個(gè)區(qū)域:
- 硬件訪問區(qū)域 - 加電后被網(wǎng)卡控制器加載,D3->D0傳輸。
- ASF訪問區(qū)域 - ASF模式啟動(dòng)后加載。
- 軟件訪問區(qū)域。
PCI接口
pci_init時(shí)掃描總線讀取外設(shè)信息,通過VENDER_ID和DEVICE_ID在pci_attach()查找設(shè)備,如果找到了設(shè)備,則會(huì)調(diào)用對(duì)應(yīng)設(shè)備的attach函數(shù)初始化對(duì)應(yīng)設(shè)備,然后在 struct pci_func中填充讀取到的配置信息。其中82450EM的 VENDER_ID 為 0x8086,DEVICE ID為0x100e,在5.2中可以找到。reg_base和reg_size數(shù)組存儲(chǔ)Base Address Register(BAR)的信息,BAR的作用就是用于說明該設(shè)備想在主存中映射多少內(nèi)存空間和起始位置,一個(gè)網(wǎng)卡通常有6個(gè)32位的BAR或者3個(gè)64位的BAR。reg_base記錄了memory-mapped IO region的基內(nèi)存地址或者基IO端口,reg_size則記錄了reg_base對(duì)應(yīng)的內(nèi)存區(qū)域的大小或者IO端口的數(shù)目,irq_line是分配給設(shè)備中斷用的IRQ線。
在pci_scan_bus中會(huì)設(shè)置好pic_func的dev_id,dev_class,dev,bus等值,而reg_base,reg_size,irq_line則是需要通過設(shè)備的attach函數(shù)調(diào)用pci_func_enable()中來初始化。如實(shí)驗(yàn)中的網(wǎng)卡的函數(shù)我們定義在 kern/e1000.c 中,名為 e1000_attach()。
MMIO
其中初始化了設(shè)備外,還要設(shè)置好MMIO映射,這里映射的物理地址是 reg_base[0](測(cè)試網(wǎng)卡的物理地址為 0xfebc0000),大小為 reg_size0,即我們映射了 BAR[0],第0個(gè)基地址寄存器,然后將MMIO映射的虛擬地址保存到一個(gè)全局變量中(映射虛擬地址是 0xef804000)。
struct pci_func {
struct pci_bus *bus; // Primary bus for bridges
uint32_t dev;
uint32_t func;
uint32_t dev_id;
uint32_t dev_class;
uint32_t reg_base[6];
uint32_t reg_size[6];
uint8_t irq_line;
};
pci讀取總線獲取PCI設(shè)備配置的操作通過兩個(gè)IO端口實(shí)現(xiàn),一個(gè)是地址端口0xcf8,一個(gè)是數(shù)據(jù)端口0xcfc。具體通過 pci_conf_read 和 pci_conf_write 兩個(gè)函數(shù)實(shí)現(xiàn),沒有探究細(xì)節(jié)了,大致原理就是在對(duì)應(yīng)IO端口讀取寫入配置。
static uint32_t pci_conf1_addr_ioport = 0x0cf8;
static uint32_t pci_conf1_data_ioport = 0x0cfc;
DMA
可以想象的是,從E1000的寄存器來接收和傳輸數(shù)據(jù),效率會(huì)很低,而且要求E1000內(nèi)部來緩存數(shù)據(jù)包。為此,E1000采用了DMA來直接在網(wǎng)卡和主存之間傳輸數(shù)據(jù),而不用CPU的參與。驅(qū)動(dòng)程序負(fù)責(zé)為發(fā)送隊(duì)列和接收隊(duì)列分配內(nèi)存,設(shè)置DMA描述符,并為E1000配置這些隊(duì)列的位置,之后的流程都是異步的。傳輸數(shù)據(jù)包時(shí),驅(qū)動(dòng)程序?qū)?shù)據(jù)包復(fù)制到傳輸隊(duì)列中的下一個(gè)DMA描述符中,并通知E1000另一個(gè)數(shù)據(jù)包可用,等到發(fā)送數(shù)據(jù)包的時(shí)候,E1000從DMA描述符復(fù)制出數(shù)據(jù)包。同樣,當(dāng)E1000接收到一個(gè)數(shù)據(jù)包時(shí),它將它復(fù)制到接收隊(duì)列中的下一個(gè)DMA描述符中,驅(qū)動(dòng)程序可以在下一次讀取它。
接收和發(fā)送隊(duì)列從頂層看來非常相似,兩者都由一系列描述符組成。盡管這些描述符的確切結(jié)構(gòu)各不相同,但每個(gè)描述符都包含一些標(biāo)志和包含分組數(shù)據(jù)的緩沖區(qū)的物理地址(要么是網(wǎng)卡待發(fā)送的分組數(shù)據(jù),要么由操作系統(tǒng)分配的緩沖區(qū)以便網(wǎng)卡存入接收到的數(shù)據(jù)包)。
隊(duì)列實(shí)現(xiàn)為循環(huán)數(shù)組,這意味著當(dāng)網(wǎng)卡或驅(qū)動(dòng)程序到達(dá)數(shù)組的末尾時(shí),它會(huì)轉(zhuǎn)回到頭部。兩者都有一個(gè)頭指針header和一個(gè)尾指針tail,數(shù)組項(xiàng)是DMA描述符。網(wǎng)卡總是消耗來自頭部的描述符并移動(dòng)頭指針,而驅(qū)動(dòng)程序總是將DMA描述符添加到尾部并移動(dòng)尾指針。傳輸隊(duì)列中的描述符表示等待發(fā)送的數(shù)據(jù)包(因此,在穩(wěn)定狀態(tài)下,傳輸隊(duì)列為空)。接收隊(duì)列中的描述符是網(wǎng)卡可以接收數(shù)據(jù)包的空閑描述符(因此,在穩(wěn)定狀態(tài)下,接收隊(duì)列由所有可用的接收描述符組成)。
這些數(shù)組指針以及描述符中數(shù)據(jù)包緩沖區(qū)的地址都必須是物理地址,因?yàn)橛布苯釉谖锢韮?nèi)存上執(zhí)行DMA,而不通過MMU,不經(jīng)過分頁轉(zhuǎn)換。
4 傳輸數(shù)據(jù)包
4.1 傳輸描述符格式和初始化
E1000的發(fā)送和接收數(shù)據(jù)包的功能基本是獨(dú)立的,因此我們可以分開來實(shí)現(xiàn)。我們首先實(shí)現(xiàn)傳輸數(shù)據(jù)包功能,因?yàn)槿绻幌葘?shí)現(xiàn)傳輸功能我們無法測(cè)試接收數(shù)據(jù)包功能。
首先,我們要按照文檔14.5節(jié)中描述的步驟初始化要發(fā)送的網(wǎng)卡(不用過多關(guān)注細(xì)節(jié))。傳輸初始化的第一步是設(shè)置傳輸隊(duì)列。隊(duì)列的結(jié)構(gòu)在3.4節(jié)中描述,描述符的結(jié)構(gòu)在3.3.3節(jié)中描述。我們不會(huì)使用E1000的TCP offload功能,因此關(guān)注legacy transform descriptor format即可。
為描述E1000的結(jié)構(gòu),使用C語言中的結(jié)構(gòu)體十分方便。比如對(duì)于文檔3.3.3節(jié)表3-8中描述的legacy transform descriptor format:
63 48 47 40 39 32 31 24 23 16 15 0
+---------------------------------------------------------------+
| Buffer address |
+---------------+-------+-------+-------+-------+---------------+
| Special | CSS | Status| Cmd | CSO | Length |
+---------------+-------+-------+-------+-------+---------------+
發(fā)送描述符可以用下面的結(jié)構(gòu)體來描述:
struct tx_desc
{
uint64_t addr;
uint16_t length;
uint8_t cso;
uint8_t cmd;
uint8_t status;
uint8_t css;
uint16_t special;
};
你的驅(qū)動(dòng)程序必須為發(fā)送描述符數(shù)組和發(fā)送描述符指向的數(shù)據(jù)包緩沖區(qū)保留內(nèi)存。有幾種方法可以做到這一點(diǎn),如動(dòng)態(tài)分配頁面或者簡單地在全局變量中聲明。無論哪種方式,請(qǐng)記住E1000直接訪問物理內(nèi)存,這意味著它訪問的任何緩沖區(qū)必須在物理內(nèi)存中連續(xù)。
還有多種方法來處理數(shù)據(jù)包緩沖區(qū)。比較簡單的方式是在驅(qū)動(dòng)程序初始化期間為每個(gè)描述符保留數(shù)據(jù)包緩沖區(qū)的空間,并簡單地將數(shù)據(jù)包數(shù)據(jù)復(fù)制到這些預(yù)分配的緩沖區(qū)中。以太網(wǎng)數(shù)據(jù)包的最大為1518字節(jié),可以根據(jù)這個(gè)設(shè)置緩沖區(qū)的大小。更復(fù)雜的驅(qū)動(dòng)程序可以動(dòng)態(tài)地分配數(shù)據(jù)包緩沖區(qū)或者傳遞由用戶空間直接提供的緩沖區(qū)(稱為“零拷貝”的技術(shù))。
根據(jù)文檔14.5中描述完成網(wǎng)卡初始化。寄存器初始化參照文檔13章,傳輸描述符及其數(shù)組參照3.3.3和3.4節(jié)。注意傳輸描述符數(shù)組的對(duì)齊要求和數(shù)組長度限制。TDLEN必須是128字節(jié)對(duì)齊,每個(gè)傳輸描述符長度為16字節(jié),傳輸描述符數(shù)組的描述符數(shù)目必須是8的整數(shù)倍,不過不要超過64個(gè),否則會(huì)影響ring overflow測(cè)試。對(duì)于TCTL.COLD,您可以認(rèn)為是全雙工操作。
查看文檔14.5節(jié),可以看到網(wǎng)卡初始化步驟如下:
- 為發(fā)送描述符隊(duì)列分配一塊內(nèi)存,并設(shè)置傳輸描述符基地址寄存器(Transmit Descriptor Base Address,TDBAL/TDBAH) 為分配內(nèi)存的地址。
- 設(shè)置傳輸描述符長度寄存器(Transmit Descriptor Length,TDLEN)寄存器的值為描述符隊(duì)列的大小,必須128字節(jié)對(duì)齊。
- 設(shè)置發(fā)送描述符的header和tail指針為0.
- 根據(jù)需要初始化傳輸控制寄存器( Transmit Control Register, TCTL):
- 設(shè)置TCTL.EN位為1以支持常規(guī)操作。
- 設(shè)置 Pad Short Packets(TCTL.PSP) 為1.
- 設(shè)置 Collision Threshold(TCTL.CT)位為需要的值。以太網(wǎng)標(biāo)準(zhǔn)是設(shè)置為0x10,這個(gè)設(shè)置在半雙工模式中有用。
- 設(shè)置 Collision Distance (TCTL.COLD)為期望的值。在全雙工模式設(shè)置為0x40,在1000M半雙工網(wǎng)絡(luò)這個(gè)值設(shè)置為0x200,在10/100M半雙工設(shè)置為0x40,我們這里設(shè)置為0x40。
- 設(shè)置 Transmit IPG(TIPG)寄存器的IPGT,IPGR1和IPGR2的值。TIPG用于設(shè)置
legal Inter Packet Gap。TIPG設(shè)置參考13.4.34中的表13-77,分別將ipgt設(shè)置為10,ipgr1設(shè)為4(ipgr2的2/3),ipgr2設(shè)置為6。
根據(jù)要求來設(shè)置傳輸描述符和描述符數(shù)組,采用簡單點(diǎn)的方式,描述符數(shù)組和packet buffer全部采用數(shù)組方式。當(dāng)我們傳輸數(shù)據(jù)包時(shí),如果設(shè)置了描述符的cmd參數(shù)為RS,則當(dāng)網(wǎng)卡發(fā)送完數(shù)據(jù)包時(shí),會(huì)設(shè)置DD位,即設(shè)置描述符中的status對(duì)應(yīng)位,我們可以根據(jù)DD位來判斷當(dāng)前描述符是否可以重用,如果DD置位了,則表示可以回收并重新使用了。
傳輸數(shù)據(jù)包函數(shù)如果正確的話,make E1000_DEBUG=TXERR,TX run-net_testoutput會(huì)輸出如下,其中index是描述符數(shù)組索引,后面的0x302040是packet buffer地址,而9000009是cmd/CSO/length值(因?yàn)槲覀冊(cè)O(shè)置了RS和EOP位,所以cmd為8位0x09,CSO為8位0x00,length為16為0x0009,表示長度為9),0是special/CSS/status值。
Transmitting packet 1
e1000: index 0: 0x302040 : 9000009 0
Transmitting packet 2
e1000: index 1: 0x30262e : 9000009 0
Transmitting packet 3
e1000: index 2: 0x302c1c : 9000009 0
如果遇到很多"e1000: tx disabled"提示信息,則說明你的TCTL寄存器沒有設(shè)置正確。
4.2 output helper進(jìn)程
現(xiàn)在在網(wǎng)卡驅(qū)動(dòng)傳輸端有了一個(gè)系統(tǒng)調(diào)用,輸出進(jìn)程的目標(biāo)就是循環(huán)執(zhí)行下面操作:
- 從網(wǎng)絡(luò)服務(wù)器進(jìn)程接收NSREQ_OUTPUT IPC消息
- 使用新添加的系統(tǒng)調(diào)用(sys_pkg_send)將IPC消息中附帶的數(shù)據(jù)包發(fā)送給網(wǎng)卡。
NSREQ_OUTPUT IPC消息是lwip的net/lwip/jos/jif/jif.c中的low_level_output發(fā)送的,IPC消息中會(huì)包含一個(gè)共享頁,這個(gè)頁內(nèi)容是一個(gè)union Nsipc,其中有一個(gè)struct jif_pkt pkt,而 jif_pkt結(jié)構(gòu)體定義如下,其中jp_len是數(shù)據(jù)包長度,而jp_data則是數(shù)據(jù)內(nèi)容。這里用到長度為0的數(shù)組的技巧。
struct jif_pkt {
int jp_len;
char jp_data[0];
};
注意網(wǎng)卡驅(qū)動(dòng),輸出進(jìn)程以及網(wǎng)絡(luò)服務(wù)器進(jìn)程之間的交互。當(dāng)網(wǎng)絡(luò)服務(wù)器進(jìn)程通過IPC發(fā)送數(shù)據(jù)包給輸出進(jìn)程時(shí),如果此時(shí)因?yàn)榫W(wǎng)卡驅(qū)動(dòng)沒有更多buffer導(dǎo)致輸出進(jìn)程掛起,則網(wǎng)絡(luò)服務(wù)器進(jìn)程必須阻塞等待。這里的流程是:
core network env -> output helper env -> e1000 driver
5 接收數(shù)據(jù)包及input helper進(jìn)程
類似傳輸數(shù)據(jù)包,接下來完成接收數(shù)據(jù)包流程。這里要設(shè)置接收描述符和接收描述符隊(duì)列,接收描述符和隊(duì)列結(jié)構(gòu)在文檔3.2節(jié)描述,而初始化細(xì)節(jié)在 14.4節(jié)。
接收的描述符的隊(duì)列大小這里設(shè)置的是128個(gè),另外,而E1000_RA 這個(gè)設(shè)置MAC地址時(shí)要注意,比如我們測(cè)試的MAC地址是 52:54:00:12:34:56,則ral處要設(shè)置為 0x12005452,而rah則要設(shè)置為 0x5634| E1000_RAH_AV,E1000_RAH_AV標(biāo)識(shí)地址有效。
RDH寄存器指向網(wǎng)卡可存放數(shù)據(jù)包的第一個(gè)描述符,當(dāng)網(wǎng)卡接收到數(shù)據(jù)包時(shí),會(huì)將數(shù)據(jù)包存入接收隊(duì)列,并將RDH寄存器的值加1,這個(gè)更新寄存器的值的操作是網(wǎng)卡硬件執(zhí)行的。
RDT寄存器則是存放的是網(wǎng)卡可用用來存放數(shù)據(jù)包的最后一個(gè)描述符的下一個(gè)描述符,這里我們?cè)O(shè)置為127,即浪費(fèi)一個(gè)描述符作為標(biāo)識(shí),我們的隊(duì)列最多可以存放127個(gè)數(shù)據(jù)包。
而測(cè)試程序net/testinput.c主要是做了下面幾個(gè)事情:
- 創(chuàng)建一個(gè)子進(jìn)程運(yùn)行 output(),一個(gè)子進(jìn)程運(yùn)行input(),然后通過lwip構(gòu)建一個(gè)ARP報(bào)文并發(fā)送給output environment。ARP報(bào)文通過 ipc_send()發(fā)送NSREQ_OUTPUT類型的IPC消息給output 進(jìn)程。
- output 進(jìn)程接收到IPC消息后,會(huì)讀取IPC映射頁中數(shù)據(jù)包內(nèi)容,調(diào)用系統(tǒng)調(diào)用 sys_pkt_send() 將數(shù)據(jù)包發(fā)送到網(wǎng)卡的發(fā)送描述符隊(duì)列中,發(fā)送程序就是我們實(shí)現(xiàn)的 e1000_transmit()函數(shù)。
- 而當(dāng)網(wǎng)卡接收到ARP請(qǐng)求后,會(huì)響應(yīng)請(qǐng)求并輸出響應(yīng)到我們?cè)O(shè)置的接收描述符隊(duì)列中。
- 網(wǎng)卡輸出完畢后,會(huì)設(shè)置接收描述符的DD標(biāo)記,此時(shí)input進(jìn)程從接收描述符接收到網(wǎng)卡的數(shù)據(jù)包,并將其發(fā)送給core network server 進(jìn)程。注意這里,每次接收后發(fā)送要間隔一段時(shí)間,因?yàn)榫W(wǎng)絡(luò)服務(wù)器進(jìn)程讀取數(shù)據(jù)需要一定時(shí)間。
6 WEB服務(wù)器
最后是實(shí)現(xiàn)web服務(wù)器,類似httpd,主要完成send_file和send_data函數(shù)。實(shí)現(xiàn)就是根據(jù)請(qǐng)求解析出文件名,然后調(diào)用 fstat 獲取文件大小類型等元數(shù)據(jù),并調(diào)用readn讀取文件以及使用writen寫入文件數(shù)據(jù)到socket中。
注意這里的accept,bind等函數(shù)都是 lib/sockets.c中定義的,最終都是通過IPC功能將請(qǐng)求發(fā)送至 core network server進(jìn)程(ns/serv.c),然后 core network server進(jìn)程再調(diào)用的lwip來實(shí)現(xiàn)相關(guān)功能。這里用到了線程,線程實(shí)現(xiàn)在 net/lwip/jos/arch/thread.c中。