开始日期: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操作系统进行交互呢?
为了完成理解,笔者参考了不少博客。
- 参考链接:
[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溢出为止)
《操作系统及其应用》王红 第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.h
,cmd
实际是有8个标志位,但只提供了两个标志位,我们可以大胆猜测只用设置这两个1
2
3/* Transmit Descriptor command definitions [E1000 3.3.3.1] */
实际上我们可以对应查找文档:
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 | int |
e1000_recv
这里也有三个问题需要解决:
为什么获取的索引是下一个等待被接收的索引,而不是当前索引?
为什么不用锁?为什么要接受全部packet直到溢出?
- 因为当前索引在前一次的
recv
过程中已经被传递(line 21~22
),重新分配并清空了(line 25~27
)- 重新分配并清空,是为下一次RAM的数据写入做准备
- 后两个问题其实可以看作一个问题同时解答,因为
recv
过程发生在OSI协议的数据链路层,而在这个层次的packet
接收是不区分进程。既然不区分进程接收,自然就可以一直接收,直到不能再接收为止(溢出)。
参考代码:
1 | static void |
总结
- 完成日期:22.5.16
- 一开始我就猜测要使用循环,但没看到hints里有具体提出就没有实现,嗯,以后要相信自己的直觉
e1000_recv
的实现过程中有一句:重新分配一个新的mbuf
,我理解出错,去重新创建了一个新的mbuf
,根本上是因为我没理解到rx_ring
是被循环使用的,这个循环数组一直是用来写入,传递packet
的- 对于网络协议层的代码其实我并不熟悉,也就只有基础的理论知识而已,后续得安排课程
- 最近在听《任我行》陈奕迅