关于c:MIT-6181068286S081-操作系统工程-Lab7-Networking

5次阅读

共计 6588 个字符,预计需要花费 17 分钟才能阅读完成。

networking

在本试验中,你将为网络接口卡(NIC)编写 xv6 设施驱动程序。

要求

前言

您将应用名为 E1000 的网络设备来解决网络通信。对于 xv6(以及您编写的驱动程序),E1000 看起来像连贯到实在以太网局域网(LAN)的实在硬件。实际上,您的驱动程序将与之通信的 E1000 是 qemu 提供的模仿,连贯到 LAN 也由 qemu 模仿。在此模仿 LAN 上,xv6(“客机”)的 IP 地址为 10.0.2.15。Qemu 还安顿运行 qemu 的计算机呈现在 IP 地址为 10.0.2.2 的局域网上。当 xv6 应用 E1000 将数据包发送到 10.0.2.2 时,qemu 会将数据包传送到运行 qemu 的(实在)计算机上的相应应用程序(“主机”)。

您将应用 QEMU 的“用户模式网络堆栈”。QEMU 的文档在此处提供了无关用户模式堆栈的更多信息。咱们更新了 Makefile 以启用 QEMU 的用户模式网络堆栈和 E1000 网卡。

Makefile 将 QEMU 配置为将所有传入和传出数据包记录到试验目录中的 packets.pcap 文件。查看这些记录以确认 xv6 正在传输和接管您冀望的数据包可能会有所帮忙。显示记录的数据包的命令:`tcpdump -XXnr packets.pcap
`

咱们已将一些文件增加到此试验的 xv6 存储库中。文件 kernel/e1000.c 蕴含 E1000 的初始化代码以及用于发送和接管数据包的空函数,您将补充这些函数。kernel/e1000_dev.h 蕴含由 E1000 定义并在英特尔 E1000 软件开发人员手册中形容的寄存器和标记位的定义。kernel/net.ckernel/net.h 蕴含一个简略的网络堆栈,用于实现 IP、UDP 和 ARP 协定。这些文件还蕴含用于保留数据包的灵便数据结构的代码,称为 mbuf。最初,kernel/pci.c 蕴含当 xv6 启动时在 PCI 总线上搜寻 E1000 卡的代码。

需要

你的工作是在 kernel/e1000.c 中实现 e1000_transmit()e1000_recv(),以便驱动程序能够传输和接管数据包。

在编写代码时,您会发现自己参考了 E1000 软件开发人员手册。特地有帮忙的可能是以下局部:

  • 第 2 局部是必不可少的,它概述了整个设施。
  • 第 3.2 节概述了数据包接管。
  • 第 3.3 节概述了数据包传输,以及第 3.4 节。
  • 第 13 节概述了 E1000 应用的寄存器。
  • 第 14 节能够帮忙您理解咱们提供的 init 代码。

浏览 E1000 软件开发人员手册。本手册介绍了几个密切相关的以太网控制器。QEMU 模仿 82540EM。当初浏览第 2 章以感触该设施。要编写驱动程序,您须要相熟第 3 章和第 14 章以及 4.1(只管不是 4.1 的大节)。您还须要应用第 13 章作为参考。其余章节次要介绍 E1000 的组件,您的驱动程序不用与之交互。一开始不要放心细节; 只需理解文档的构造,以便后续查找。E1000 具备许多高级性能,其中大部分性能都能够疏忽。实现本试验只须要一小部分基本功能。

咱们在 e1000.c 中为您提供的 e1000_init() 函数将 E1000 配置为: 读取要从 RAM 传输的数据包,并将收到的数据包写入 RAM。这种技术称为 DMA,用于间接内存拜访,指的是 E1000 硬件间接向 RAM 写入和读取数据包的事实。

因为数据包突发的达到速度可能快于驱动程序解决它们的速度,因而 e1000_init() 为 E1000 提供了多个缓冲区,E1000 能够将数据包写入其中。E1000 要求这些缓冲区由 RAM 中的“描述符”数组形容; 每个描述符都蕴含 RAM 中的一个地址,E1000 能够在其中写入收到的数据包。struct rx_desc形容了描述符格局。描述符数组称为接管环或接管队列。它是一个圆形环,当卡或驱动程序达到阵列的开端时,它会绕回结尾。e1000_init() 应用 mbufalloc() 将 E1000 的 mbuf 数据包缓冲区调配给 DMA。还有一个传输环,驱动程序应将它心愿 E1000 发送的数据包放入其中。e1000_init() 将两个环配置为大小为 RX_RING_SIZETX_RING_SIZE

