MIT 6.828 - 10. Lab 10: network

Tags: MIT 6.828

实验总结

  1. 本次实验用时约 20 个小时。
  2. 收获是 学会如何看外设的 datasheet 写驱动;学会编写基本 UDP 网络栈的方法

实验结束后的全部代码在:https://github.com/monkey2000/xv6-riscv/tree/net/

测试结果:

1
2
3
4
5
6
7
8
9
running nettests: 
$ make qemu-gdb
(4.1s)
nettest: one ping: OK
nettest: single process: OK
nettest: multi-process: OK
nettest: DNS: OK
time: OK
Score: 100/100

0. 实验准备

实验指导链接

上来直接:

1
2
$ cd xv6-riscv-fall19
$ git checkout net

1. 实现

network 分为几个部分完成:

  1. 硬件驱动:我认为这个是最有意思的地方,首先需要查(甚至通读) datasheet 了解如何给硬件发送信号(一般是用 MMIO 或 PMIO)和向硬件传输数据(一般来说都是用 ring buffer + DMA )。
  2. 各层的网络栈处理程序:基本上就是按照 Eth -> IP -> UDP/Arp 的顺序处理即可。
  3. 与用户态应用之间的接口:具体而言,本实验中我实现了 connect() close() read() write()

下面我们分层介绍一些代码细节。

1.1. 硬件驱动

这个实验中仅要求完成 e1000_recv()e1000_transmit() 两个驱动函数。

具体两个函数的功能在实验指导中有详解,此处不赘述。

需要注意的是,在 e1000_recv() 中处理数据包中断时,很可能有多个待处理的包都在 buffer 里。我错误理解为一个 interrupt 对应一个 packet (实际为一对多),导致调了很久。

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
int
e1000_transmit(struct mbuf *m) {
acquire(&e1000_lock);

uint32 desc_pos = regs[E1000_TDT];

// overflow detection
if((tx_ring[desc_pos].status & E1000_RXD_STAT_DD) == 0) {
release(&e1000_lock);
return -1;
}

if(tx_mbufs[desc_pos] != 0)
mbuffree(tx_mbufs[desc_pos]);

tx_mbufs[desc_pos] = m;
tx_ring[desc_pos].addr = (uint64) m->head;
tx_ring[desc_pos].length = m->len;
tx_ring[desc_pos].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;

regs[E1000_TDT] = (desc_pos + 1) % TX_RING_SIZE;
__sync_synchronize();
release(&e1000_lock);

return 0;
}

static void
e1000_recv(void) {
struct mbuf *buf;

uint32 desc_pos = (regs[E1000_RDT] + 1) % RX_RING_SIZE;

while((rx_ring[desc_pos].status & E1000_RXD_STAT_DD)) {
acquire(&e1000_lock);
buf = rx_mbufs[desc_pos];
mbufput(buf, rx_ring[desc_pos].length);

// refill a new mbuf
rx_mbufs[desc_pos] = mbufalloc(0);
if (!rx_mbufs[desc_pos])
panic("e1000");
rx_ring[desc_pos].addr = (uint64) rx_mbufs[desc_pos]->head;
rx_ring[desc_pos].status = 0;

regs[E1000_RDT] = desc_pos;
__sync_synchronize();
release(&e1000_lock);

net_rx(buf);

desc_pos = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
}
}

1.2. 各层的处理程序

这部分无需自己实现,不过 challenge 里对这部分要求很高(如实现一个完整的 arp 协议)。

我个人建议做实验时在这部分添加一些调试用的输出,方便查看(当然也可以用 wireshark 浏览 dump 出来的流量)。

1.3. 与用户之间的接口

原本程序里的单链表实在难用…

这部分需要实现的包括 sockread() sockwrite() sockclose() 等函数。可以仿照 pipe 的实现编写。

同时还要实现一个 port demuxer sockrecvudp(),根据 (raddr, lport, rport) 找到相应的 socket ,并放入相应的数据包,唤醒在这个 socket 上等待的进程(我认为这个比较重要)

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
// called by protocol handler layer to deliver UDP packets
void
sockrecvudp(struct mbuf *m, uint32 raddr, uint16 lport, uint16 rport) {
struct sock *so = sockets;
acquire(&lock);
while(so) {
if (so->raddr == raddr &&
so->lport == lport &&
so->rport == rport) {
break;
}
so = so->next;
}
release(&lock);

// if no socket machted
if(so == 0) {
mbuffree(m);
return;
}

acquire(&so->lock);
mbufq_pushtail(&so->rxq, m);
release(&so->lock);
wakeup(&so->rxq);
}


知识共享许可协议 2000 - 2099 MonKey's Blog | 自豪地采用 Hexo + Pandoc + KaTeX + Highlight.js