这是我自学 MIT6.S081 操作系统课程的 lab 代码笔记第十一篇:Networking(最后一篇)。此 lab 大致耗时:2小时。
课程地址:https://pdos.csail.mit.edu/6.S081/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.S081/2020/labs/net.html
我的代码地址:https://github.com/Miigon/my-xv6-labs-2020/tree/net
Commits: https://github.com/Miigon/my-xv6-labs-2020/commits/net本文中代码注释是编写博客的时候加入的,原仓库中的代码可能缺乏注释或代码不完全相同。
Lab 11: Networking (hard)
熟悉系统驱动与外围设备的交互、内存映射寄存器与 DMA 数据传输,实现与 E1000 网卡交互的核心方法:transmit 与 recv。
本 lab 的难度主要在于阅读文档以及理解 CPU 与操作系统是如何与外围设备交互的。换言之,更重要的是理解概念以及 lab 已经写好的模版代码的作用。
代码实现
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
57
58
59
60
61
62
int
e1000_transmit(struct mbuf *m)
{
acquire(&e1000_lock); // 获取 E1000 的锁,防止多进程同时发送数据出现 race
uint32 ind = regs[E1000_TDT]; // 下一个可用的 buffer 的下标
struct tx_desc *desc = &tx_ring[ind]; // 获取 buffer 的描述符,其中存储了关于该 buffer 的各种信息
// 如果该 buffer 中的数据还未传输完,则代表我们已经将环形 buffer 列表全部用完,缓冲区不足,返回错误
if(!(desc->status & E1000_TXD_STAT_DD)) {
release(&e1000_lock);
return -1;
}
// 如果该下标仍有之前发送完毕但未释放的 mbuf,则释放
if(tx_mbufs[ind]) {
mbuffree(tx_mbufs[ind]);
tx_mbufs[ind] = 0;
}
// 将要发送的 mbuf 的内存地址与长度填写到发送描述符中
desc->addr = (uint64)m->head;
desc->length = m->len;
// 设置参数,EOP 表示该 buffer 含有一个完整的 packet
// RS 告诉网卡在发送完成后,设置 status 中的 E1000_TXD_STAT_DD 位,表示发送完成。
desc->cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;
// 保留新 mbuf 的指针,方便后续再次用到同一下标时释放。
tx_mbufs[ind] = m;
// 环形缓冲区内下标增加一。
regs[E1000_TDT] = (regs[E1000_TDT] + 1) % TX_RING_SIZE;
release(&e1000_lock);
return 0;
}
static void
e1000_recv(void)
{
while(1) { // 每次 recv 可能接收多个包
uint32 ind = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
struct rx_desc *desc = &rx_ring[ind];
// 如果需要接收的包都已经接收完毕,则退出
if(!(desc->status & E1000_RXD_STAT_DD)) {
return;
}
rx_mbufs[ind]->len = desc->length;
net_rx(rx_mbufs[ind]); // 传递给上层网络栈。上层负责释放 mbuf
// 分配并设置新的 mbuf,供给下一次轮到该下标时使用
rx_mbufs[ind] = mbufalloc(0);
desc->addr = (uint64)rx_mbufs[ind]->head;
desc->status = 0;
regs[E1000_RDT] = ind;
}
}
操作系统想要发送数据的时候,将数据放入环形缓冲区数组 tx_ring 内,然后递增 E1000_TDT,网卡会自动将数据发出。当网卡收到数据的时候,网卡首先使用 direct memory access,将数据放入 rx_ring 环形缓冲区数组中,然后向 CPU 发起一个硬件中断,CPU 在收到中断后,直接读取 rx_ring 中的数据即可。
完结撒花
本 lab 的完成,为接近两个月的 MIT6.S081 旅途画上了句号。不得不说 MIT 的课程以及作业设计真的很不错,目标明确但又充满探索的 xv6 lab,加上自动 grading,体验简直好到爆。推荐同样对操作系统有兴趣的同学们可以来刷一刷,亲自动手体验实现操作系统各项机能。
同时,对计算机科学的探索依然没有穷尽,后面还会同步更新更多的刷课记录以及课程推荐。