net.c 中的网络堆栈须要发送数据包时,它会用一个 mbuf 调用 e1000_transmit(),该 mbuf 保留要发送的数据包。你的传输代码必须在 TX(传输)环的描述符中搁置指向数据包数据的指针。struct tx_desc形容描述符格局。您须要确保最终开释每个 mbuf,但仅在 E1000 实现数据包传输后(E1000 在描述符中设置 E1000_TXD_STAT_DD 位以批示这一点)。每当 E1000 从以太网接管到数据包时,它会应用 DMA 技术将数据包间接写入到下一个 RX(接管)环描述符中的 addr 指向的内存。如果 E1000 中断尚未挂起,则 E1000 会要求 PLIC 在启用中断后立刻传递一个。您的 e1000_recv() 代码必须扫描 RX 环,并通过调用 net_rx() 将每个新数据包的 mbuf 传送到网络堆栈(在 net.c 中)。而后,您须要调配一个新的 mbuf 并将其放入描述符中,以便当 E1000 再次达到 RX 环中的该点时,它会找到一个新的缓冲区,将新数据包 DMA 入其中。除了在 RAM 中读取和写入描述符环外,驱动程序还须要通过其内存映射管制寄存器与 E1000 交互,以检测何时收到的数据包可用,并告诉 E1000 驱动程序已应用要发送的数据包填充了一些 TX 描述符。全局变量 regs 蕴含指向 E1000 第一个管制寄存器的指针; 驱动程序能够通过 regs 当作数组索引来获取其余寄存器。您须要应用索引E1000_RDT, E1000_TDT

要测试驱动程序,请在一个窗口中运行 make server,在另一个窗口中运行 make qemu 而后在 xv6 中运行 nettestsnettests 中的第一个测试尝试将 UDP 数据包发送到主机操作系统,该数据包发送到 make server 运行的程序。如果尚未实现试验,E1000 驱动程序实际上不会发送数据包,并且不会产生太多操作。

实现试验后,E1000 驱动程序将发送数据包,qemu 将其传送到您的主机,make server 将看到它,它将发送响应数据包,E1000 驱动程序和 nettests 将看到响应数据包。然而,在主机发送回复之前,它会向 xv6 发送“ARP”申请数据包以找出其 48 位以太网地址,并冀望 xv6 应用 ARP 回复进行响应。kernel/net.c 将在您实现 E1000 驱动程序的工作后处理此问题。如果一切顺利,nettests 将打印 testing ping:okmake server将打印 a message from xv6!

tcpdump -XXnr packets.pcap 该当有以下输入:

reading from file packets.pcap, link-type EN10MB (Ethernet)
15:27:40.861988 IP 10.0.2.15.2000 > 10.0.2.2.25603: UDP, length 19
        0x0000:  ffff ffff ffff 5254 0012 3456 0800 4500  ......RT..4V..E.
        0x0010:  002f 0000 0000 6411 3eae 0a00 020f 0a00  ./....d.>.......
        0x0020:  0202 07d0 6403 001b 0000 6120 6d65 7373  ....d.....a.mess
        0x0030:  6167 6520 6672 6f6d 2078 7636 21         age.from.xv6!
15:27:40.862370 ARP, Request who-has 10.0.2.15 tell 10.0.2.2, length 28
        0x0000:  ffff ffff ffff 5255 0a00 0202 0806 0001  ......RU........
        0x0010:  0800 0604 0001 5255 0a00 0202 0a00 0202  ......RU........
        0x0020:  0000 0000 0000 0a00 020f                 ..........
15:27:40.862844 ARP, Reply 10.0.2.15 is-at 52:54:00:12:34:56, length 28
        0x0000:  ffff ffff ffff 5254 0012 3456 0806 0001  ......RT..4V....
        0x0010:  0800 0604 0002 5254 0012 3456 0a00 020f  ......RT..4V....
        0x0020:  5255 0a00 0202 0a00 0202                 RU........
15:27:40.863036 IP 10.0.2.2.25603 > 10.0.2.15.2000: UDP, length 17
        0x0000:  5254 0012 3456 5255 0a00 0202 0800 4500  RT..4VRU......E.
        0x0010:  002d 0000 0000 4011 62b0 0a00 0202 0a00  .-....@.b.......
        0x0020:  020f 6403 07d0 0019 3406 7468 6973 2069  ..d.....4.this.i
        0x0030:  7320 7468 6520 686f 7374 21              s.the.host!

您的输入看起来会有些不同,但它应该蕴含字符串“ARP, Request”,“ARP, Reply”,“UDP”,“a.message.from.xv6”和“this.is.the.host”。

nettests 执行其余一些测试,最终通过(实在)互联网向 Google 的名称服务器之一发送 DNS 申请。应确保代码通过所有这些测试,之后应看到以下输入:

$ nettests
nettests running on port 25603
testing ping: OK
testing single-process pings: OK
testing multi-process pings: OK
testing DNS
DNS arecord for pdos.csail.mit.edu. is 128.52.129.126
DNS OK
all tests passed.

提醒

首先将 print 语句增加到 e1000_transmit()e1000_recv(),并运行 make server 和(在 xv6 中)nettests。您应该从打印语句中看到 nettests 生成了对 e1000_transmit 的调用。

