总览

这次实验的主要要点是理解抽象
mbuf 是对 OS 内部网络数据的抽象,而 {tx, rx}_desc 是对网卡网络数据的抽象。网卡作为 file,有 descriptor 也是抽象。总之能理解抽象这次 lab 就完成大半了
还有一点可以研究的是 DMA,中文文档似乎没有细提,这是第一次接触 DMA,没想到就是 memory mapped?可能还要对 memory mapped 有一定了解就好做。
还补充一点,需要一些黑盒思想,要相信环形缓冲区中的数据会被网卡等自动发送。

实现

总的来说是跟着 hint 和 init 做
在 transmit 部份,主要知道有什么可以操作 (tx_mbufs and tx_ring) ,知道传入一个 mbuf 要从 ring 传出就好了,另外要知道 regs 怎么对网卡操作

在 recv 部份,第一次看官方 hint 的时候没想到一次 recv 可能接收多个包,所以就没有放在循环里面错了,后来看了中文文档才想到这一点,放在 while 里面遍历所有内容 其余的也是跟着 hint 做

实现不难,主要是理解

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.
  //
  int idx = regs[E1000_TDT];
  struct tx_desc desc = tx_ring[idx];
  if (desc.status != E1000_TXD_STAT_DD) {
    return -1;
  }
 
  // use mbuffree() to free the last mbuf that was transmitted from that descriptor (if there was one).
  if (tx_mbufs[idx]) {
    mbuffree(tx_mbufs[idx]);
  }
 
  // Then fill in the descriptor. m->head points to the packet's content in memory, and m->len is the packet length. Set the necessary cmd flags (look at Section 3.3 in the E1000 manual) and stash away a pointer to the mbuf for later freeing.
  tx_ring[idx].addr = (uint64) m->head;
  tx_ring[idx].length = m->len;
  tx_ring[idx].cmd |= E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;
  tx_mbufs[idx] = m;
 
  // Finally, update the ring position by adding one to E1000_TDT modulo TX_RING_SIZE.
  regs[E1000_TDT] = (regs[E1000_TDT] + 1) % TX_RING_SIZE;
 
  // If e1000_transmit() added the mbuf successfully to the ring, return 0. On failure (e.g., there is no descriptor available to transmit the mbuf), return -1 so that the caller knows to free the mbuf.
  return 0;
}
 
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()).
  //
 
  // First ask the E1000 for the ring index at which the next waiting received packet (if any) is located, by fetching the E1000_RDT control register and adding one modulo RX_RING_SIZE.
  int idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
 
  // Then check if a new packet is available by checking for the E1000_RXD_STAT_DD bit in the status portion of the descriptor. If not, stop.
  if (!(rx_ring[idx].status & E1000_RXD_STAT_DD)) {
    return;
  }
 
  // Otherwise, update the mbuf's m->len to the length reported in the descriptor. Deliver the mbuf to the network stack using net_rx().
  while (rx_ring[idx].status & E1000_RXD_STAT_DD) {
    struct mbuf *m = rx_mbufs[idx];
    m->len = rx_ring[idx].length;
    net_rx(m);
 
    // Then allocate a new mbuf using mbufalloc() to replace the one just given to net_rx(). Program its data pointer (m->head) into the descriptor. Clear the descriptor's status bits to zero.
    rx_mbufs[idx] = mbufalloc(0);
    if (!rx_mbufs[idx]) {
      panic("e1000");
    }
    rx_ring[idx].addr = (uint64) rx_mbufs[idx]->head;
    rx_ring[idx].status = 0;
 
    // Finally, update the E1000_RDT register to be the index of the last ring descriptor processed.
    regs[E1000_RDT] = idx;
    idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
  }
}

misc

  • tcpdump -XXnr packets.pcap
    • -XX: When parsing and printing, in addition to printing the headers of each packet, print the data of each packet, including its link level header, in hex and ASCII.
    • -n: Don’t convert addresses (i.e., host addresses, port numbers, etc.) to names.
    • -r: Read packets from file (which was created with the -w option or by other tools that write pcap or pcapng files).

测试

$ nettests
nettests running on port 25099
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
== Test running nettests == 
$ make qemu-gdb
(7.6s) 
== Test   nettest: ping == 
  nettest: ping: OK 
== Test   nettest: single process == 
  nettest: single process: OK 
== Test   nettest: multi-process == 
  nettest: multi-process: OK 
== Test   nettest: DNS == 
  nettest: DNS: OK 
== Test time == 
time: FAIL 
    Cannot read time.txt
Score: 99/100
make: *** [Makefile:336: grade] Error 1

总结

不得不说 MIT 的课程以及作业设计真的很不错,目标明确,网上资源多,加上自动 grading,体验非常好。 单就这个 lab 来说,主要是理解这种抽象,实现本身并不难。 这种环形 buufer 还有 knuth 提出的环形寄存器,用于 PL,以后有机会去学习一下 好像总体来说,这五个 lab 都不算非常困难?最困难的是读 xv6 book,那个读起来好无聊。