6.S081的Lab学习——Lab11: Network

news2024/11/15 11:11:52

文章目录

  • 前言
  • Network
    • 提示:
      • 实现e1000_transmit的一些提示:
      • 实现e1000_recv的一些提示:
    • 解析
  • 总结


前言

一个本硕双非的小菜鸡,备战24年秋招。打算尝试6.S081,将它的Lab逐一实现,并记录期间心酸历程。
代码下载

官方网站:6.S081官方网站

安装方式:
通过 APT 安装 (Debian/Ubuntu)
确保你的 debian 版本运行的是 “bullseye” 或 “sid”(在 ubuntu 上,这可以通过运行 cat /etc/debian_version 来检查),然后运行:

sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

(“buster”上的 QEMU 版本太旧了,所以你必须单独获取。

qemu-system-misc 修复
此时此刻,似乎软件包 qemu-system-misc 收到了一个更新,该更新破坏了它与我们内核的兼容性。如果运行 make qemu 并且脚本在 qemu-system-riscv64 -machine virt -bios none -kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 之后出现挂起

则需要卸载该软件包并安装旧版本:

  $ sudo apt-get remove qemu-system-misc
  $ sudo apt-get install qemu-system-misc=1:4.2-3ubuntu6

在 Arch 上安装

sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-arch-extra

测试您的安装
若要测试安装,应能够检查以下内容:

$ riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-gcc (GCC) 10.1.0
...

$ qemu-system-riscv64 --version
QEMU emulator version 5.1.0

您还应该能够编译并运行 xv6: 要退出 qemu,请键入:Ctrl-a x。

# in the xv6 directory
$ make qemu
# ... lots of output ...
init: starting sh
$

在本实验中,您将获得重新设计代码以提高并行性的经验。多核机器上并行性差的一个常见症状是频繁的锁争用。提高并行性通常涉及更改数据结构和锁定策略以减少争用。您将对xv6内存分配器和块缓存执行此操作。

切换分支执行操作

git stash
git fetch
git checkout net
make clean

Network

您将使用名为E1000的网络设备来处理网络通信。对于xv6(以及您编写的驱动程序),E1000看起来像是连接到真正以太网局域网(LAN)的真正硬件。事实上,用于与您的驱动程序对话的E1000是qemu提供的模拟,连接到的LAN也由qemu模拟。在这个模拟LAN上,xv6(“来宾”)的IP地址为10.0.2.15。Qemu还安排运行Qemu的计算机出现在IP地址为10.0.2.2的LAN上。当xv6使用E1000将数据包发送到10.0.2.2时,qemu会将数据包发送到运行qemu的(真实)计算机上的相应应用程序(“主机”)。

您将使用QEMU的“用户模式网络栈(user-mode network stack)”。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.c和kernel/net.h包含一个实现IP、UDP和ARP协议的简单网络栈。这些文件还包含用于保存数据包的灵活数据结构(称为mbuf)的代码。最后,kernel/pci.c包含在xv6引导时在PCI总线上搜索E1000卡的代码。

您的工作是在kernel/e1000.c中完成e1000_transmit()和e1000_recv(),以便驱动程序可以发送和接收数据包。当make grade表示您的解决方案通过了所有测试时,您就完成了。

[!TIP] 在编写代码时,您会发现自己参考了《E1000软件开发人员手册》。以下部分可能特别有用:

Section 2是必不可少的,它概述了整个设备。
Section 3.2概述了数据包接收。
Section 3.3与Section 3.4一起概述了数据包传输。
Section 13概述了E1000使用的寄存器。
Section 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()为要进行DMA的E1000分配mbuf数据包缓冲区。此外还有一个传输环,驱动程序将需要E1000发送的数据包放入其中。e1000_init()将两个环的大小配置为RX_RING_SIZE和TX_RING_SIZE。

当net.c中的网络栈需要发送数据包时,它会调用e1000_transmit(),并使用一个保存要发送的数据包的mbuf作为参数。传输代码必须在TX(传输)环的描述符中放置指向数据包数据的指针。struct tx_desc描述了描述符的格式。您需要确保每个mbuf最终被释放,但只能在E1000完成数据包传输之后(E1000在描述符中设置E1000_TXD_STAT_DD位以指示此情况)。

当当E1000从以太网接收到每个包时,它首先将包DMA到下一个RX(接收)环描述符指向的mbuf,然后产生一个中断。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中运行nettests。nettests中的第一个测试尝试将UDP数据包发送到主机操作系统,地址是make server运行的程序。如果您还没有完成实验,E1000驱动程序实际上不会发送数据包,也不会发生什么事情。

完成实验后,E1000驱动程序将发送数据包,qemu将其发送到主机,make server将看到它并发送响应数据包,然后E1000驱动程序和nettests将看到响应数据包。但是,在主机发送应答之前,它会向xv6发送一个“ARP”请求包,以找出其48位以太网地址,并期望xv6以ARP应答进行响应。一旦您完成了对E1000驱动程序的工作,kernel/net.c就会处理这个问题。如果一切顺利,nettests将打印testing ping: OK,make 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执行一些其他测试,最终通过(真实的)互联网将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.

您应该确保make grade同意您的解决方案通过。

提示:

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

实现e1000_transmit的一些提示:

  1. 首先,通过读取E1000_TDT控制寄存器,向E1000询问等待下一个数据包的TX环索引。
  2. 然后检查环是否溢出。如果E1000_TXD_STAT_DD未在E1000_TDT索引的描述符中设置,则E1000尚未完成先前相应的传输请求,因此返回错误。
  3. 否则,使用mbuffree()释放从该描述符传输的最后一个mbuf(如果有)。
  4. 然后填写描述符。m->head指向内存中数据包的内容,m->len是数据包的长度。设置必要的cmd标志(请参阅E1000手册的第3.3节),并保存指向mbuf的指针,以便稍后释放。
  5. 最后,通过将一加到E1000_TDT再对TX_RING_SIZE取模来更新环位置。
  6. 如果e1000_transmit()成功地将mbuf添加到环中,则返回0。如果失败(例如,没有可用的描述符来传输mbuf),则返回-1,以便调用方知道应该释放mbuf。

实现e1000_recv的一些提示:

  1. 首先通过提取E1000_RDT控制寄存器并加一对RX_RING_SIZE取模,向E1000询问下一个等待接收数据包(如果有)所在的环索引。
  2. 然后通过检查描述符status部分中的E1000_RXD_STAT_DD位来检查新数据包是否可用。如果不可用,请停止。
  3. 否则,将mbuf的m->len更新为描述符中报告的长度。使用net_rx()将mbuf传送到网络栈。
  4. 然后使用mbufalloc()分配一个新的mbuf,以替换刚刚给net_rx()的mbuf。将其数据指针(m->head)编程到描述符中。将描述符的状态位清除为零。
  5. 最后,将E1000_RDT寄存器更新为最后处理的环描述符的索引。
  6. e1000_init()使用mbufs初始化RX环,您需要通过浏览代码来了解它是如何做到这一点的。
  7. 在某刻,曾经到达的数据包总数将超过环大小(16);确保你的代码可以处理这个问题。

您将需要锁来应对xv6可能从多个进程使用E1000,或者在中断到达时在内核线程中使用E1000的可能性。

解析

最后一题了,说真的这题就是6.S081最后的仁慈
这题其实并不重要,但是本着来都来了的原则,给6.S081来个完美的收尾吧!

这题核心就是用来解决设备驱动与网卡设备之间的通信问题。我们可以通过寄存器映射,将硬件的寄存器给映射到了内核的地址空间中,我们访问内核的某个地址,就是在访问硬件的寄存器,这一下子就打通了内核和硬件之间的桥梁

伪代码已经给好,照着实现就行。主要负责将一个 mbuf(内存缓冲区,用于存储网络数据包)传递给 e1000 网络接口卡以进行传输。实现了将网络数据包发送到 e1000 网卡的逻辑。包括获取锁、检查发送状态、释放旧的 mbuf、设置新的发送描述符、更新寄存器和释放锁。

//kernel/e1000.c
int
e1000_transmit(struct mbuf *m)
{
  //
  // Your code here.
  acquire(&e1000_lock);
  // 查询ring里下一个packet的下标
  int idx = regs[E1000_TDT];

  if ((tx_ring[idx].status & E1000_TXD_STAT_DD) == 0) {
    // 之前的传输还没有完成
    release(&e1000_lock);
    return -1;
  }

  // 释放上一个包的内存
  if (tx_mbufs[idx])
    mbuffree(tx_mbufs[idx]);

  // 把这个新的网络包的pointer塞到ring这个下标位置
  tx_mbufs[idx] = m;
  tx_ring[idx].length = m->len;
  tx_ring[idx].addr = (uint64) m->head;
  tx_ring[idx].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;
  regs[E1000_TDT] = (idx + 1) % TX_RING_SIZE;

  release(&e1000_lock);
  return 0;
  //
  // 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.
  //
  
  return 0;
}

另外一个,处理从 e1000 网络接口卡接收到的数据包。实现了从 e1000 网卡接收数据包的逻辑,包括检查新数据包、传递给网络栈、分配新的 mbuf 以及更新接收描述符和硬件寄存器。

//kernel/e1000.c
static void
e1000_recv(void)
{
  //
  // Your code here.
    while (1) {
    // 把所有到达的packet向上层递交
    int idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
    if ((rx_ring[idx].status & E1000_RXD_STAT_DD) == 0) {
      // 没有新包了
      return;
    }
    rx_mbufs[idx]->len = rx_ring[idx].length;
    // 向上层network stack传输
    net_rx(rx_mbufs[idx]);
    // 把这个下标清空 放置一个空包
    rx_mbufs[idx] = mbufalloc(0);
    rx_ring[idx].status = 0;
    rx_ring[idx].addr = (uint64)rx_mbufs[idx]->head;
    regs[E1000_RDT] = idx;
   }
  //
  // Check for packets that have arrived from the e1000
  // Create and deliver an mbuf for each packet (using net_rx()).
  //
}

成功!
在这里插入图片描述

总结

总算是完成了6.s081的所有lab了,中间其实断断续续的,感觉努力整的话一个多月就能差不多了。最大的收获就是能够亲自实现了曾经只是在纸面上学习过的某些定义。感觉深入的理解了底层的原理这种感觉是非常好的。想起侯捷老师曾说过的“源码面前了无秘密”。感觉如果真的有时间精力的话看看这些代码,学习人家怎么的流程真的挺好的。我感觉我可以跟面试官多叭叭两句了(笑)。
听过这个实验开始变得水了,很多人都在往简历上写emmm。。。不过真的不影响他的含金量, 这几个斯坦福大学的实验都值得一做。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1926887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go-知识测试-性能测试分析工具-benchstat

Go-知识测试-性能测试分析工具-benchstat benchmark 结果benchstat确认 benchstat 已安装确认 GOPATH 和 GOBIN将 $GOPATH/bin 添加到 PATH验证安装检查安装路径 使用 传送门:Go-知识测试-性能测试 benchmark 结果 benchmark 测试是实际项目中经常使用的测试方法&a…

飞凌全志T527开发板U-Boot添加自定义菜单

昨日,终于收到了心心念念的飞凌OK-T527开发板,板子很漂亮,外设丰富,性能强悍,T527创新性地使用了RISC-V架构的协处理器,后期值得研究一下异核的使用: 有趣的是,板子上电,…

智能制造 v3.13.16 发布,ERP、MES 更新

智能制造一体化管理系统 [SpringBoot2 - 快速开发平台],适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模…

0.单片机工作原理

文章目录 最小系统 单片机芯片 时钟电路 复位电路 电源 最小系统 单片机芯片 本次51单片机的芯片为:STC89C52 Flash(闪存)程序存储器:存储程序的空间 SRAM:数据存储器,可用于存放程序执行的中间结果和过程数据 DPTR:…

某客户报表系统Oracle数据库挂起问题分析处理

某客户报表系统Oracle数据库挂起问题分析处理 一、概要 某客户报表系统Oracle数据库在3月5号、6号均出现一节点实例短暂挂起现象,挂起现象有两种,第一是普通用户不能登录数据库,第二是sys用户可以登录数据库,但是做简单的select查…

C判断一个点在三角形上

背景 鼠标操作时,经常要判断是否命中显示控件,特开发此算法快速判断。 原理 三角形三等分点定理是指在任意三角形ABC中,可以找到三个点D、E和F,使得线段AD、BE和CF均等分三角形ABC。 这意味着三个等分点分别位于三个边界上&…

数据湖表格式 Hudi/Iceberg/DeltaLake/Paimon TPCDS 性能对比(Spark 引擎)

当前,业界流行的集中数据湖表格式 Hudi/Iceberg/DeltaLake,和最近出现并且在国内比较火的 Paimon。我们现在看到的很多是针对流处理场景的读写性能测试,那么本篇文章我们将回归到大数据最基础的场景,对海量数据的批处理查询。本文…

具身大模型研究综述

源自:哈工大SCIR 作者:陈一帆,张宇驰,孙楚芮,冯怀绪,宋浩,王寄哲 指导老师:张伟男 注:若出现无法显示完全的情况,可 V 搜索“人工智能技术与咨询”查看完整…

什么叫图像的双边滤波,并附利用OpenCV和MATLB实现双边滤波的代码

双边滤波(Bilateral Filtering)是一种在图像处理中常用的非线性滤波技术,主要用于去噪和保边。它在空间域和像素值域上同时进行加权,既考虑了像素之间的空间距离,也考虑了像素值之间的相似度,从而能够有效地…

赛氪网荣获2024年中国高校计算机教育大会合作伙伴荣誉

2024年7月13日,在黑龙江哈尔滨召开的“2024年中国高校计算机教育大会(CCEC2024)”,环球赛乐(北京)科技有限公司(以下简称”赛氪网“)凭借其在高等教育与科技创新领域的卓越贡献,荣幸地获得了本次…

SpringBoot详细解析

1.什么是springboot springboot也是spring公司开发的一款框架。为了简化spring项目的初始化搭建的。那么spring对应springboot有什么缺点呢? spring项目搭建的缺点: 配置麻烦依赖tomcat启动慢 2.springboot的特点 自动配置 Spring Boot的自动配置是一个运行时&…

Docker之在外执行docker内部命令(十一)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

react + redux 状态管理操作

目录 1 概念2 Redux 安装3 创建子模块并导入4 中间件为 react 注入 store5 在组件中使用 store 数据6 修改 store 数据7 提交 action 传参8 异步状态操作9 redux 调试工具 1 概念 Redux 是一个全局状态管理的 JS 库 2 Redux 安装 在react中使用redux,官方要求安…

「网络通信」HTTP 协议

HTTP 🍉简介🍉抓包工具🍉报文结构🍌请求🍌响应🍌URL🥝URL encode 🍌方法🍌报文字段🥝Host🥝Content-Length & Content-Type🥝User…

Ubuntu使用K3S一分钟快速搭建K8S集群

快速入门指南 | Rancher文档 准备3台服务器 Master节点安装脚本# K3s 提供了一个安装脚本,可以方便的在 systemd 或 openrc 的系统上将其作为服务安装。这个脚本可以在 https://get.k3s.io 获得。要使用这种方法安装 K3s,只需运行以下命令:…

[超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动+CUDA+cuDNN+Pytorch)--[3]安装cuDNN与Pytorch

本次配置过程的三篇博文分享分别为为: [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[1]安装显卡驱动 [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[2]安装Anaconda与CUDA [超级详细系列]ubuntu22.04配置深…

sql server 练习题5

课后作业 在homework库下执行 作业1: 案例:根据用户分数划分等级。小于60分为不及格,[60,80)为及格,[80,90)为良好,大于等于90分以上为优秀。 建表语句: CREATE TABLE Grades ( ID INT PRIMARY KEY, Name V…

数电基础 - 时序逻辑电路

目录 一. 简介 二. 分析方法 三. 常用的时序逻辑电路 四. 冒险现象 五. 总结 一. 简介 时序逻辑电路是数字电路的重要组成部分,与组合逻辑电路不同,它在任何时刻的输出不仅取决于当时的输入信号,还与电路原来的状态有关。 时序逻辑电路…

pytorch 是如何调用 cusolver API 的调用

0,环境 ubuntu 22.04 pytorch 2.3.1 x86 RTX 3080 cuda 12.2 1, 示例代码 以potrs为例; hello_cholesk.py """ hello_cholesky.py step1, Cholesky decompose; step2, inverse A; step3, Cholesky again; python3 hello_cholesky.py -…

【Django+Vue3 线上教育平台项目实战】构建课程详情页与集成视频播放功能

文章目录 前言一、课程列表页面a.后端代码b.前端代码 二、课程详情页面a. 视频播放功能的集成1.获取上传视频的链接地址2.集成在前端页面中1>使用vue-alipayer视频播放组件2>使用video标签 b. 页面主要内容展示1.后端代码1>分析表2>核心逻辑 2.前端代码3.效果图 前…