- 任务:为 xv6 的 E1000 网卡添加发送/接收网络包的功能。
- 官方实验指南:.828/2022/labs/net.html
- 官方参考书:.828/2022/xv6/book-riscv-rev3.pdf
# 实验前先切换分支
$ git fetch
$ git checkout net
$ make clean
在kernel/e1000.c
文件里,设备 E1000 的初始化代码已经给出。但是发送包和接受包的两个对应函数体暂时为空:e1000_transmit()
和 e1000_recv()
,因此本次实验的任务就是实现这两个函数,让xv6能够发送以及接收包
根据官方实验文档可以得到下述提示信息:
E1000_TDT
的值(用 regs[E1000_TDT]
的方式来读取),来获取“发送环(TX ring)”中下一个可以存放新 mbuf 的 tx_desc 结构体的位置,记为 idxE1000_TXD_STAT_DD
标志位是否为零,来判断“发送环”是否存在溢出的情况——如果为零,则 idx 指向的tx_desc 结构体存在尚未发送的 mbuf。该情况表明该“发送环”没有空的位置来存放新 mbuf(也即溢出),e1000_transmit()返回错误tx_desc
结构体,则需要执行mbuffree()
函数,来将已经发送的 mbuf 数据进行释放以腾出空间(如果已经是空闲 tx_desc 的话,则没有必要执行)。这里注意,释放的是 mbuf,而不是外部包裹它的 tx_desc,因为后者需要被重复利用而没有必要反复释放和创建,以免在该 OS 的网络路径上造成不必要的 CPU 开销网卡E1000交互方法:
- E1000 使用了 DMA(direct memory access)技术,可以直接把接收到的数据包写入计算机的内存,这在数据量大的时候非常有用,可以当作缓存。
- 在发送时也可以把描述符写入内存的特定位置,这样 E1000 就会自己去找到待发送的数据,然后发送。
- 不管是接收还是发送,数据包都是以描述符数组描述的。在下面的接收和发送部分,会分别介绍接收描述符和发送描述符的格式。
//在kernel/e1000_dev.h文件中有定义
struct tx_desc
{uint64 addr; //数据包发送到地址uint16 length; //数据包长度uint8 cso; //checksum offsetuint8 cmd; //command fielduint8 status; uint8 css;uint16 special;
};
mbuf
因此首先来研究下这个结构体:struct mbuf {struct mbuf *next; // the next mbuf in the chain[下一个数据块的指针]char *head; // the current start position of the buffer[头指针]unsigned int len; // the length of the buffer[长度]char buf[MBUF_SIZE]; // the backing store[数据保存]
};
e1000_transmit()
函数接收一个mbuf类型的网络数据,并写进相应的tx_desc内存地址,让网卡能够发现这个数据有了上述知识铺垫并且根据实验提示可以实现如下代码:
//kernel/e1000.c文件:实现代码如下
int e1000_transmit(struct mbuf *m){ acquire(&e1000_lock);uint idx = regs[E1000_TDT];struct tx_desc *desc = &tx_ring[idx];if(!(desc->status & E1000_TXD_STAT_DD)){release(&e1000_lock); //标志位判断return -1;}if(tx_mbufs[idx] != NULL){mbuffree(tx_mbufs[idx]);tx_mbufs[idx] = NULL;}desc->addr = (uint64)m->head;desc->length = m->len;desc->cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;tx_mbufs[idx] = m; regs[E1000_TDT] = (idx + 1) % TX_RING_SIZE;release(&e1000_lock);return 0;
}
根据官方实验文档可以得到下述提示信息:
RX_RING_SIZE
),然后再赋值给 idxrx_desc
结构体。需要通过查询其 E1000_RXD_STAT_DD 状态位来判断其是否为一个真正待“接收处理”的rx_desc 结构体;如果不是,则表明接收环中没有需要待接收处理的数据,e1000_recv()处理逻辑终止net_rx()
,将此 mbuf 向上层传递在 e1000_recv()
中,需要一次性读出所有的待读取数据包。也就是加一个循环,然后一直读取 tail
位置的描述符,直到描述符的状态为未完成接收。
首先查看一下接受数据包的描述符
//在kernel/e1000_dev.h文件中有定义
struct rx_desc
{uint64 addr; /* Address of the descriptor's data buffer */uint16 length; /* Length of data DMAed into data buffer */uint16 csum; /* Packet checksum */uint8 status; /* Descriptor status */uint8 errors; /* Descriptor Errors */uint16 special;
};
比较重要 status
和 length
属性。网卡在写入的时候就会设置这些属性。
length
表示写入 addr
的数据包长度。status
则可以代表状态[TCPCS
、RSV
、VP
、IXSM
、EOP
、DD
]
根据上述提示以及铺垫的知识可以实现下述代码:
//kernel/e1000.c文件:实现代码如下
static void
e1000_recv(void)
{while(1){uint idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;struct rx_desc *desc = &rx_ring[idx];if(!(desc->status & E1000_RXD_STAT_DD)){return; //标志位判断到DD} rx_mbufs[idx]->len = desc->length;net_rx(rx_mbufs[idx]);rx_mbufs[idx] = mbufalloc(0);desc->addr = (uint64)rx_mbufs[idx]->head;desc->status = 0;regs[E1000_RDT] = idx;}
}
make server
make qemu
,顺利进入 xv6 的内部终端后,敲nettests
。testing ping: OK
而服务端会在终端输出:a message from xv6!
在根目录下敲入make grade
命令就可以测试整个程序
本文发布于:2024-01-27 18:03:27,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17063498041792.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |