这个实验要实现网卡驱动,实验说明部分说的比较多,要我们看 E1000 的使用手册,其实只要看下课程视频了解一下这个网卡的工作原理,然后根据实验提示走就可以了,实验其实并不难。
上图是课程上的截图,表示了网卡是如何和内存进行交互,根据网络收包和发包两个流程,将在内存中分配两个环形缓冲区 tx ring 和 rx ring。收包时先找到缓冲区空闲的一块缓存,然后将网络包复制到缓存区,实验其实只要做到这一步就可以了,至于上层是如何处理环形缓冲区里的数据其实在实验里是没有涉及的,这块不同的操作系统处理的方式都不一样,我可以暂时先忽略这里。按实验的给的提示完成这个实验即可。
实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
int
e1000_transmit(struct mbuf *m)
{
//
// Your code here.
//
// the mbuf contains an ethernet frame; program it into
// the TX descriptor ring so that the e1000 sends it. Stash
// a pointer so that it can be freed after sending.
//
acquire(&e1000_lock); // 多进程发包需要加锁保护
uint32 idx = regs[E1000_TDT]; // 取可用缓冲区下标
if ((tx_ring[idx].status & 1) != E1000_TXD_STAT_DD) { // 判断缓冲区是不是有效
release(&e1000_lock);
return -1;
}
if (tx_mbufs[idx]) // 释放之前的缓存
mbuffree(tx_mbufs[idx]);
// 设置缓存区
tx_mbufs[idx] = m;
tx_ring[idx].addr = (uint64)m->head;
tx_ring[idx].length = m->len;
// 这里根据手册设置需要设置 E1000_TXD_CMD_RS 位,以及 E1000_TXD_CMD_EOP 以太网结束分隔符。
tx_ring[idx].cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;
// 更新 TDT 寄存器
regs[E1000_TDT] = (idx+1) % TX_RING_SIZE;
release(&e1000_lock);
return 0;
}
static void
e1000_recv(void)
{
//
// Your code here.
//
// Check for packets that have arrived from the e1000
// Create and deliver an mbuf for each packet (using net_rx()).
//
// 获取可用缓冲区,recv 是阻塞调用所以可以不加锁
uint32 idx = regs[E1000_RDT];
idx = (idx+1)% RX_RING_SIZE;
while ((rx_ring[idx].status & 1) == E1000_RXD_STAT_DD) { // 一直收包直到遇到不能收包的缓冲区才停止
rx_mbufs[idx]->len = rx_ring[idx].length;
// 交给上层处理
net_rx(rx_mbufs[idx]);
// 重新初始化缓冲区
rx_mbufs[idx] = mbufalloc(0);
rx_ring[idx].addr = (uint64)rx_mbufs[idx]->head;
rx_ring[idx].status = 0;
// 进入下一个缓冲区
idx = (idx+1)% RX_RING_SIZE;
}
// 更新 RDT
regs[E1000_RDT] = idx - 1;
}
|
总结
这个实验要我们实现一个网卡驱动,其实这个实验可能更偏硬件或嵌入式一点,所以对于想了解操作系统如何处理网络包来说更推荐直接看课程视频,里面把网络协议栈都讲的很清楚,还有 tcpdump 的抓包分析。这个实验主要是让我们知道网卡是如何工作的。
Author
Hao
LastMod
2022-02-05
License
本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可