惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
小众软件
小众软件
量子位
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
博客园_首页
IT之家
IT之家
S
SegmentFault 最新的问题
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
月光博客
月光博客
有赞技术团队
有赞技术团队
H
Help Net Security
云风的 BLOG
云风的 BLOG

VnYzm的博客

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界
MIT-6.S081-2020实验(xv6-riscv64)十一:net
VnYzm · 2021-01-14 · via VnYzm的博客

实验文档

概述

这次实验主要实现网卡驱动的一部分,文档内容非常长,实际实验不算难,跟着hint就行,但还是需要对整体框架有一定的了解。

内容

发送函数:

int
e1000_transmit(struct mbuf *m)
{
  acquire(&e1000_lock);
  uint32 index = regs[E1000_TDT];
  if ((tx_ring[index].status & E1000_TXD_STAT_DD) == 0) {
      release(&e1000_lock); return -1;
  }
  if (tx_mbufs[index] != 0) mbuffree(tx_mbufs[index]);
  tx_mbufs[index] = m;
  tx_ring[index].addr = (uint64)m->head; tx_ring[index].length = m->len;
  tx_ring[index].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;
  regs[E1000_TDT] = (index + 1) % TX_RING_SIZE;
  release(&e1000_lock); return 0;
}

这里有两个问题,第一是可不可以只保存一个mbuf,每次调用函数的时候就释放掉上一个mbuf,答案是不能,因为多进程的影响,可能有多个进程都调用了这个函数,那么就会有很多“上一个mbuf”,自然需要一个数组来存,那么怎么知道上一个mbuf的位置呢,这就是寄存器E1000_TDT的作用,个人猜测在进程切换的时候寄存器的值是包含在进程状态里面的,所以相当于每个进程都有各自保存上一个mbuf的位置。第二是tx_desc结构体里的cmd应该填啥,本来按照实验文档给出的参考材料,这玩意有8个位,只有一个位明确是必须填0,其他位都是可以或者必须填成1的,但是查看e1000_dev.h,发现它里面刚好有且仅有两个常量E1000_TXD_CMD_RSE1000_TXD_CMD_EOP,暗示我们就填这两个位即可。

接收函数:

static void
e1000_recv(void)
{
  for (;;) {
      uint32 index = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
      if ((rx_ring[index].status & E1000_RXD_STAT_DD) == 0) break;
      rx_mbufs[index]->len = rx_ring[index].length;
      net_rx(rx_mbufs[index]); rx_mbufs[index] = mbufalloc(0);
      rx_ring[index].addr = (uint64)rx_mbufs[index]->head; rx_ring[index].status = 0;
      regs[E1000_RDT] = index;
  }
}

这里我没有使用锁,因为这个函数是不会并行的。这个函数的作用是一次性读取收到的所有rx_ring,然后由net_rx发给上一层,这里没有区分收到的包是哪个进程的,因为这个函数实际上处于OSI里非常底层的数据链路层,net_rx函数收到包后会调用net_rx_ip或net_rx_arp,进入网络层,net_rx_ip函数会调用net_rx_udp,进入传输层,net_rx_udp函数会调用sockrecvudp,再由socket机制统一根据端口把包分给各个进程。也就是说,在物理层之上,传输层及以下的层都是不考虑并行的。所以网上很多题解提到net_rx必须在锁被释放之后才能调用,实验文档的描述有问题,个人认为这个实验设计预期recv函数就是不加锁的,所以可以按照文档里hint的步骤来。另外就是这个index的意义,上个函数里表示的是“上一个包”,这个函数里表示的是“当前包”,即把当前包对应的mbuf送给上层,然后再新建一个包,令当前的rx_desc指向它,等待后面物理层往里面填东西。

最后是测试,测试程序中用的dns是谷歌的域名服务器8.8.8.8,由于众所周知的原因,不调整就会卡死。找到dns函数,修改有4个8的那一行为国内的dns服务器,我填的是114.114.114.114:

  dst = (114 << 24) | (114 << 16) | (114 << 8) | (114 << 0);

MIT-6.S081-2020实验到这里就结束了,感谢MIT的教授们设计了这些实验并公开给全世界学生学习。这些实验虽然代码量都没多少,但是想要完成实验就必须反复查看和修改中断、内存、进程、文件相关的代码,不需要看长篇累牍的书籍,听高深复杂的课程,xv6内核实现的重点就已经跃然心中了。不说教学手段,仅说实验文档和代码也是十分出色,实验文档循循善诱,每一步要做什么都清清楚楚,而xv6的程序代码完全不拖泥带水,每行都有其作用,不绕弯子,代码注释也是鞭辟入里,确实是受益匪浅。之后我打算再好好研究一下xv6内核本身以及实验中各种测试函数的设计,这里面的应该也有很多值得我学习的地方。

不过我还是有点想法,之前虽然说syscall实验我觉得很不错,但现在看来似乎有点多余了,因为这个实验的内容后面大部分的实验基本每次都要重做一遍,个人感觉这个实验可以和别的实验合并;然后lazyalloc、copy on write和mmap三个实验有不少重叠,多线程调度实验和锁的实验的考察点也基本一致,这些都可以规约和化简;最后我觉得最好能增加系统引导过程相关的实验以及物理内存分配相关的实验,系统引导这个我确实是不知道要怎么设计,物理内存分配的19年有一个,但是今年把它删掉了,个人觉得要么弄个链表+淘汰策略,要么弄个伙伴算法的都可以。当然这些只是我站在外行角度的口胡,那些精通科研和教育的专家肯定会有更好的想法。

完结撒花!