Mit 6.828_Lab6_Part B

Part B: Receiving packets and the web server

Receiving Packets

就像传输数据包一样,您必须为E1000配置接收数据包的功能,并提供接收描述符队列和接收描述符。 3.2节描述了数据包接收的工作方式,包括接收队列结构和接收描述符,初始化过程在14.4节中进行了详细说明。

exercise 9:阅读第3.2节。 您可以忽略有关中断和校验和卸载的任何信息(如果您以后决定使用这些功能,可以返回到这些部分),并且不必担心阈值的详细信息以及卡的内部缓存如何工作。

接收队列与发送队列非常相似,不同之处在于它由等待输入数据包填充的空数据包缓冲区组成。 因此,当网络空闲时,发送队列为空(因为已发送所有数据包),但接收队列已满(为空数据包缓冲区)。

当E1000收到数据包时,它首先检查它是否与卡上配置的过滤器匹配(例如,查看数据包是否已寻址到该E1000的MAC地址),并在不匹配任何过滤器时忽略该数据包。 否则,E1000尝试从接收队列的开头检索下一个接收描述符。 如果头部(RDH)追上了尾部(RDT),则接收队列中没有可用的描述符,因此卡将丢弃数据包。 如果有空闲的接收描述符,它将把数据包数据复制到描述符所指向的缓冲区中(这一步完成填充),设置描述符的DD(描述符完成)和EOP(数据包结尾)状态位,并增加RDH。

如果E1000在一个接收描述符中接收到一个比数据包缓冲区大的数据包,它将从接收队列中检索所需数量的描述符,以存储该数据包的全部内容。 为了表明这种情况已经发生,它将在所有这些描述符上设置DD状态位,但仅在这些描述符的最后一个上设置EOP状态位。 您可以在驱动程序中解决这种可能性,也可以简单地将卡配置为不接受“长数据包”(也称为巨型帧),并确保接收缓冲区足够大以存储最大可能的标准以太网数据包(1518字节) )。

exercise 10:按照第14.4节中的过程设置接收队列并配置E1000。 您不必支持“长数据包”或多播。 目前,不要将卡配置为使用中断。 如果您决定使用接收中断,则可以稍后更改。 同样,将E1000配置为剥离以太网CRC,因为等级脚本希望将其剥离。

默认情况下,卡将过滤掉所有数据包。 您必须使用卡自身的MAC地址配置接收地址寄存器(RAL和RAH),才能接受发往该卡的数据包。 您可以简单地对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的说明中提供)。 如果使接收数据包缓冲区足够大并禁用长数据包,则不必担心数据包跨越多个接收缓冲区。 另外,请记住,就像发送一样,接收队列和数据包缓冲区在物理内存中必须是连续的。

您应该至少使用128个接收描述符
14.4章节中接收初始化的操作主要是几个以下步骤:

1.设置RAL/RAH为52:54:00:12:34:56
2.将MTA数组初始化为0(MTA数组占4096bit,即512字节,见13.5.1节)
3.设置IMS寄存器来配置中断(忽略此步,暂时不使用中断)
4.特定的情况下设置RDTR(也跟中断有关,暂时不用设置)
5.为接收描述符队列分配一块内存,确保内存按16字节对齐(上面代码已经分配好了),使用内存始址设置RDBAL/RDBAH寄存器,RDBAL用于保存32位地址,RDBAL+RDBAH用于保存64位地址(跟设置TDBAL/TDBAH是一样的)
6.使用接收描述符队列的大小(字节)来设置RDLEN寄存器,该值必须是128的倍数
7.将RDH(接收描述符队列头)和RDT(接收描述符队列尾)初始化为合适的值,RDH应该指向第一个可用的接收描述符,RDT应该指向最后一个可用的接收描述符的下一个描述符(在初始化时,所有包都可用,RDH应该设为0,但RDT不能设为0,因为上面提到了RDH
= RDT时,网卡接收会丢包,所以这里设为RX_MAX_LEN-1,让RDT指向最后一个可用的描述符,那么(RDT+1)%RX_MAX_LEN就是第一个可用或不可用的描述符)。
8.设置RCTL寄存器的值:
8.1 设置RCTL.EN位为1
8.2设置RCTL.LPE为1来支持长数据包(这步我会将该寄存器设为0,因为我不打算支持长包)
8.3设置RCTL.LBM(loop back mode)为00
8.4设置RCTL.RDMTS为你想要的值(MTS == Minimum Threshold Size,设置临界值,练习9提到无需关注该部分,这里忽略)
8.5 设置RCTL.MO(Multicast Offset)为你想要的值(这里也忽略,不关注multicast的部分)
8.6 设置RCTL.BAM为1来接收广播
8.7 根据设置的接收缓冲区大小来配置RCTL.BSIZE和RCTL.BSEX,(这里根据上面提供的图可知两个寄存器都设为0)
8.8 设置RCTL.SECRC为1

