Lab: networking
实验准备
切换到net分支
$ git fetch
$ git checkout net
$ make clean
Your Job
需求
在本实验中,您将为网络接口卡(NIC)编写一个xv6设备驱动程序。
您将使用一个名为 E1000 的网络设备来处理网络通信。对于 xv6(以及您编写的驱动程序),E1000 看起来就像是连接到真实以太网局域网(LAN)的真实硬件。实际上,您的驱动程序将与 qemu 提供的 E1000 的仿真进行通信,该仿真连接到 qemu 进行仿真的局域网。在这个仿真的局域网上,xv6(即"guest")具有 IP 地址 10.0.2.15。qemu 还会使运行 qemu 的计算机以 IP 地址 10.0.2.2 出现在该局域网中。当 xv6 使用 E1000 发送数据包到 10.0.2.2 时,qemu 将把数据包传递给在运行 qemu 的实际计算机上适当的应用程序(即"host")。、
Makefile配置QEMU以将所有传入和传出的数据包记录到实验目录下的packets.pcap文件中。查看这些记录可以帮助确认xv6是否正在传输和接收您预期的数据包。要显示记录的数据包:tcpdump -XXnr packets.pcap
我们已向xv6代码库添加了一些文件,供本实验使用。文件kernel/e1000.c包含E1000的初始化代码,以及用于传输和接收数据包的空函数,您需要填写这些函数。kernel/e1000_dev.h包含由E1000定义并在Intel E1000软件开发手册中描述的寄存器和标志位的定义。kernel/net.c和kernel/net.h包含一个简单的网络栈,实现了IP、UDP和ARP协议。这些文件还包含了一个用于保存数据包的灵活数据结构,称为mbuf。最后,kernel/pci.c包含的代码在xv6启动时在PCI总线上搜索E1000网卡。
您的任务是完成kernel/e1000.c中的e1000_transmit()和e1000_recv()函数,以便驱动程序可以进行数据包的传输和接收。当make grade命令显示您的解决方案通过了所有测试时,表示您已经完成了任务。
The solution
有关资料:
intel千兆网卡的interrupt auto-mask分析
通过查看手册以及相关资料结合lab所给代码可知:
- e1000网卡触发硬中断后会禁止网卡中断,避免频繁硬中断,降低内核的工作效率导致livelock出现。
- 尾指针寄存器被修改后网卡就会进行下一步操作,由于网卡与cpu可并行运行,故我们要保证设置好描述符后再修改尾指针寄存器。
e1000_recv()
该函数用于处理接收的数据包,属于驱动的bottom部分。
一个中断只会被PLIC分配给一个CPU处理,在处理过程中网卡中断被禁用(即网卡不能在CPU处理本次中断过程中再次产生中断),并且该函数不与其他函数共用同一数据结构。这保证竞态条件不会发生,故函数中无需加锁。
static void
e1000_recv(void)
{
while(1){//一次处理多个包,防止丢失
int ring_idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;//获取下一个环索引
if(rx_ring[ring_idx].status & E1000_TXD_STAT_DD){//检查索引到的位置是否有数据包待接受
rx_mbufs[ring_idx]->len = rx_ring[ring_idx].length;//将数据长度存入缓冲区结构体
net_rx(rx_mbufs[ring_idx]);//将数据包交给net_rx解析
rx_mbufs[ring_idx] = mbufalloc(0);//申请新缓冲区
if (!rx_mbufs[ring_idx])//判断是否申请成功
panic("e1000");
rx_ring[ring_idx].addr = (uint64) rx_mbufs[ring_idx]->head;//缓冲区首地址存入描述符结构体
rx_ring[ring_idx].status = 0;//将描述符的状态位清除为零
__sync_synchronize();//内存屏障,防止指令重排
regs[E1000_RDT] = ring_idx;//修改尾指针寄存器
}
else return;
}
}
e1000_transmit()
该函数用于设置要发送的数据包,属于驱动的top部分,由于可能有多个进程同时想要发包进而需要获取环索引,为了避免两个进程获取的环索引一样,故需加锁处理。
int
e1000_transmit(struct mbuf *m)
{
acquire(&e1000_lock);//加🔓
int ring_idx = regs[E1000_TDT];//获取下一个环索引
if(tx_ring[ring_idx].status & E1000_TXD_STAT_DD){//检查索引到的位置E1000有没有完成先前的传输请求
if(tx_mbufs[ring_idx])//检查用于传输上一个包的缓冲区是否被释放
mbuffree(tx_mbufs[ring_idx]);//释放缓冲区
tx_ring[ring_idx].addr = (uint64) m->head;//填写描述符addr字段使其指向当前缓冲区
tx_ring[ring_idx].length = m->len;//填写描述符length字段
tx_ring[ring_idx].cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;//查看e1000开发者手册3.3.3.1的Notes部分
tx_mbufs[ring_idx] = m;
__sync_synchronize();//内存屏障,防止指令重排
regs[E1000_TDT] = (regs[E1000_TDT]+1) % TX_RING_SIZE;
release(&e1000_lock);//解🔓
return 0;
}
else{
release(&e1000_lock);//解🔓
return -1;//error
}
return 0;
}