0%

MIT6.S081 Lab Networking

开始日期:22.5.15

操作系统:Ubuntu20.0.4

Link:Lab Networking

my github repository: duilec/MITS6.081-fall2021/tree/net

Lab Networking

写在前面

本次实验看起来比较唬人,文档、字都很多,但实际的coding并不难,本次的hints就是伪代码级别的,完全可以按着来写。难点应该在于理解整个收发包(recevice/transmit packet)的过程:
为了完成收发包的过程,cpu、网卡(ethernet)、RAM(buffer的存放处)这三者是如何通过xv6操作系统进行交互呢?
为了完成理解,笔者参考了不少博客。

实验内容

概述

xv6得依靠network stack(网络栈)实现收发数据,即通过network stack收发packet。

发包:

当network stackk需要发送一个packet的时候,会先将这个packet存放到发送环形缓冲区tx_ring,最后通过网卡将这个packet发送出去。
(每次发送packet前都需要检查一下上一次的packet发送完没,如果发送完了,要将其的释放掉)

收包:

当网卡需要接收packet的时候,网卡会直接访问内存(DMA),先将接受到的RAM的数据(即packet的内容)写入到接收环形缓冲区rx_ring中。接着,网卡会向cpu发出一个硬件中断,当cpu接受到硬件中断后,cpu就可以从接收环形缓冲区rx_ring中读取packet传递到network stack中了(net_rx())。
(网卡会一次性接收全部的packets,即接收到rx_ring溢出为止)

《操作系统及其应用》王红 第1版
p173 (4)输入完成时,DMA控制器发出中断信号,CPU响应后进行相应的中断处理
这说明硬件中断是有可能由DMA控制器发出的,而不一定是I/O设备网卡(22.5.19)

e1000_transmit

这里有三个问题需要解决:

为什么要用锁?何时释放tx_mbufs[tx_index]cmd的flag该如何设置?

  • 因为会有多线程测试,可能会出现多个线程会访问同一个mbuf的情况,导致竞争出错

  • 获取锁之后,如果该索引能被访问,就要将原有的(上一次的)mbuf其释放掉,以便装入新的mbuf

    You will need to ensure that each mbuf is eventually freed, but only after the E1000 has finished transmitting the packet

  • 参考e1000_dev.hcmd实际是有8个标志位,但只提供了两个标志位,我们可以大胆猜测只用设置这两个

    1
    2
    3
    /* Transmit Descriptor command definitions [E1000 3.3.3.1] */
    #define E1000_TXD_CMD_EOP 0x01 /* End of Packet */
    #define E1000_TXD_CMD_RS 0x08 /* Report Status */

    实际上我们可以对应查找文档:

    P39 The transmit descriptor status field is only present in cases where RS (or RPS for the 82544GC/EIonly) is set in the command field.

    因为我们要使用tx_ring[tx_index].status来检查该tx_mbufs[tx_index]是否可以被使用,使用要用到标志位E1000_TXD_CMD_RS

    P39 When set, indicates the last descriptor making up the packet. One or many descriptors can be used to form a packet.

    因为我们是按packet的形式传递数据的,所以要设置E1000_TXD_CMD_ECP,来标志一个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
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.
//
acquire(&e1000_lock);

// get the current index
uint32 tx_index = regs[E1000_TDT];

// check status
if ((tx_ring[tx_index].status & E1000_TXD_STAT_DD) == 0){
release(&e1000_lock);
return -1;
}

// free the last mbuf
if (tx_mbufs[tx_index]){
mbuffree(tx_mbufs[tx_index]);
}

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

// set the next index of mbuf
regs[E1000_TDT] = (tx_index + 1) % TX_RING_SIZE;

release(&e1000_lock);

return 0;
}

e1000_recv

这里也有三个问题需要解决:

为什么获取的索引是下一个等待被接收的索引,而不是当前索引?
为什么不用锁?为什么要接受全部packet直到溢出?

  • 因为当前索引在前一次recv过程中已经被传递(line 21~22),重新分配并清空了(line 25~27
    • 重新分配并清空,是为下一次RAM的数据写入做准备
  • 后两个问题其实可以看作一个问题同时解答,因为recv过程发生在OSI协议的数据链路层,而在这个层次的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
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()).
//

while (1) {
// get next index
uint32 rx_index = (regs[E1000_RDT] + 1) % RX_RING_SIZE;

// check status, if not set we will return
if ((rx_ring[rx_index].status & E1000_RXD_STAT_DD) == 0)
return ;

// deliver to network stack
rx_mbufs[rx_index]->len = rx_ring[rx_index].length;
net_rx(rx_mbufs[rx_index]);

// alloc a new mbuf and fill a new descriptor
rx_mbufs[rx_index] = mbufalloc(0);
rx_ring[rx_index].addr = (uint64)rx_mbufs[rx_index]->head;
rx_ring[rx_index].status = 0;

// as current index
regs[E1000_RDT] = rx_index;
}
}

总结

  • 完成日期:22.5.16
  • 一开始我就猜测要使用循环,但没看到hints里有具体提出就没有实现,嗯,以后要相信自己的直觉
  • e1000_recv的实现过程中有一句:重新分配一个新的mbuf,我理解出错,去重新创建了一个新的mbuf,根本上是因为我没理解到rx_ring是被循环使用的,这个循环数组一直是用来写入,传递packet
  • 对于网络协议层的代码其实我并不熟悉,也就只有基础的理论知识而已,后续得安排课程
  • 最近在听《任我行》陈奕迅