文章

MIT 6.S081 | 0x07 Networking

我又回来了!这次争取一鼓作气更完这个系列!

MIT 6.S081 | 0x07 Networking

首先想解jiaobian一下,为什么拖了这么久:文档太多,没看进去 :joy:,拖着拖着就彻底忘了。我经过深刻反思,认为这是一个很不好的习惯,所以我又来了,抓紧更新,争取把这个系列收尾。

Your job is to complete e1000_transmit() and e1000_recv(), both in kernel/e1000.c, so that the driver can transmit and receive packets. You are done when make grade says your solution passes all the tests.

这个部分让我们实现网络设备 E1000 的发送和接收功能,主要就是完成 e1000_transmit()e1000_recv() 这两个函数。初看的时候没什么头绪,参考的那个文件长的不得了,磨了几天就忘掉了。现在看到实验的 Hints 部分原来把收发过程写的很清楚,我就照着这个提示写了,主要需要理解的就是这个环形缓冲区的使用。

提示

首先,在e1000_transmit()e1000_recv()中添加打印语句,并运行make server以及在xv6中执行nettests。你应该能从打印语句中看到nettests触发了对e1000_transmit的调用。

实现e1000_transmit()的一些提示:

  1. 首先,通过读取E1000_TDT控制寄存器,询问E1000下一个数据包期望的发送环索引。
  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. 然后,通过检查描述符状态部分中的E1000_RXD_STAT_DD位来判断是否有新数据包可用。如果没有,停止处理。
  3. 否则,根据描述符中的长度报告更新mbuf的m->len。使用net_rx()将mbuf传递给网络栈。
  4. 然后,使用mbufalloc()分配一个新的mbuf以替换刚刚给net_rx()的那个。将其数据指针(m->head)编程到描述符中,并清零描述符的状态位。
  5. 最后,更新E1000_RDT寄存器为最后处理的环描述符的索引。

e1000_init()使用mbufs初始化RX环,你可能需要查看它是如何做到的,并可能借鉴其中的代码。

总有一天到达的数据包总数将超过环的大小(16个);确保你的代码能够处理这种情况。

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
diff --git a/kernel/e1000.c b/kernel/e1000.c
index 70a2adf..0414749 100644
--- a/kernel/e1000.c
+++ b/kernel/e1000.c
@@ -102,7 +102,30 @@ e1000_transmit(struct mbuf *m)
   // the TX descriptor ring so that the e1000 sends it. Stash
   // a pointer so that it can be freed after sending.
   //
-  
+  acquire(&e1000_lock);
+
+  uint32 idx = regs[E1000_TDT];
+
+  if (!(tx_ring[idx].status & E1000_TXD_STAT_DD)) {
+    release(&e1000_lock);
+    return -1;
+  }
+
+  if (tx_mbufs[idx]) {
+    mbuffree(tx_mbufs[idx]);
+    tx_mbufs[idx] = 0;
+  }
+
+  tx_ring[idx].addr = (uint64)m->head;
+  tx_ring[idx].length = m->len;
+
+  tx_ring[idx].cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;
+
+  tx_mbufs[idx] = m;
+
+  regs[E1000_TDT] = (regs[E1000_TDT] + 1) % TX_RING_SIZE;
+
+  release(&e1000_lock);
   return 0;
 }
 
@@ -115,6 +138,23 @@ e1000_recv(void)
   // Check for packets that have arrived from the e1000
   // Create and deliver an mbuf for each packet (using net_rx()).
   //
+  while (1) {
+    uint32 tail = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
+
+    if (!(rx_ring[tail].status & E1000_TXD_STAT_DD)) {
+      break;
+    }
+
+    rx_mbufs[tail]->len = rx_ring[tail].length;
+    net_rx(rx_mbufs[tail]);
+
+    rx_mbufs[tail] = mbufalloc(0);
+
+    rx_ring[tail].addr = (uint64)rx_mbufs[tail]->head;
+    rx_ring[tail].status = 0;
+
+    regs[E1000_RDT] = tail;
+  }
 }

评测

在进行 make grade 的时候有个测试 nettest: DNS 没有通过。看了一下测试代码,它要求的 IP 是 128.52.129.126,但我实际拿到的 IP 是 198.18.0.51,看了一下这两个地址其实是一个网站。这么来看,我的代码应该是没问题的,只是因为测试代码没有更新 IP 地址吧。

本文由作者按照 CC BY 4.0 进行授权