1.首先按照接收描述符的结构定义结构体,并分配相应大小的空间
在这里插入图片描述

//define the receive descriptor
struct rx_desc 
{
	uint64_t addr;
	uint16_t length;
	uint16_t checksum;
	uint8_t status;
	uint8_t errors;
	uint16_t special;
}__attribute__((packed));

//receive
struct tBuffer rDescbuffer[128]; 
struct rx_desc rRing[128];	

2.然后在e1000.c中编写接收初始化的代码(注意memset必须在mac网址设置之前,不然会将设置好的mac网址清除掉)

//it is very familiar with transmitInit()
void e1000_receiveInit()
{
	memset(rRing,0,sizeof(struct rx_desc)*RX_MAX_LEN);
	memset(rDescbuffer,0,sizeof(struct tBuffer)*RX_MAX_LEN);
	for(int i=0;i<RX_MAX_LEN;i++) //为每个接收描述符绑定一个发送包缓冲区,注意要绑定的是其物理地址
	{	
		rRing[i].addr = PADDR(rDescbuffer[i].buffer);
	}
	memset((char*)(&E1000[E1000_MTA/4]),0,MTA_SIZE);
	E1000[E1000_RA/4] = 0x52 | (0x54<<8) | (0x00<<16) | (0x12<<24);
	E1000[E1000_RA/4 + 1] = (0x34) | (0x56<<8) | E1000_RAH_AV;
	//our machine is 32 bites,so the high bites is 0
	E1000[E1000_RDBAL/4] = PADDR(rRing);
	E1000[E1000_RDBAH/4] = 0;
	E1000[E1000_RDLEN/4] = RX_MAX_LEN*sizeof(struct rx_desc);
	E1000[E1000_RDH/4] = 0;
	E1000[E1000_RDT/4] = RX_MAX_LEN -1;
	//pci_e1000[E1000_TCTL>>2] |= 0x4010A;
	E1000[E1000_RCTL/4] = (E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_LBM_NO | E1000_RCTL_SECRC);
	
}

即使没有编写接收数据包的代码,也可以立即进行接收功能的基本测试。 运行make E1000_DEBUG = TX,TXERR,RX,RXERR,RXFILTER run-net_testinput。 testinput将发送ARP(地址解析协议)公告包(使用您的包传输系统调用),QEMU将自动回复该公告包。 即使您的驱动程序尚未收到此答复,您也应该看到 "e1000: unicast match[0]: 52:54:00:12:34:56"消息,表明E1000已接收并匹配了一个数据包 配置的接收过滤器。 如果看到的是"e1000: unicast mismatch: 52:54:00:12:34:56"消息,则E1000会过滤掉数据包,这意味着您可能未正确配置RAL和RAH。 确保正确设置了字节顺序,并且不要忘记在RAH中设置“地址有效”位。 如果没有收到任何“ e1000”消息,则可能未正确启用接收功能。
测试结果:
在这里插入图片描述
现在您已经准备好实现接收数据包。 要接收数据包,驱动程序将必须跟踪它希望保存下一个接收到的数据包的描述符(提示:根据您的设计,E1000中可能已经有一个寄存器来跟踪此信息)。 与发送类似,文档指出无法从软件中可靠地读取RDH寄存器,因此,要确定是否已将数据包传递到此描述符的数据包缓冲区,您必须读取描述符中的DD状态位。 如果DD位置1,则可以从该描述符的数据包缓冲区中复制数据包数据,然后通过更新队列的尾部索引RDT告知卡该描述符是空闲的。

如果未设置DD位,则表示未收到任何数据包。这与发送队列已满时的接收端等效,在这种情况下,您可以执行一些操作。您可以简单地返回“重试”错误,并要求调用方重试。尽管此方法在整个传输队列中都很好,因为这是一个短暂的条件,但对于空的接收队列却没有多大用处,因为接收队列可能会长时间保持为空。第二种方法是暂停调用环境,直到接收队列中有要处理的数据包为止。此策略与sys_ipc_recv非常相似。就像在IPC中一样,由于每个CPU只有一个内核堆栈,因此一旦离开内核,堆栈上的状态就会丢失。我们需要设置一个标志,指示接收队列下溢已暂停环境并记录系统调用参数。这种方法的缺点是复杂性:必须指示E1000生成接收中断,并且驱动程序必须处理它们,以恢复被阻塞的环境,等待数据包。

