就像传输数据包一样,您必须配置E1000来接收数据包,并提供接受描述符队列和接收描述符。3.2节描述了包接收的工作原理,包括接收队列结构和接收描述符,第14.4节中对初始化过程进行了详细说明。
Exercise 9.
阅读3.2节。您可以忽略关于 interrupts and checksum offloading(中断和校验卸载)的任何内容(如果您决定稍后使用这些特性,您可以返回到这些部分),并且您不必关心details of thresholds(临界值)以及card的内部缓存如何工作。
接收队列和发送队列非常相似,不同之处在于它由等待输入数据包填充的空数据缓冲区组成。因此,当网络空闲时,发送队列为空(因为已发送所有数据包),但接收队列已满(为空的数据包缓冲区)。
当E1000收到数据包时,它首先检查它是否与card上配置的过滤器匹配(例如,查看数据包是否已寻址到该E1000的MAC地址)。如果它不匹配任何过滤器,则忽视这个数据包。否则,E1000将从接收队列的头部检索下一个接收描述符如果头(RDH)赶上了尾(RDT),那么接收队列就没有可用的描述符,因此卡将丢弃数据包。如果有一个空闲的接收描述符,那么它将数据包数据复制到描述符所指向的缓冲区中,设置描述符的DD(Descriptor Done)和EOP(End of Packet)状态位,并增加RDH。
如果E1000在一个接收描述符中接收到一个比数据包缓冲区大的数据包,它将从接收队列中检索出尽可能多的描述符,以存储该数据包的全部内容。为了表明这种情况已经发生,它将在所有这些描述符上设置DD状态位,但仅在最后一个描述符上设置EOP状态位。您可以在驱动程序中解决这种可能性,也可以简单地将card配置为不接受“long packets”(也被称作巨型帧),并确保接收缓冲区足够大以存储最大可能的标准 以太网数据包(standard Ethernet packet)(1518字节)。
Exercise 10.
按照第14.4节中的过程设置接收队列并配置E1000。您不必支持“long packets”或者multicast。到目前为止,不要将card配置为使用中断。如果您决定使用接收中断,则可以稍后更改。l另外,配置E1000来剥离Ethernet CRC,因为grade脚本希望剥离它。
默认情况下,card将过滤所有数据包。你必须使用卡自身的MAC地址配置接收地址寄存器(RAL和RAH),才能接受发往该card的数据包。您可以简单地对QEMU的默认MAC地址52:54:00:12:34:56进行硬编码(我们已经在lwIP中对它进行了硬编码,因此在这里也不会使情况变得更糟)。要特别注意字节顺序。由于MAC地址是从最低字节到最高字节写入的,因此52:54:00:12是MAC地址的低32位,而34:56是高16位。
E1000仅支持一组特定的接收缓冲区大小(在13.4.22中的RCTL.BSIZE的说明中提供)。如果使接收数据包缓冲物足够大并禁用long packet,则不必担心数据包跨越多个接收缓冲区。另外,请记住,就像发送一样,接收队列和数据包缓冲区必须在物理内存中是连续的。
您应该至少使用128个接收描述符。
查看开发手册14.4章节中关于接收初始化的描述。流程如下:
1. 设置Receive Address寄存器(RAL/RAH)为网卡的MAC地址。
2. 初始化multicast表数组为0。
3. 设置中断相关寄存器的值,这里我们需要关闭中断。
4. 为接收描述符队列分配一块连续空间,设置RDBAL和RDBAH寄存器的值指向起始地址,其中RDBAL为32位地址,RDBAL和RDBAH表示64位地址。
5. 设置RDLEN寄存器的值为描述符队列的大小,以字节计算。
6. 设置接收队列的Head指针(RDH)和Tail指针(RDT)寄存器的值为0。Head指针指向第一个可用的描述符,Tail指向最后一个可用描述符的下一个描述符。
7. 设置接收控制寄存器RCTL的值,主要包括设置RCTL.EN标志位为1 (激活)、RCTL.LBM标志位为00(关闭回环)、RCTL.BSIZE标志位为00和RCTL.BSEX位为0(buffer大小为2048字节)、RCTL.SECRX标志位为1(忽略校验)
即使没有编写接收数据包的代码,您也可以立即进行接收功能的基本测试。运行 make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput。 测试输入将发送ARP(地址解析协议)公告数据包(使用您的数据包传输系统调用),QEMU会自动回复该消息。即使您的驱动程序尚未收到此答复,您也应该看到"e1000: unicast match[0]: 52:54:00:12:34:56",则E1000会过滤掉数据包,这意味着您可能未正确配置RAL和RAH。确保正确设置了字节顺序,并且不要忘记在RAH中设置“地址有效位”。如果没有收到任何“e1000”消息,则可能未正确启用接收功能。
接收初始化:
(寄存器的定义就不展示了,可以在手册中查到)
void e1000_receive_init(){
int i;
memset(rx_list,0,sizeof(struct rx_desc)*RX_LEN);
for(i=0;i
初始化之后进行接收功能的基本测试可以看到:
现在您已经准备好实现接收数据包。要接收数据包,驱动程序必须跟踪它希望保存下一个接收到的数据包的描述符(根据您的设计,E1000中可能已经有一个寄存器来跟踪此信息)。与发送类似,文档指出不能从软件中可靠地读取RDH寄存器,因此为了确定是否已将数据包传递到此描述符的数据包缓冲区,您必须读取描述符中的DD状态位。如果DD位置1,则可以从描述符的数据包缓冲区中复制数据包数据,然后通过更新队列的尾部索引RDT告知card描述符是空闲的。
如果未设置DD位,则表示未收到任何数据包。这与传输队列已满时的接收端等效(?)。在这种情况下,您可以执行一些操作。您可以简单地返回“try again”错误,并要求调用方重试。虽然这种方法适用于满的传输队列的情况,因为这是种暂时的情况(?),但对于空的接收队列却没有多大用处,因为接收队列可能会长时间都保持为空。第二种方法是挂起调用环境,直到接收队列中有要处理的包为止。这种策略非常类似于sys_ipc_recv。就像在IPC中一样,由于每个CPU只有一个内核堆栈,因此一旦离开内核,堆栈上的状态就会丢失。我们需要设置一个标志,指示这个环境已经被receive queue underflow挂起,并且记录了系统调用参数。这个方法的缺点是复杂性:必须指示E1000生成接收中断,并且驱动程序必须处理它们,以恢复被阻塞的环境,等待数据包。
Exercise 11.
编写一个函数以接收来自E1000的数据包,并通过系统调用将其暴露给用户空间,确保处理接收队列为空。
这里就只粘贴了处理接收的代码了,添加系统调用就不展示了,跟上面的一样。
int
handle_e1000_receive(void *addr, size_t *len){
int tdt = e1000[E1000_LOCATE(E1000_RDT)];
int next_avil_desc = (tdt+1)%RX_LEN;
if((rx_list[next_avil_desc].status & E1000_RXD_STAT_DD) == 0)
return -1;
*len = rx_list[next_avil_desc].length;
memcpy(addr,&rx_buf[next_avil_desc],*len);
rx_list[next_avil_desc].status &= ~E1000_RXD_STAT_DD;
//这里刚开始赋值成了tdt,出了错误
e1000[E1000_LOCATE(E1000_RDT)] = next_avil_desc;
return 0;
}
在network server input environment中,您将需要使用新的接收系统调用来接收数据包,并使用NSREQ_INPUT IPC信息将其传递到core network server environment.。这些IPC输入消息应附有一个页面,页面上带有union Nsipc,其中的struct jif_pkt pkt字段填充了从网络接收到的数据包。
Exercise 12.
实现net/input.c。
再次运行testinput make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput,你应该看到
Sending ARP announcement...
Waiting for packets...
e1000: index 0: 0x26dea0 : 900002a 0
e1000: unicast match[0]: 52:54:00:12:34:56
input: 0000 5254 0012 3456 5255 0a00 0202 0806 0001
input: 0010 0800 0604 0002 5255 0a00 0202 0a00 0202
input: 0020 5254 0012 3456 0a00 020f 0000 0000 0000
input: 0030 0000 0000 0000 0000 0000 0000 0000 0000
以“input: ”开头的行是QEMU ARP应答的十六进制转储。
您的代码应通过的testinput测试make grade。请注意,如果没有发送至少一个ARP数据包以通知QEMU JOS的IP地址,就无法测试数据包的接收,因此,传输代码中的错误会导致该测试失败。
为了更彻底地测试您的网络代码,我们提供了一个名为echosrv的daemon(后台程序),它设置了一个运行在端口7的echo server,该服务器将echo通过TCP连接发送的任何内容。该服务器将echo(输出)通过TCP连接发送的任何内容。使用make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-echosrv在一个终端中启动服务器,在另一个终端中make nc-7来连接它。服务器应该回显你输入的每一行。每次模拟的E1000收到数据包之后,QEMU都应在控制台上打印以下内容:
e1000: unicast match[0]: 52:54:00:12:34:56
e1000: index 2: 0x26ea7c : 9000036 0
e1000: index 3: 0x26f06a : 9000039 0
e1000: unicast match[0]: 52:54:00:12:34:56
这个时候,你应该也能够通过echosrv测试。
#include
extern union Nsipc nsipcbuf;
void sleep(int msec){
unsigned now = sys_time_msec();
unsigned end = now + msec;
if((int)now < 0 && (int)now > -MAXERROR)
panic("sys_time_msec: %e\n",(int)now);
while(sys_time_msec()
Web服务器以它最简单的形式将文件内容发送到发出请求的客户端。我们在user/httpd.c中为一个非常简单的web服务器提供了框架代码。框架代码处理到来的连接并解析头文件。
Exercise 13.
Web服务器缺少用于将文件内容发送回客户端的代码。通过实现send_file和send_data完成web服务器。
完成web服务器的安装后,启动Web服务器(make run-httpd-nox),然后将您喜欢的浏览器输入http://host:port/index。host是运行QEMU的计算机的名称,port是make which-ports为web服务器报告的端口号。您应该看到一个由运行在JOS中的http服务器提供服务的web页面。
响应客户端的请求,把服务端的数据发送到客户端。
//send_data
static int
send_data(struct http_request *req, int fd)
{
// LAB 6: Your code here.
//555忘记注释掉panic了
//panic("send_data not implemented");
int r;
size_t len;
char buf[BUFFSIZE];
//read返回的是读取到的字节数
while(1){
if((r = read(fd,buf,BUFFSIZE))<0)
return r;
len = r;
if(write(req->sock,buf,len)!=len){
die("Failed to send data to client\n");
return -1;
}
}
}
//send_file
struct Stat st;
if((r=open(req->url,O_RDONLY))<0)
return send_error(req,404);
fd = r;
if((r=fstat(fd,&st))<0){
send_error(req,404);
goto end;
}
if(st.st_isdir){
send_error(req,404);
goto end;
}
file_size = st.st_size;
啊终于完成了。