MIT6.828学习之Lab6: Network Driver

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-7make nc-80,它们允许你跟运行在你终端的这些端口的servers直接交互。(这些目标只连接到正在运行的QEMU实例;你必须单独QEMU本身)

mylab@ubuntu:/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

mylab@ubuntu:/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

下图显示了这些环境及其关系。该图显示了包括设备驱动程序在内的整个系统,稍后将对此进行介绍。在这个实验室中,您将实现用绿色突出显示的部分。图片出自此处
在这里插入图片描述

The Core Network Server Environment

The core network server environment 由 socket call dispatcherlwIP本身组成。 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.cnet/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(主要是配置寄存器)。
    在这里插入图片描述

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

在这里插入图片描述

Other Details

network server

lwip讲解(lightweight TCP/IP protocol stack)
TCP/IP 协议栈主要是4层结构:应用层、传输层、网络层、链路层。谢谢一像素的图
在这里插入图片描述

  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,但是代码太复杂看不清了。

在这里插入图片描述
硬件路径:
网卡工作原理详解

网卡的功能主要有两个:
一是将电脑的数据封装为帧,并通过网线(对无线网络来说就是电磁波)将数据发送到网络上去;
二是接收网络上其它设备传过来的帧,并将帧重新组合成数据,发送到所在的电脑中。

在这里插入图片描述

Questions

1. 在httpd环境中调用socket只需要提供IP地址和端口号,那到底什么时候需要MAC地址呢?
完整的能在物理介质中传输的数据帧应该是包含TCP、IP、以太网首部的。所以如果想要包上以太网首部,就必须得直到目的主机IP地址跟以太网地址,所以我认为如果找不到以太网地址,NS环境会先发一个ARP请求包,等得到目的MAC地址后,更新ARP表再继续对数据添加以太网首部信息(不确定)。

2. 有了 MAC 地址,为什么还要用 IP 地址?
请看该知乎提问的二楼回答

Code

代码见GitHub

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MIT 6.828是一门关于xv6操作系统的课程,该课程提供了关于xv6操作系统的中文指南和实验室。 xv6是一个操作系统的教学版本,MIT 6.828课程提供了xv6全文的翻译成书供学习使用。 在课程中,还提供了一些实验,供学生进行实践和学习。 关于xv6的具体实现细节,根据引用中的内容,在user.h文件中可以找到函数的定义。需要在date.c的代码中补充相应的函数。 另外,引用中提供了一个样例过程,展示了一系列操作的执行顺序,包括fork、exec、open、close、write等。这个样例过程可以帮助理解xv6操作系统的运行机制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [MIT-6.828:MIT 6.828操作系统课程](https://download.csdn.net/download/weixin_42139357/15728097)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [MIT6.828 Homework3 xv6 system calls](https://blog.csdn.net/qq_43012789/article/details/107746030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值