Lab5 Network diver
本实验就是实现E1000
网卡对于数据包的传输和接受,其实lab
的提示部分已经很详细了,只需要将文字转化为代码即可。
Transmit (moderate)
- 首先,通过读取
E1000_TDT
控制寄存器,向E1000
询问等待下一个数据包的TX环索引。 - 然后检查环是否溢出。如果
E1000_TXD_STAT_DD
未在E1000_TDT
索引的描述符中设置,则E1000
尚未完成先前相应的传输请求,因此返回错误。 - 否则,使用
mbuffree()
释放从该描述符传输的最后一个mbuf
(如果有)。 - 然后填写描述符。
m->head
指向内存中数据包的内容,m->len
是数据包的长度。设置必要的cmd
标志(请参阅E1000
手册的第3.3节),并保存指向mbuf
的指针,以便稍后释放。 - 最后,通过将一加到
E1000_TDT
再对TX_RING_SIZE
取模来更新环位置。 - 如果
e1000_transmit()
成功地将mbuf
添加到环中,则返回0。如果失败(例如,没有可用的描述符来传输mbuf
),则返回-1,以便调用方知道应该释放mbuf
。
int e1000_transmit(struct mbuf *m)
{
acquire(&e1000_lock);
int index = regs[E1000_TDT];
if ((tx_ring[index].status & E1000_TXD_STAT_DD) == 0)
{
release(&e1000_lock);
return -1;
}
if (tx_mbufs[index])
mbuffree(tx_mbufs[index]);
tx_mbufs[index] = m;
tx_ring[index].length = m->len;
tx_ring[index].cmd = (E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP);
tx_ring[index].addr = (uint64)m->head;
regs[E1000_TDT] = (index + 1) % TX_RING_SIZE;
release(&e1000_lock);
return 0;
}
为什么要使用锁,是为了避免竞态条件,在发送数据时,由于多个线程同时访问发送环(TX_ring)中的描述符,倘若没有锁来保护对描述符的访问,可能对引发竞态条件,导致访问失败。
Recv (moderate)
- 首先通过提取
E1000_RDT
控制寄存器并加一对RX_RING_SIZE
取模,向E1000
询问下一个等待接收数据包(如果有)所在的环索引。 - 然后通过检查描述符
status
部分中的E1000_RXD_STAT_DD
位来检查新数据包是否可用。如果不可用,请停止。 - 否则,将
mbuf
的m->len
更新为描述符中报告的长度。使用net_rx()
将mbuf
传送到网络栈。 - 然后使用
mbufalloc()
分配一个新的mbuf
,以替换刚刚给net_rx()
的mbuf
。将其数据指针(m->head
)编程到描述符中。将描述符的状态位清除为零。 - 最后,将
E1000_RDT
寄存器更新为最后处理的环描述符的索引。 e1000_init()
使用mbufs
初始化RX
环,您需要通过浏览代码来了解它是如何做到这一点的。- 在某刻,曾经到达的数据包总数将超过环大小(16);确保你的代码可以处理这个问题。
static void
e1000_recv(void)
{
while (1)
{
int index = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
if ((rx_ring[index].status & E1000_RXD_STAT_DD) == 0)
{
return;
}
rx_mbufs[index]->len = rx_ring[index].length;
net_rx(rx_mbufs[index]);
rx_mbufs[index] = mbufalloc(0);
rx_ring[index].addr = (uint64)rx_mbufs[index]->head;
rx_ring[index].status = 0;
regs[E1000_RDT] = index;
}
}
接收时不使用锁的原因是,调用e1000_recv()
是在e1000_intr()
中进行的,而e1000_intr()
是中断处理函数,所以说e1000_recv
主要在中断上下文中执行,而中断上下文是一个特殊的执行环境,它不会被抢占。在中断上下文中执行的代码是原子的,不会被其他中断打断,因此不需要额外的锁来保护。