对实现 e1000_transmit 的提醒:

  • 首先通过读取 E1000_TDT 管制寄存器,向 E1000 询问它期待下一个数据包的 TX 环索引。
  • 而后查看环是否溢出。如果在按 E1000_TDT 索引的描述符中没有设置E1000_TXD_STAT_DD,则 E1000 尚未实现之前的传输申请,因而返回谬误。
  • 否则,应用 mbuffree() 开释从该描述符传输的最初一个 mbuf(如果有)。
  • 而后填写描述符。m->head 指向内存中数据包的内容,m->len 是数据包长度。设置必要的 cmd 标记(查看 E1000 手册中的第 3.3 节)并存储指向 mbuf 的指针以供当前开释。
  • 最初,通过 E100_TDT + 1 再对TX_RING_SIZE 取余来更新环地位
  • 如果 e1000_transmit() 胜利地将 mbuf 增加到环中,则返回 0。失败时(例如,没有可用于传输 mbof 的描述符),返回 -1,以便调用方晓得开释 mbuf。

对实现 e1000_recv 的提醒:

  • 首先向 E1000 询问下一个期待接管的数据包(如果有)所在的环索引,办法是获取 E1000_RDT 管制寄存器, + 1 后对 RX_RING_SIZE 取余。
  • 而后,通过查看描述符状态局部中的 E1000_RXD_STAT_DD 位来查看新数据包是否可用。如果没有,请进行。
  • 否则,请将 mbuf 的 m->len 更新为描述符中报告的长度。应用 net_rx() 将 mbuf 传送到网络堆栈。
  • 而后应用 mbufalloc() 调配一个新的 mbuf 来替换刚刚给 net_rx() 的 mbuf。将其数据指针(m->head)放到描述符中。将描述符的状态位清零。
  • 最初,将 E1000_RDT 寄存器更新为最初一个解决的描述符环的索引。
  • e1000_init() 应用 mbufs 初始化 RX 环,您须要查看它是如何做到这一点的,兴许还能够借用代码。
  • 在某些时候,达到的数据包总数将超过环大小(16); 确保你的代码能够解决这个问题。

您须要锁来应答 xv6 可能从多个过程应用 E1000 的可能性,或者在中断达到时可能在内核线程中应用 E1000。

实现

e1000_transmit()的实现

int e1000_transmit(struct mbuf* m) {
    //
    // Your code here.
    //
    // the mbuf contains an ethernet frame; program it into
    // the TX descriptor ring so that the e1000 sends it. Stash
    // a pointer so that it can be freed after sending.
    //
    uint32 idx;
    struct tx_desc* desc;

    acquire(&e1000_lock);
    idx = regs[E1000_TDT];  // 获取寄存器中保留的 TX 环索引
    desc = tx_ring + idx;   // 失去以后要发送的描述符
    if (idx >= TX_RING_SIZE) {  // 索引溢出解决
        panic("transmit: 环溢出");
    }
    if (!(desc->status & E1000_TXD_STAT_DD)) { // 之前的传输申请未实现
        release(&e1000_lock);
        return -1;
    }
    // 将传入的 mbuf 信息填写到描述符
    desc->addr = (uint64)m->head;
    desc->length = (uint16)m->len;
    desc->cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;
    // 贮存传入的 mbuf 指针以供之后开释
    if (tx_mbufs[idx]) { // 若有 mbuf 未开释
        mbuffree(tx_mbufs[idx]);
    }
    tx_mbufs[idx] = m;
    // 更新环指针
    regs[E1000_TDT] = (regs[E1000_TDT] + 1) % TX_RING_SIZE;
    release(&e1000_lock);
    return 0;
}

e1000_recv()的实现

static void e1000_recv(void) {
    //
    // Your code here.
    //
    // Check for packets that have arrived from the e1000
    // Create and deliver an mbuf for each packet (using
    // net_rx()).
    //
    uint32 idx;
    struct rx_desc* desc;
    
    while (1) {idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE; // 获取 RX 环索引
        desc = rx_ring + idx;  // 获取描述符
        if (!(desc->status & E1000_RXD_STAT_DD)) {
            // 接管未实现
            break;
        }
        rx_mbufs[idx]->len = desc->length;  // 将对应 mbuf 更新为接管后的长度
        net_rx(rx_mbufs[idx]);  // 提交到网络协议栈
        // 将该地位的 mbuf 指针换成一个新的
        // 然而因为网络协议栈要用所以不能开释内存
        rx_mbufs[idx] = mbufalloc(0);  
        // 更新文件描述符,以对应新的空白 mbuf           
        desc->addr = (uint64)rx_mbufs[idx]->head;
        desc->status = 0;
        // 更新 RX 环索引
        regs[E1000_RDT] = idx;
    }
}

遇到的问题

  1. 对于是否用锁的问题
    最开始我在e1000_recv() 中也习惯性地应用了锁。而后就呈现了如下谬误:

    但实际上只在中断解决时接管(只在 e1000_intr() 中被调用),并不是并发的,所以不须要锁。(而且就算是并发的,因为收和发的环不一样,该当是两把锁才对)

后果

  • 运行后果
  • 测试后果

集体播种

  1. 理解了网卡驱动中收发数据的根本工作原理。
  2. 通过两组(收、发)描述符和数据缓存环(实质上是数组)来记录接管和发送的数据包,而后通过寄存器来获取以后须要解决的数据在环中的地位。
正文完
 0