开始日期:22.5.15
操作系统:Ubuntu20.0.4
Link:Lab Networking
目录- Lab Networking
- 写在前面
- 实验内容
- 概述
- e_1000_transmit
- e1000_recv
- 总结
本次实验看起来比较唬人,文档、字都很多,但实际的coding并不难,本次的hints就是伪代码级别的,完全可以按着来写。难点应该在于理解整个收发包(recevice/transmit packet)的过程:
即为了完成收发包的过程,cpu、网卡(ethernet)、RAM(buffer的存放处)这三者是如何通过xv6操作系统进行交互呢?
为了完成理解,笔者参考了不少博客。
- 参考链接:
[mit6.s081] 笔记 Lab11: Networking | 网络
MIT6.S081 2021 networking
MIT 6.S081 2020 LAB11记录
MIT-6.S081-2020实验(xv6-riscv64)十一:net
MIT-6.S081 Networking
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溢出为止)
这里有三个问题需要解决:
为什么要用锁?何时释放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.h
,cmd
实际是有8个标志位,但只提供了两个标志位,我们可以大胆猜测只用设置这两个/* 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
结束了。
参考代码:
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
接收是不区分进程。既然不区分进程接收,自然就可以一直接收,直到不能再接收为止(溢出)。
参考代码:
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
的- 对于网络协议层的代码其实我并不熟悉,也就只有基础的理论知识而已,后续得安排课程
- 最近在听《任我行》陈奕迅