文章目錄
-
- Introduction
-
- Getting Started
- QEMU's virtual network
- Packet Inspection(封包檢測)
- Debugging the E1000
- The Network Server
-
- The Core Network Server Environment
- The Output Environment
- The Input Environment
- The Timer Environment
- Part A: Initialization and transmitting packets
- Part B: Receiving packets and the web server
-
- 一、Receiving packets
- 二、web server
-
- socket
- Other Details
-
- network server
- E1000及其收發packet的軟體路徑、硬體路徑
- Questions
- Code
Introduction
這個lab是預設你可以自己做的最後一個項目。
既然你有一個檔案系統,OS還應該有
network stack
。在這個lab中,你将為網絡接口卡(
network interface card
)寫一個
驅動程式
。這個card是基于Intel 82540EM晶片,也被稱為E1000。
Getting Started
然而網卡驅動程式(
network card driver
)并不足以讓你的OS連上Internet。在新的lab6代碼中,我們為你提供了一個
network stack
和一個
network server
。仔細檢視net/目錄下的内容,還有kern/目錄下的新檔案。
除了
寫驅動程式
,你還需要為通路你的驅動建立一個
系統調用接口
。你會實作缺失的
網絡伺服器代碼
,以便在網絡堆棧和你的驅動程式之間傳輸資料包(packets)。你也會通過完成一個
web server
将所有内容連在一起。使用這個新的web server,你将能從你的檔案系統中服務檔案。
大部分核心裝置驅動程式代碼你得自己從頭開始寫。這個lab比之前的labs提供的引導更少:沒有骨架檔案,沒有寫好的系統調用接口,也沒有設計決策。是以,我們建議你在開始任何單個exercise之前把作業的全部内容閱讀一遍。
QEMU’s virtual network
我們将使用QEMU的
使用者模式網絡堆棧
,因為它的運作
不需任何管理權限
。QEMU的文檔中有更多關于user-net的内容。我們已經更新了makefile,去啟用QEMU的使用者模式網絡堆棧和虛拟E1000網卡。
預設的,QEMU提供一個運作在IP 10.0.2.2上的虛拟路由器(
virtual router
),并且為JOS配置設定IP位址10.0.2.15。為了簡單,我們将這些預設值寫死(hard-code)到net/ns.h中的network server。
盡管QEMU的虛拟網絡允許JOS與Internet做任意連接配接,JOS的10.0.2.15位址在運作于QEMU内部的虛拟網絡之外沒有任何意義(也就是說,QEMU充當NAT),不懂,是以我們不能直接連接配接運作在JOS内的servers,甚至是運作QEMU的主機。為了解決這個問題,我們将QEMU配置為在一些
host machine的端口
上運作a server,該伺服器簡單連接配接JOS上的某些端口,并在你的實際主機(real host)與虛拟網絡之間來回傳回資料。
你會運作JOS servers在ports 7(echo) 和 80 (http)。可以運作
make which-ports
去找到QEMU會轉發到你的開發主機上的哪些端口。為了友善,makefile也提供
make nc-7
和
make nc-80
,它們允許你跟運作在你終端的這些端口的servers直接互動。(這些目标隻連接配接到正在運作的QEMU執行個體;你必須單獨QEMU本身)
[email protected]:/mnt/hgfs/share/lab2$ make which-ports
Local port 26001 forwards to JOS port 7 (echo server)
Local port 26002 forwards to JOS port 80 (web server)
Packet Inspection(封包檢測)
makefile也将QEMU的網絡堆棧配置為記錄所有輸入和輸出包(incoming and outgoing packets)到你lab目錄中的
qemu.pcap
。
為了得到一個hex/ASCII dump(轉儲)的被捕獲資料包,可以這樣使用tcpdump:
tcpdump -XXnr qemu.pcap
[email protected]:/mnt/hgfs/share/lab2$ tcpdump -XXnr qemu.pcap
reading from file qemu.pcap, link-type EN10MB (Ethernet)
Debugging the E1000
我們很幸運可以使用模拟硬體。因為E1000現在是運作在軟體中的,是以模拟的E1000可以以一種使用者可讀的格式報告它的
内部狀态
和它遇到的
任何問題
。這樣會很奢侈是以不會驅動開發者寫在bare metal(裸金屬)上。
E1000可以生成很多調試輸出,是以必須啟用特定的
logging channels
。你可能會發現一些有用的channels是:
Flag Meaning
tx Log packet transmit(傳輸) operations
txerr Log transmit ring(傳輸環路) errors
rx Log changes to RCTL
rxfilter Log filtering(過濾) of incoming packets
rxerr Log receive ring errors
unknown Log reads and writes of unknown registers
eeprom Log reads from the EEPROM
interrupt Log interrupts and changes to interrupt registers.
例如,要啟用“tx”和“txerr”日志記錄,請使用
make E1000_DEBUG=tx,txerr,rx,rxfilter,rxerr,unknown,eeprom,interrupt
注:E1000_DEBUG标志僅在QEMU的6.828版本中有效。
您可以進一步使用軟體仿真硬體進行調試。如果您遇到瓶頸,并且不了解為什麼E1000沒有按照預期的方式響應,您可以檢視QEMU在
hw/net/e1000.c
中的E1000實作。找不到。。。
The Network Server
從零開始編寫網絡堆棧是一項艱苦的工作。相反,我們将使用
lwIP
,這是一個開源的
輕量級TCP/IP協定套件
,其中包括一個network stack。你可以在這找到更多關于lwIP的資訊。在這個任務中,就我們而言,lwIP是
a black box
,它實作了
BSD socket interface
,并具有
a packet input port and packet output port
。
網絡伺服器實際上由四個環境組成:
-
environment (includescore network server
andsocket call dispatcher
)lwIP
-
environmentinput
-
environmentoutput
-
environmenttimer
下圖顯示了這些環境及其關系。該圖顯示了包括
裝置驅動程式
在内的整個系統,稍後将對此進行介紹。在這個實驗室中,您将實作用綠色突出顯示的部分。圖檔出自此處。
The Core Network Server Environment
The core network server environment 由
socket call dispatcher
和
lwIP
本身組成。 socket call dispatcher 的工作原理與檔案伺服器完全相同。使用者環境使用
stubs
(在lib/nsipc.c中)向核心網絡環境發送IPC消息。如果您檢視
lib/nsipc.c
,您将看到我們找到核心網絡伺服器的方式與找到檔案伺服器的方式相同:i386_init使用
NS_TYPE_NS
建立NS(network server)環境,是以我們掃描envs,尋找這種特殊類型的環境。對于每個使用者環境IPC,網絡伺服器中的dispatcher代表使用者調用lwIP提供的适當
BSD socket interface function
。
普通使用者環境不直接使用nsipc_* calls。相反,它們使用
lib/sockets.c
中的函數,它們提供了一個
file descriptor-based
的sockets API。是以,使用者環境通過檔案描述符引用sockets,就像它們引用磁盤上的檔案一樣。許多操作(connect, accept等)是特定于sockets的,但是
read、writes和close
都要經過
lib/fd.c
中的普通檔案描述符
device-dispatch code
。就像檔案伺服器為所有打開的檔案保留惟一ID,lwIP也為所有
打開的套接字
生成
惟一ID
。在檔案伺服器和網絡伺服器中,我們使用存儲在
struct Fd
中的資訊将每個環境的檔案描述符
映射到
這些惟一的ID空間。
盡管看起來檔案伺服器和網絡伺服器的IPC排程程式的行為相同,但還是有一個
關鍵差別
。BSD sockets calls 比如 accept和recv 可以
無限期地阻塞
。如果dispatcher(排程器)讓lwIP執行這些阻塞調用中的一個,排程器也會阻塞,對于整個系統,一次隻能有一個未完成的網絡調用。對于每個傳入的IPC消息,
dispatcher
建立一個線程,并在
新建立的線程
中處理請求。如果
線程阻塞
,則隻有該線程處于休眠狀态,而其他線程繼續運作。
除了core network environment 之外,還有三個
helper環境
。除了接受來自使用者應用程式的消息外,核心網絡環境的dispatcher還接受來自
input和timer環境
的消息。
The Output Environment
當服務于
使用者環境sockets calls
時,
lwIP
将生成
packets
供
網卡傳輸
(network card transmit)。
LwIP
将使用
NSREQ_OUTPUT
IPC消息将每個要傳輸的包發送到
output helper environment
,IPC消息的
頁面參數
中附加了這個包。output helper environment負責
接收
這些IPC消息,并通過您即将建立的系統調用接口将包
轉發
到
裝置驅動程式
。
The Input Environment
netword card
接收到的資料包需要
注入到lwIP
中。對于裝置驅動程式接收到的每個包,輸入環境将包
從核心空間中取出
(使用您将實作的核心系統調用),并使用
NSREQ_INPUT IPC消息
将包發送到core server environment。
包輸入功能與核心網絡環境分離,因為JOS使得
同時接受
IPC消息和輪詢或等待來自裝置驅動程式的包變得困難。我們在JOS中沒有一個select系統調用,該調用允許環境監視多個輸入源,以确定哪些輸入已準備好被處理。
如果您檢視一下
net/input.c
和
net/output.c
,您将會發現這兩者都需要實作。該實作主要取決于您的系統調用接口。在實作驅動程式和系統調用接口之後,您将為這兩個helper環境編寫代碼。
The Timer Environment
timer環境
定期
向核心網絡伺服器發送
NSREQ_TIMER IPC消息
,通知它一個計時器已經過期(expired)。lwIP使用來自這個線程的計時器消息來實作各種網絡逾時(
network timeouts
)。
Part A: Initialization and transmitting packets
實驗過程點此處
-
是外圍裝置互連(Peripheral Component Interconnect)的簡稱。在一個PCI系統中,最多可以有PCI
。在一根PCI總線上最多不超過256根PCI總線
,可以是一個網卡、顯示卡或者聲霸卡等。一個PCI實體裝置最多可提供32個實體裝置
。每個功能對應1個8個功能
。謝謝 bysui256位元組的PCI配置空間
-
是E1000
,是一個PCI裝置。PCI總線具有address、data和interrupt lines,允許網卡
,并允許CPU與PCI裝置通信
。PCI裝置在使用之前需要被發現和初始化。PCI裝置讀寫記憶體
是在PCI總線上尋找附加裝置的過程。discovery
是配置設定I/O和記憶體空間,以及協商(negotiating)給裝置使用的IRQ(Interrupt ReQuest)線的過程。(所有IRQ線跟可程式設計中斷控制器PIC引腳相連)initialization
- i386_init裡通過
調用pci_init
将周遊PCI總線discovery裝置E1000,然後根據pci_scan_bus
=0x8086以及VENDOR_ID
=0x100E搜尋DEVICE_ID
數組,比對成功,調用對應條目的attachfn函數即pci_attach_vendor
執行裝置initialization。e1000_init(struct pci_func *pcif)
- pci配置空間的讀寫
- E1000在這裡隻公開了一個功能。通過pci_scan_bus與pci_func_enable初始化該pci_func。尤其是後者初始化的三個條目,
存儲記憶體映射I/O區域的base memory addresses(or base I/O ports for I/O port resources),主要是很多reg_base
(位址),E1000的寄存器位置
包含對應的reg_base基值的位元組大小或I/O端口數量,reg_size
包含配置設定給裝置用于中斷的IRQ lines。irq_line
- 從E1000的寄存器寫和讀來發送和接收packet會很慢,并且需要E1000在内部緩沖packet資料。是以E1000使用
(DMA)直接從記憶體讀寫資料包資料,而不涉及CPU。這就需要通過Direct Memory Access
操作兩個E1000驅動程式
來實作,并用這兩個描述符ringDMA描述符ring
(主要是配置寄存器)。配置E1000
- 在這裡先來談談transmitting packets,其實我感覺說send packets還好一些。
通過output helper environment
接到即将要發送的資料包後,通過NSREQ_OUTPUT IPC消息
調用JOS kernel中的系統調用
,将資料放入E1000驅動程式
的TX descriptor list
對應buffer,之後尾部描述符
發送。特别需要注意的是,如果尾部的描述符的DD未被設定(未被網卡取走),證明目前E1000網卡會從頭部取資料包
,這種情況我們的設計是TX ring滿了
,讓output helper 環境不停系統調用傳回-1
,直到成功為止。try again
Part B: Receiving packets and the web server
實驗過程點此處
一、Receiving packets
跟transmitting packets基本一樣。首先在JOS 核心中驅動程式在E1000_init()
設定接收隊列并配置E1000
(主要是配置寄存器)。
然後
input helper environment
通過
系統調用
調用JOS kernel中的
E1000驅動程式
,從
TX descriptor list
的
尾部描述符的下一個
對應buffer中取出資料(本來應該是取尾部,但是由于尾部初始化指向的最後一個有效描述符,而非有效描述符的下一個,是以隻好取尾部的下一個),然後通過
NSREQ_INPUT IPC消息
發到
network server
對資料包進行“解封”,最後取出純應用層資料給httpd使用者程式。
特别需要注意的是,如果尾部的描述符的DD未被設定(網卡未放入資料),證明目前
RX ring空了
,這種情況我們的設計也是
系統調用傳回-1
,讓input helper 環境不停
try again
,直到成功為止。但是這樣是不好的,接收隊列可能很長一段時間為空。比較好的方法是挂起調用者環境,但是還得設定接收中斷取喚醒挂起環境比較複雜,是以還是采用的try again。
二、web server
這就涉及到
網絡程式設計
的知識了。
套接字socket
作為對TCP/IP 協定的抽象或者說是lwip 協定棧内的一種連接配接方式,使用者程序借助socket,通過
IP位址跟端口
就可以輕松變成web server或者web client
socket
深入淺出 TCP/IP 協定棧
網絡程式設計接口
HTTP、TCP和Socket的概念和原理及其差別
Socket通信原理簡介
Other Details
network server
lwip講解(lightweight TCP/IP protocol stack)
TCP/IP 協定棧主要是4層結構:應用層、傳輸層、網絡層、鍊路層。謝謝一像素的圖
- 應用層協定:應用層定義了各種各樣的協定來
,常見的有 HTTP、FTP、SMTP 等,HTTP 是一種比較常用的應用層協定。可以通過規範資料格式
知道收發資料的是那個應用程式。端口号
- 傳輸層協定:決定傳輸方式。
,就像有根傳送帶,讓資料傳輸非常TCP協定
,而且讀寫可靠且有順序
(通過I/Obuffer的幫助,可以任意次寫也能任意次讀)。沒有資料邊界
就像用運輸摩托,UDP
,非常高效
,不管順序,不管是否送到,隻管發送
(一次隻能送摩托容量的資料)有資料邊界
- 網絡層協定:通過
,根據IP可以确定網絡位址(C類IP前24位),後8位為主機位址。根據IP協定确定路徑
可知道ARP協定
IP位址對應的同一子網下
即以太網位址,緩存在路由器跟主機的ARP表中。MAC位址
則是可以通過路由協定
借助路由器實作網關
中的通信不同子網
- 鍊路層協定:
并形成具有特定意義的對電信号進行分組
,然後資料幀
通過以廣播的形式
發送給接收方。以太網規協定定,接入網絡的裝置都必須安裝實體媒體
, 資料包必須是從一塊網卡傳送到另一塊網卡,是以才需要網絡擴充卡,即網卡
。引用自一像素以太網(MAC)位址
lwip協定棧就是對這些協定的封裝。在Lab 6中,如果是httpd環境接收資料,那麼
input helper 環境
從
RX ring
中取出資料包(此時的資料包是鍊路層分組出的資料幀,有着TCP頭部、IP頭部、以太網頭部),通過
NSREQ_INPUT IPC消息
發給network server環境,由
NS環境
調用lwip代碼對資料包進行
"解包裝"
,一層一層驗證、擷取資訊、并去掉這些頭部,知道最後隻剩符合
http格式的資料
後存在socket的netbuf中,等待
httpd環境
通過
NSREQ_RECV IPC消息
擷取。
如果是發資料,就由
httpd環境
通過
NSREQ_SEND IPC消息
将資料存到socket的netbuf中,由
NS 環境
調用lwip代碼對其進行
"加包裝"
,一層一層的添加頭部,直到變成符合鍊路層發送的資料幀,通過
NSREQ_OUTPUT IPC消息
發給
output helper 環境
,由其放入到
TX ring
中,等待
網卡
進行發送。
E1000及其收發packet的軟體路徑、硬體路徑
花了很長的時間探索lwip 協定棧中代碼,想跟到資料包從E1000網卡到DMA descriptor ring到input helper environment到lwip中"解封裝"處理後到httpd使用者程序的全過程,但是中間還是斷了,跟不到,到tcp_input()函數就不知道資料去哪了,應該去掉tcp頭部後進入msg或者進入socket中的netbuf,但是代碼太複雜看不清了。
硬體路徑:
網卡工作原理詳解
網卡的功能主要有兩個:
一是将電腦的資料封裝為幀,并通過網線(對無線網絡來說就是電磁波)将資料發送到網絡上去;
二是接收網絡上其它裝置傳過來的幀,并将幀重新組合成資料,發送到所在的電腦中。
Questions
1. 在httpd環境中調用socket隻需要提供IP位址和端口号,那到底什麼時候需要MAC位址呢?
完整的能在實體媒體中傳輸的資料幀應該是包含TCP、IP、以太網首部的。是以如果想要包上以太網首部,就必須得直到目的主機IP位址跟以太網位址,是以我認為如果找不到以太網位址,NS環境會先發一個ARP請求包,等得到目的MAC位址後,更新ARP表再繼續對資料添加以太網首部資訊(不确定)。
2. 有了 MAC 位址,為什麼還要用 IP 位址?
請看該知乎提問的二樓回答
Code
代碼見GitHub