文章目录
-
- 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