xv6 Lab6 network driver - MIT 6.1810 Fall 2025 Operating System
本文最后更新于 2026年8月17日 晚上
阅读
中断 Interrupt:硬件需要得到 OS 的关注,产生一个中断,过程与系统调用类似。但是不同点:
- 异步,与当前 CPU 运行的进程无关
- 并行,设备与 CPU 并行运行
- 需要驱动程序
许多设备驱动都会在两个不同的上下文中执行代码:
- top half(上半部):运行在某个进程的内核线程中。
- bottom half(下半部):在发生中断时执行,也就是中断处理程序。
Part One:NIC
网络栈已经准备好了一个装有完整数据包的内存 buffer(存放数据包 packets 的),我们需要做的是补全两个函数。
一些陌生名词的解释:
-
NIC Network Interface Card 网卡,E1000 也是网卡的一种
-
DMA Direct Memory Access 直接内存访问:
- 一般而言,硬件不能直接操纵内核内存
- 但是配置好 DMA 之后,E1000 可以直接读写指定的内存,也就是 buffer
- 这样,CPU 就不需要自己搬运数据
-
Descriptor 描述符,主要是告诉网卡,数据包的位置、长度、以及别的信息,代码里面是
tx_descdescriptor ├── addr:buffer 的内存地址 ├── length:数据包长度 ├── status:网卡是否处理完成 └── cmd:要求网卡执行什么操作- TX descriptor:描述「需要发送的数据包」。
- RX descriptor:描述「用来接收数据包的空 buffer」。
-
Descriptor Ring 因为描述符的数量不够,所以是一个回环,题目里面是 0~15
-
Control register,驱动通过控制寄存器来通知 E1000 相关的操作,具体而言,是
regs[] -
TDT Transmit Descriptor Tail,可以理解为「TX ring 中,驱动下一次应该填写哪个 descriptor」
-
RDT Receive Descriptor Tail,「驱动已经处理完并重新交还给 E1000 的最后一个 RX descriptor」
-
DD Descriptor Done,descriptor 的
status字段中的一个标志位- TX 的 DD 表示 E1000 已经把这个 descriptor 对应的数据包发送完,也不再读取它的 buffer
- RX 的 DD 表示 E1000 已经收到一个数据包,并将它完整写入这个 descriptor 指向的 buffer
-
EOP End Of Packet 数据包的结尾
-
RS Report Status
-
net_rx()xv6 网络栈接收数据包的入口
首先是补全 e1000_transmit():
代码
int
e1000_transmit(char *buf, int len)
{
acquire(&e1000_lock);
uint32 index = regs[E1000_TDT];
struct tx_desc *desc = &tx_ring[index];
if((desc->status & E1000_TXD_STAT_DD) == 0){
release(&e1000_lock);
return -1;
}
if(desc->addr != 0)
kfree((void *)desc->addr);
desc->addr = (uint64)buf;
desc->length = len;
desc->cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;
desc->status = 0;
regs[E1000_TDT] = (index + 1) % TX_RING_SIZE;
release(&e1000_lock);
return 0;
}根据上面的概念和 hints,即可完成。
然后是 e1000_recv(),同理完成即可。
Part Two: UDP Receive
这个 part 里面,spec 给得更加详细了。照着读就行,甚至不需要读别的手册。
照着 hints 一步一步来:
1. 创建数据结构
Create a struct to keep track of bound ports and the packets in their queues.
定义两个:
udp_packet表示队列中的一个数据包udp_port表示一个已经绑定的端口,同时记录下一个端口,从而形成链表,依此可以检查端口是否分配
使用端口对象池分配绑定端口:
#define UDP_PORTS_PER_PAGE 12
struct udp_port_page {
struct udp_port ports[UDP_PORTS_PER_PAGE];
int used;
struct udp_port_page *next;
};sys_bind() 的实现:检查传入端口号,然后检查是否绑定,没有就分配,然后加入 ports 链表。
ip_rx() 的实现:检查收到的包,然后找对应的端口。
sys_recv() 的实现:根据hint跑即可。
另:有个试着获得 mit pdos 官网 DNS 的测试,如果开着梯子的一些特殊设置似乎过不去,然后我就试着关掉梯子 / 系统代理 / TUN 连环关掉测试...最后得到的结果是:打开系统代理,关闭 TUN 即可。
记得修改代理之后,还需要重启 WSL 才能应用。
