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_desc

    descriptor
    ├── 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()

代码C · 26 行
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 才能应用。

通过所有测试