MIT-6.S081-2020实验(xv6-riscv64)十一:net


实验文档

概述

这次实验主要实现网卡驱动的一部分,文档内容非常长,实际实验不算难,跟着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年有一个,但是今年把它删掉了,个人觉得要么弄个链表+淘汰策略,要么弄个伙伴算法的都可以。当然这些只是我站在外行角度的口胡,那些精通科研和教育的专家肯定会有更好的想法。

完结撒花!