本实验的主要内容:
- 网卡初始化以及发送数据包。
- 接收数据包以及 web 服务器。
1、启动网络服务器
简述启动网络服务器的过程。
网络服务器在系统启动时初始化,
--> i386_init(void)
...
// hardware initialization functions
// 发现 PCI 设备并初始化。
--> pci_init()
// 遍历 PCI 总线寻找设备。当它找到一个设备时,它会读取它的供应商 ID 和设备 ID,并使用这两个值作为 // 一个键来搜索 `pci_attach_vendor` 数组。
// 如果发现的设备的供应商 ID 和设备 ID 与数组中的某个条目匹配,PCI 代码将调用该条目的 // `attachfn` 来执行设备初始化。
--> pci_scan_bus(&root_bus)
--> bhlc = pci_conf_read(&df, PCI_BHLC_REG)
--> pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off)
--> inl(pci_conf1_data_ioport)
--> pci_attach(&af)
--> pci_attach_match()
...
// Start ns.
--> ENV_CREATE(net_ns, ENV_TYPE_NS)
// net/serv.c
--> umain(int argc, char **argv)
--> ns_envid = sys_getenvid()
// fork off the timer thread which will send us periodic messages
--> timer_envid = fork() // 创建计时器线程
// 计时器进程定期向核心网络服务器发送`NSREQ_TIMER`类型的消息,通知它计时器已过期。
--> timer(ns_envid, TIMER_INTERVAL)
// fork off the input thread which will poll the NIC driver for input
// packets
--> input_envid = fork() // 创建输入辅助线程
// - 从设备驱动程序读取数据包
// - 发送到网络服务器
--> input(ns_envid)
// fork off the output thread that will send the packets to the NIC
// driver
--> output_envid = fork() // 创建输出辅助线程
// - 从网络服务器读取数据包
// - 发送到设备驱动程序
--> output(ns_envid)
// lwIP requires a user threading library; start the library and jump
// into a thread to continue initialization.
--> thread_init()
--> thread_create(0, "main", tmain, 0)
--> tmain(uint32_t arg)
// 初始化网络服务器
// 主要是 TCP/IP 协议的一些内容
--> serve_init(inet_addr(IP),inet_addr(MASK),inet_addr(DEFAULT))
// 运行网络服务器
--> serve()
--> thread_yield()
2、发送数据包
以 testoutput.c
为例。
// net/testoutput.c
--> umain(int argc, char **argv)
--> ns_envid = sys_getenvid()
// 创建输出辅助线程
--> output_envid = fork()
// 运行输出辅助线程
--> output(ns_envid)
// 为将要发送的数据分配内存
--> r = sys_page_alloc(0, pkt, PTE_P|PTE_U|PTE_W)
// 从 ns 发送消息给 输出线程
--> ipc_send(output_envid, NSREQ_OUTPUT, pkt, PTE_P|PTE_W|PTE_U)
// 回收内存
--> sys_page_unmap(0, pkt)
// 调度等待数据包发送完毕
--> sys_yield()
--> output(envid_t ns_envid)
// 接收到来自 ns 的数据包
--> ipc_recv()
// 发送数据包给 e1000 网卡
--> sys_pkt_try_send()
// 执行完毕
--> sys_yield()
3、接收数据包
以 testinput.c
为例。
// net/testinput.c
--> umain(int argc, char **argv)
--> ns_envid = sys_getenvid()
// 创建输出辅助线程
--> output_envid = fork()
// 运行输出辅助线程
--> output(ns_envid)
// 创建输入辅助线程
--> input_envid = fork()
// 运行输入辅助线程
--> input(ns_envid)
// 声明本机 IP
--> announce()
// 从输入进程得到数据
--> ipc_recv((int32_t *)&whom, pkt, &perm)
// 以十六进制打印
--> hexdump("input: ", pkt->jp_data, pkt->jp_len)
--> input(envid_t ns_envid)
// 从 e1000 网卡接收数据包
--> sys_pkt_try_receive()
// 将接收到数据包宝贝到目的地址
--> memcpy()
// 发送数据包给 ns
--> ipc_send()
// 等待 50 ms (ns 读取 IPC 数据),再接收下一个数据包
--> sleep(50)
4、Web 服务器
如 user/httpd.c
所示。
// user/httpd.c
--> umain(int argc, char **argv)
// Create the TCP socket.
--> serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)
// Construct the server sockaddr_in structure.
--> memset(&server, 0, sizeof(server)); // Clear struct
--> server.sin_family = AF_INET; // Internet/IP
--> server.sin_addr.s_addr = htonl(INADDR_ANY); // IP address
--> server.sin_port = htons(PORT); // server port = 80
// Bind the server socket.
--> bind(serversock, (struct sockaddr *) &server, sizeof(server)
// Listen on the server socket.
--> listen(serversock, MAXPENDING)
// Wait for client connection.
--> clientsock = accept(serversock, (struct sockaddr *) &client, &clientlen)
// 处理来自客户端请求。
--> handle_client(clientsock)
// Receive message.
// 从 sock 套接字处读取信息。
--> received = read(sock, buffer, BUFFSIZE)
// given a request, this function creates a struct http_request.
// - 将 request 的前4个字节与 ‘GET’ 比较。
// - 第五个字节到下一个空格之间的内容为 url。
// - 接着是 http 版本。
// - 即 GET url version,无 entity,构造一个结构体 req。
--> r = http_request_parse(struct http_request *req, char *request)
// 发送 struct http_request *req, 即返回的 HTTP 报文结构。
// open the requested url for reading.
// if the file does not exist, send a 404 error using send_error.
// if the file is a directory, send a 404 error using send_error.
// set file_size to the size of the file.
--> send_file(req)
// Open a file (or directory).
// Send a file-open request to the file server.
--> r = open(req->url, O_RDONLY)
// http 头.
--> r = send_header(req, 200)
// Content-Length.
--> r = send_size(req, file_size)
// Content-Type.
--> r = send_content_type(req)
// 终止符。
--> r = send_header_fin(req)
// 从 fd 中读 size 大小数据,并发送。
--> r = send_data(req, fd)
// 关闭文件描述符。
--> close(fd)
// 回收 req 结构体。
--> req_free(req)
// 关闭客户端 socket
--> close(sock)
// 关闭服务器 socket
--> close(serversock)
C/S 通信过程到此结束。
- PCI 设备在使用之前需要被发现和初始化。发现是在 PCI 总线上寻找连接设备的过程。初始化是分配 I/O 和内存空间以及协商 IRQ 线以供设备使用的过程。