天天看點

MIT6.828學習之Lab6: Network Driver

文章目錄

    • 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

網絡伺服器實際上由四個環境組成:

  • core network server

    environment (includes

    socket call dispatcher

    and

    lwIP

    )
  • input

    environment
  • output

    environment
  • timer

    environment

下圖顯示了這些環境及其關系。該圖顯示了包括

裝置驅動程式

在内的整個系統,稍後将對此進行介紹。在這個實驗室中,您将實作用綠色突出顯示的部分。圖檔出自此處。

MIT6.828學習之Lab6: Network Driver

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

實驗過程點此處

  1. PCI

    是外圍裝置互連(Peripheral Component Interconnect)的簡稱。在一個PCI系統中,最多可以有

    256根PCI總線

    。在一根PCI總線上最多不超過

    32個實體裝置

    ,可以是一個網卡、顯示卡或者聲霸卡等。一個PCI實體裝置最多可提供

    8個功能

    。每個功能對應1個

    256位元組的PCI配置空間

    。謝謝 bysui
  2. E1000

    網卡

    ,是一個PCI裝置。PCI總線具有address、data和interrupt lines,允許

    CPU與PCI裝置通信

    ,并允許

    PCI裝置讀寫記憶體

    。PCI裝置在使用之前需要被發現和初始化。

    discovery

    是在PCI總線上尋找附加裝置的過程。

    initialization

    是配置設定I/O和記憶體空間,以及協商(negotiating)給裝置使用的IRQ(Interrupt ReQuest)線的過程。(所有IRQ線跟可程式設計中斷控制器PIC引腳相連)
  3. i386_init裡通過

    pci_init

    調用

    pci_scan_bus

    将周遊PCI總線discovery裝置E1000,然後根據

    VENDOR_ID

    =0x8086以及

    DEVICE_ID

    =0x100E搜尋

    pci_attach_vendor

    數組,比對成功,調用對應條目的attachfn函數即

    e1000_init(struct pci_func *pcif)

    執行裝置initialization。
  4. pci配置空間的讀寫
  5. E1000在這裡隻公開了一個功能。通過pci_scan_bus與pci_func_enable初始化該pci_func。尤其是後者初始化的三個條目,

    reg_base

    存儲記憶體映射I/O區域的base memory addresses(or base I/O ports for I/O port resources),主要是很多

    E1000的寄存器位置

    (位址),

    reg_size

    包含對應的reg_base基值的位元組大小或I/O端口數量,

    irq_line

    包含配置設定給裝置用于中斷的IRQ lines。
  6. 從E1000的寄存器寫和讀來發送和接收packet會很慢,并且需要E1000在内部緩沖packet資料。是以E1000使用

    Direct Memory Access

    (DMA)直接從記憶體讀寫資料包資料,而不涉及CPU。這就需要通過

    E1000驅動程式

    操作兩個

    DMA描述符ring

    來實作,并用這兩個描述符ring

    配置E1000

    (主要是配置寄存器)。
    MIT6.828學習之Lab6: Network Driver
  7. 在這裡先來談談transmitting packets,其實我感覺說send packets還好一些。

    output helper environment

    通過

    NSREQ_OUTPUT IPC消息

    接到即将要發送的資料包後,通過

    系統調用

    調用JOS kernel中的

    E1000驅動程式

    ,将資料放入

    TX descriptor list

    尾部描述符

    對應buffer,之後

    E1000網卡會從頭部取資料包

    發送。特别需要注意的是,如果尾部的描述符的DD未被設定(未被網卡取走),證明目前

    TX ring滿了

    ,這種情況我們的設計是

    系統調用傳回-1

    ,讓output helper 環境不停

    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通信原理簡介

MIT6.828學習之Lab6: Network Driver

Other Details

network server

lwip講解(lightweight TCP/IP protocol stack)

TCP/IP 協定棧主要是4層結構:應用層、傳輸層、網絡層、鍊路層。謝謝一像素的圖

MIT6.828學習之Lab6: Network Driver
  1. 應用層協定:應用層定義了各種各樣的協定來

    規範資料格式

    ,常見的有 HTTP、FTP、SMTP 等,HTTP 是一種比較常用的應用層協定。可以通過

    端口号

    知道收發資料的是那個應用程式。
  2. 傳輸層協定:決定傳輸方式。

    TCP協定

    ,就像有根傳送帶,讓資料傳輸非常

    可靠且有順序

    ,而且讀寫

    沒有資料邊界

    (通過I/Obuffer的幫助,可以任意次寫也能任意次讀)。

    UDP

    就像用運輸摩托,

    非常高效

    隻管發送

    ,不管順序,不管是否送到,

    有資料邊界

    (一次隻能送摩托容量的資料)
  3. 網絡層協定:通過

    IP協定确定路徑

    ,根據IP可以确定網絡位址(C類IP前24位),後8位為主機位址。根據

    ARP協定

    可知道

    同一子網下

    IP位址對應的

    MAC位址

    即以太網位址,緩存在路由器跟主機的ARP表中。

    路由協定

    則是可以通過

    網關

    借助路由器實作

    不同子網

    中的通信
  4. 鍊路層協定:

    對電信号進行分組

    并形成具有特定意義的

    資料幀

    ,然後

    以廣播的形式

    通過

    實體媒體

    發送給接收方。以太網規協定定,接入網絡的裝置都必須安裝

    網絡擴充卡,即網卡

    , 資料包必須是從一塊網卡傳送到另一塊網卡,是以才需要

    以太網(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,但是代碼太複雜看不清了。

MIT6.828學習之Lab6: Network Driver

硬體路徑:

網卡工作原理詳解

網卡的功能主要有兩個:

一是将電腦的資料封裝為幀,并通過網線(對無線網絡來說就是電磁波)将資料發送到網絡上去;

二是接收網絡上其它裝置傳過來的幀,并将幀重新組合成資料,發送到所在的電腦中。

MIT6.828學習之Lab6: Network Driver

Questions

1. 在httpd環境中調用socket隻需要提供IP位址和端口号,那到底什麼時候需要MAC位址呢?

完整的能在實體媒體中傳輸的資料幀應該是包含TCP、IP、以太網首部的。是以如果想要包上以太網首部,就必須得直到目的主機IP位址跟以太網位址,是以我認為如果找不到以太網位址,NS環境會先發一個ARP請求包,等得到目的MAC位址後,更新ARP表再繼續對資料添加以太網首部資訊(不确定)。

2. 有了 MAC 位址,為什麼還要用 IP 位址?

請看該知乎提問的二樓回答

Code

代碼見GitHub

繼續閱讀