天天看点

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

继续阅读