exercise 11:编写一个函数以接收来自E1000的数据包,并通过添加系统调用将其暴露给用户空间。 确保处理了接收队列为空的情况(这种情况的处理方式可以参考前文的叙述)。
1.kern/e1000.c

//收包函数(不支持长包),驱动程序从尾部接收数据包,网卡从头部写入数据包
int e1000_receive_data(void* addr)
{
	int tail = E1000[E1000_RDT/4];
	//note : it is a ring
	struct rx_desc* next_desc = &rRing[(tail+1) % RX_MAX_LEN];
	//DD位为0,说明接收描述符队列中无数据
	if(!(next_desc->status&E1000_RXD_STAT_DD)) return 0;
	//DD位被设置,说明该描述符对应的缓冲区已被网卡写入数据,将该数据包复制到给定的地址
	memcpy(addr,KADDR(next_desc->addr),next_desc->length); 
	//复制完后,重新置零,表示该接收描述符可用
	next_desc->status &= ~E1000_RXD_STAT_DD;
	//尾指针后移
	E1000[E1000_RDT/4] = tail; 
	return next_desc->length; //返回本次接收的字节数
}

2.kern/e1000.h

int e1000_receive_data(void* addr)

3.lib/syscall.c

int sys_e1000_receive(void* addr)
{
	return syscall(SYS_e1000_receive, 0,(uint32_t) addr,0,0,0,0);
}

4.kern/syscall.c

int sys_e1000_receive(void* addr)
{
	return e1000_receive_data(addr);
}
case SYS_e1000_receive: return sys_e1000_receive((void*) addr);

5.inc/syscall.h

SYS_e1000_receive,

6.inc/lib.h

int 	sys_e1000_receive(void* addr);

Receiving Packets: Network Server

在网络服务器输入环境中,您将需要使用新的系统调用来接收数据包,并使用NSREQ_INPUT IPC消息将其传递到核心网络服务器环境。 这些IPC输入消息应具有一个页面,页面上带有联合Nsipc,其struct jif_pkt pkt字段填充了从网络接收到的数据包。
exercise 12:实现net/input.c.

#include "ns.h"

extern union Nsipc nsipcbuf;

void
input(envid_t ns_envid)
{
	binaryname = "ns_input";

	// LAB 6: Your code here:
	// 	- read a packet from the device driver
	//	- send it to the network server
	// Hint: When you IPC a page to the network server, it will be
	// reading from it for a while, so don't immediately receive
	// another packet in to the same physical page.
	int length;
	char tmp[2048];
	while(1)
	{	
		if(!(length = sys_e1000_receive(tmp))) //尝试接收网卡输入
			sys_yield();
		else //收到了数据包
		{	
			nsipcbuf.pkt.jp_len = length;
			memcpy(nsipcbuf.pkt.jp_data, tmp, length);
            ipc_send(ns_envid, NSREQ_INPUT, &nsipcbuf, PTE_U | PTE_P);
            for(int i=0; i<500; i++)
				    sys_yield();
		}
	}
}

测试:(通过)
在这里插入图片描述

The Web Server

最简单形式的Web服务器将文件内容发送到发出请求的客户端。 我们在user / httpd.c中提供了一个非常简单的Web服务器的框架代码。 框架代码处理传入的连接并解析标头。

exercise 13:Web服务器缺少用于将文件内容发送回客户端的代码。 通过实现send_file和send_data完成Web服务器。
1.send_file

// LAB 6: Your code here.
	//panic("send_file not implemented");
	
	struct Stat stat;
	// open the requested url for reading
	if((fd = open(req->url,O_RDONLY)) <0)
		return send_error(req,404);
	// if the file does not exist, send a 404 error using send_error
	if((r = fstat(fd,&stat))<0)
		{
			send_error(req,404);
			goto end;
		}
	// if the file is a directory, send a 404 error using send_error
	if(stat.st_isdir)
		{
			send_error(req,404);
			goto end;
		}
	// set file_size to the size of the file
	file_size = stat.st_size;

2.send_data

static int send_data(struct http_request *req, int fd)
{
	// LAB 6: Your code here.
	//panic("send_data not implemented");
	char buffer[BUFFSIZE];
	int r,sum=0;
	while((r=read(fd,buffer,BUFFSIZE))>0) //循环读取数据,每次512字节
	{
		if(r != write(req->sock,buffer,r))
			die("send_data:write failed to client\n");
		sum += r;
	}
	return sum;
}

测试通过:
在这里插入图片描述
到目前为止,mit6.828的学习已全部完成,接下来会进行一遍总结。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值