实验地址

The Network Server

写个网络栈比较麻烦,这里直接用 lwip 这个开源协议簇实现下面功能,lwip 就像一个实现了 BSD socket 接口的黑箱,并有一个包输入端口和输出端口。

JOS 网络服由四个进程组成:

核心网络服务器进程(包括 socket 分发和 lwip)
socket 分发器跟文件系统一样,用户进程通过 stubs 发送 IPC 消息给核心网络服务器进程,但是用户进程不会直接使用 nsipc_* 而是使用 lib/sockets.c 的封装,用户进程就可以通过文件描述符访问 socket。由于 BSD socket 系统调用例如 acceptrecv 可能会造成阻塞,分发器直接调用会造成自己总线程阻塞,即是只能提供一个网络服务,所以使用线程避免这个情况
输入进程
每个从设备驱动接收到的 packet,输入进程从 kernel spacel 拉取并用 IPC 转发给核心网络服务器进程
输出进程
lwip 生成 packets 并通过系统调用将 packet 发送到设备驱动
计时器进程
周期性发送 NSREQ_TIMER 给核心网络服务器进程通知已经超时
Figure 1: 框架图

Figure 1: 框架图

Part A: Initialization and transmitting packets

首先需要把一个时间中断加入内核

  • Exercise 1

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    static void
    trap_dispatch(struct Trapframe *tf)
    {
      // Handle clock interrupts. Don't forget to acknowledge the
      // interrupt using lapic_eoi() before calling the scheduler!
      // LAB 4: Your code here.
      if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
        time_tick();
        lapic_eoi();
        sched_yield();
        return ;
      }
    }

The Network Interface Card

E1000 的开发手册

PCI Interface

E1000 是一个 PCI 设备,意味着 E1000 是插在主板的 PCI 总线上。PCI 总线有地址、数据和中断线,并允许 CPU 与 PCI 设备的读写交互。PCI 设备在使用前需要被发现并完成初始化。发现是遍历 PCI 总线并查找设备的过程;初始化是分配 I/O 和内存空间并协调 IRQ 线的过程。

为了在启动过程中初始化 PCI 设备,代码首先遍历 PCI 总线查找设备,在找到设备后把 vendor ID 跟 device ID 作为 key 搜索 pci_attach_vendor 数组,数组内是 struct pci_drive

1
2
3
4
struct pci_driver {
    uint32_t key1, key2;
    int (*attachfn) (struct pci_func *pcif);
};

若在数组内找到设备对应的 vendor ID 跟 device ID,代码会调用 attachfn 初始化设备。 attachfn 是一个指向 PCI 视始化的函数指针。 struct pci_func 结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct pci_func {
  struct pci_bus *bus;

  uint32_t dev;
  uint32_t func;

  uint32_t dev_id;
  uint32_t dev_class;

  uint32_t reg_base[6];  // 内存映射 I/O 的基地址
  uint32_t reg_size[6];  // reg_base 的大小,字节为单位
  uint8_t irq_line;  // IRQ 地址
};

attachfn 被调用的时候,设备仅仅是被找到但还没启用, attachfn 需要调用 pci_func_enable 启用设备,分配资源和补全 struct pci_func 结构。

  • Exercise 3

    完成 E1000 的 attachfn 部分

    QEMU 模拟的 82540EM 对应的 vendor ID 跟 device ID 如下

    Figure 2: 82540EM vendor ID 跟 device ID

    Figure 2: 82540EM vendor ID 跟 device ID

    kern/e1000.h

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    #ifndef JOS_KERN_E1000_H
    #define JOS_KERN_E1000_H
    
    #include "kern/pci.h"
    
    #define E1000_VENDOR_ID_82540EM 0x8086
    #define E1000_DEVICE_ID_82540EM 0x100e
    
    int e1000_attachfn(struct pci_func *pcif);
    
    #endif  // SOL >= 6

    kern/e1000.c

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    #include <kern/e1000.h>
    #include <kern/pmap.h>
    
    // LAB 6: Your driver code here
    
    int
    e1000_attachfn(struct pci_func *pcif) {
      pci_func_enable(pcif);
      return 0;
    }

    pci_attach_vendor[]

    1
    2
    3
    4
    5
    6
    
    // pci_attach_vendor matches the vendor ID and device ID of a PCI device. key1
    // and key2 should be the vendor ID and device ID respectively
    struct pci_driver pci_attach_vendor[] = {
      {E1000_VENDOR_ID_82540EM, E1000_DEVICE_ID_82540EM, &e1000_attachfn},
      {0, 0, 0},
    };

Memory-mapped I/O

软件通过内存映射 I/O(MMIO)与 E1000 交互,读写的位置是设备本身而不是 DRAM。

pci_func_enable 得到 E1000 分配的 MMIO 区域并存储至 BAR0(reg_base[0], reg_size[0]) 内,这部分区域是设备的物理内存地址,可通过内存地址访问。需要通过 mmio_map_region() 把 E1000 的区域映射到 MMIOBASE 上面。

  • Exercise 4

    使用 mmio_map_region() 创建内存映射

    kern/e1000.h

    1
    2
    3
    4
    5
    
    #include "inc/types.h"
    
    extern void *mmio_map_region(physaddr_t pa, size_t size);
    
    #define E1000_STATUS 0x00008 / 4 /* Device Status - RO */

    e1000_attachfn()

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    int
    e1000_attachfn(struct pci_func *pcif) {
      // ...
    
      // memory mapping
      mmio_e1000 =
          mmio_map_region((physaddr_t)pcif->reg_base[0], (size_t)pcif->reg_size[0]);
      assert(mmio_e1000[E1000_STATUS] == 0x80080783);
    
      return 0;
    }

DMA

DMA 介绍

主要讲 E1000 使用 DMA 直接读写内存,发送和接收两个队列都是一个环形数组实现,都包含一个头指针和尾指针。

Transmitting Packets

E1000 发送和接收功能相互独立,需要首先实现发送功能。

首先需要初始化发送队列,队列具体结构跟描述符结构如下图。

Figure 3: 队列结构

Figure 3: 队列结构

Figure 4: 描述符结构 1

Figure 4: 描述符结构 1

Figure 5: 描述符结构 2

Figure 5: 描述符结构 2

C Structures

合理的传输传递符如下:

1
2
3
4
5
6
63            48 47   40 39   32 31   24 23   16 15             0
+---------------------------------------------------------------+
|                         Buffer address                        |
+---------------|-------|-------|-------|-------|---------------+
|    Special    |  CSS  | Status|  Cmd  |  CSO  |    Length     |
+---------------|-------|-------|-------|-------|---------------+

对应代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct tx_desc
{
  uint64_t addr;
  uint16_t length;
  uint8_t cso;
  uint8_t cmd;
  uint8_t status;
  uint8_t css;
  uint16_t special;
};

驱动需要保存传输传输描述符数组和描述符所指的数据包缓冲,由于 E1000 直接访问物理内存,意味着数据包缓冲需要在物理内存中是连续的。

最简单的方法是在初始化的时候为数据包缓冲保留空间。以太网包最大为 1518 字节,所以缓冲区分配大小也需要这么多。

  • Exercise 5

    根据手册 14.5 描述实现:

    Figure 6: 发送过程初始化

    Figure 6: 发送过程初始化

    1. 分配一块内存作为发送描述符队列,地址要 16 字节对齐,用分配得到的基地址填充 TDBAL/TDBAH 寄存器,TDBAL 是基地址低 32 位;TDBAH 是高 32 位。
    2. 设置传输描述符长度寄存器 TDLEN,值必须是 128 字节对齐。
    3. 传输描述符寄存器 TDH/TDT 需要设置为 0
    4. 初始化传输控制寄存器 TCTL
      • TCTL.EN = 1
      • TCTL.PSP = 1
      • TCTL.CT 为以太网标准值 10h,半双工模式
      • TCTL.COLD 全双工模式下 10/100Mb/s 值为 40h
    5. 配置 TIPG 寄存器,根据手册表 13-77 设置
    Figure 7: TIPG 寄存器 1

    Figure 7: TIPG 寄存器 1

    Figure 8: TIPG 寄存器 2

    Figure 8: TIPG 寄存器 2

     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
    
    int e1000_transmit_init(void) {
      assert(mmio_e1000[E1000_STATUS] == 0x80080783);
      // 1.1 分配一块内存作为发送描述符队列
      init_desc();
    
      // 1.2 TDBAL 寄存器
      mmio_e1000[E1000_TDBAL] = PADDR(tx_descs);
      // 1.3 TDBAH 寄存器
      mmio_e1000[E1000_TDBAH] = 0;
    
      // 2 TDLEN 寄存器
      mmio_e1000[E1000_TDLEN] = VALUEMASK(TXRING_LEN, E1000_TDLEN_LEN);
    
      // 3.1 TDH 寄存器
      mmio_e1000[E1000_TDH] = 0;
      // 3.2 TDT 寄存器
      mmio_e1000[E1000_TDT] = 0;
    
      // 4 TCTL 寄存器
      // 4.1 TCTL.EN = 1
      // 4.2 TCTL.PSP = 1
      // 4.3 TCTL.CT = 0x10
      // 4.4 TCTL.COLD = 0x40
      mmio_e1000[E1000_TCTL] =
          VALUEMASK(1, E1000_TCTL_EN) | VALUEMASK(1, E1000_TCTL_PSP) |
          VALUEMASK(0x10, E1000_TCTL_CT) | VALUEMASK(0x40, E1000_TCTL_COLD);
    
      // 5 TIPG 寄存器
      // 5.1 TIPG.IPGT = 10
      // 5.2 TIPG.IPGR1 = 4(IPGR2 * 2/3)
      // 5.3 TIPG.IPGR2 = 6
      mmio_e1000[E1000_TIPG] = VALUEMASK(0x10, E1000_TIPG_IPGT) |
                               VALUEMASK(0x4, E1000_TIPG_IPGR1) |
                               VALUEMASK(0x6, E1000_TIPG_IPGR2);
      return 0;
    }

    运行 make E1000_DEBUG=TXERR,TX qemu 后运行结果如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    check_page_installed_pgdir() succeeded!
    SMP: CPU 0 found 1 CPU(s)
    enabled interrupts: 1 2 4
    PCI: 00:00.0: 8086:1237: class: 6.0 (Bridge device) irq: 0
    PCI: 00:01.0: 8086:7000: class: 6.1 (Bridge device) irq: 0
    PCI: 00:01.1: 8086:7010: class: 1.1 (Storage controller) irq: 0
    PCI: 00:01.3: 8086:7113: class: 6.80 (Bridge device) irq: 9
    PCI: 00:02.0: 1234:1111: class: 3.0 (Display controller) irq: 0
    PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11
    PCI function 00:03.0 (8086:100e) enabled
    e1000: tx disabled
    [00000000] new env 00001000
  • Exercise 6

    目前初始化已经完成,现在写发送数据包的代码。

    可能会遇到发送队列已满的情况,检测方法:设置了发送描述符的 RS 位,当网卡发送包,会设置描述符的 DD 位,通过 DD 位知道某描述符是否被回收。

    已经满后,根据自身决策处理,这里采取重试方式

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    int e1000_transmit(void *data, size_t len) {
      uint32_t current = mmio_e1000[E1000_TDT];
    
      if ((tx_descs[current].status & E1000_TXD_STAT_DD) != E1000_TXD_STAT_DD) {
        return -1;
      }
    
      len = len > TX_PACKAGE_SIZE ? TX_PACKAGE_SIZE : len;
    
      memcpy(&tx_buffers[current], data, len);
      tx_descs[current].length = len;
      tx_descs[current].status &= ~E1000_TXD_STAT_DD;
      tx_descs[current].cmd |= (E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS);
    
      mmio_e1000[E1000_TDT] = (current + 1) % TXRING_LEN;
    
      return 0;
    }

    运行 make E1000_DEBUG=TXERR,TX qemu 看到如下结果

    1
    2
    3
    4
    5
    
    PCI function 00:03.0 (8086:100e) enabled
    reg_base: febc0000, reg_size: 20000
    e1000: tx disabled
    e1000: index 0: 0x2b1000 : 1000012 0
    [00000000] new env 00001000

    运行 tcpdump -XXnr qemu.pcap 可以看到发送包的数据

    1
    2
    3
    4
    5
    
    [email protected]:~/lab/newlab# tcpdump -XXnr qemu.pcap
    reading from file qemu.pcap, link-type EN10MB (Ethernet)
    05:58:30.878055 69:73:73:69:6f:6e > 54:72:61:6e:73:6d, ethertype Unknown (0x2074), length 18:
    0x0000:  5472 616e 736d 6973 7369 6f6e 2074 6573  Transmission.tes
    0x0010:  742e                                     t.
  • Exercise 7

    添加发送包的系统调用。

    inc/lib.h

    1
    
    int sys_package_send(void *addr, size_t len);

    inc/syscall.h

    1
    2
    3
    4
    5
    
    enum {
      // ...
      SYS_package_send,
      // ...
    };

    lib/syscall.c

    1
    2
    3
    
    int sys_package_send(void *data, size_t len) {
      return syscall(SYS_package_send, (uint32_t)data, len, 0, 0, 0);
    }

    kern/syscall.c

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    int sys_package_send(void *data, size_t len) {
      return e1000_transmit(data, len);
    }
    
    
    int32_t
    syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
    {
      // ...
      switch (syscallno) {
        case SYS_package_send:
          ret = sys_package_send((void *)a1, (size_t)a2);
          break;
      }
      // ...
    }

Transmitting Packets: Network Server

输出协助进程的作用:执行一个无限循环,从核心网络服务器接收 NSREQ_OUTPUT IPC 消息并通过系统调用发送给网卡驱动。这个消息由 low_level_output() 发送,每一个 IPC 消息都由 union Nsipcstruct jif_pkt 组成。

  • Exercise 8

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    void
    output(envid_t ns_envid)
    {
      binaryname = "ns_output";
    
      // LAB 6: Your code here:
      //    - read a packet from the network server
      //    - send the packet to the device driver
      envid_t envid = 0;
      int perm = 0;
    
      while (1) {
        if (ipc_recv((envid_t *)&envid, &nsipcbuf, &perm) != NSREQ_OUTPUT) {
          continue;
        }
    
    
        while (sys_package_send((void *)nsipcbuf.pkt.jp_data, (size_t)nsipcbuf.pkt.jp_len) < 0) {
          sys_yield();
        }
      }
    }

    运行 make E1000_DEBUG=TXERR,TX run-net_testoutput 得到如下结果。

     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
    
    e1000: tx disabled
    FS is running
    FS can do I/O
    Device 1 presence: 1
    Transmitting packet 0
    Transmitting packet 1
    e1000: index 0: 0x2b0040 : 9000009 0
    Transmitting packet 2
    e1000: index 1: 0x2b062e : 9000009 0
    block cache is good
    Transmitting packet 3
    e1000: index 2: 0x2b0c1c : 9000009 0
    superblock is good
    bitmap is good
    Transmitting packet 4
    e1000: index 3: 0x2b120a : 9000009 0
    Transmitting packet 5
    e1000: index 4: 0x2b17f8 : 9000009 0
    Transmitting packet 6
    e1000: index 5: 0x2b1de6 : 9000009 0
    Transmitting packet 7
    e1000: index 6: 0x2b23d4 : 9000009 0
    Transmitting packet 8
    e1000: index 7: 0x2b29c2 : 9000009 0
    Transmitting packet 9
    e1000: index 8: 0x2b2fb0 : 9000009 0
    e1000: index 9: 0x2b359e : 9000009 0
    No runnable environments in the system!

    通过 tcpdump -XXnr qemu.pcap 得到如下输出。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    [email protected]:~/lab/mit_6.828_jos# tcpdump -XXnr qemu.pcap
    reading from file qemu.pcap, link-type EN10MB (Ethernet)
    07:21:14.798168 [|ether]
            0x0000:  5061 636b 6574 2030 30                   Packet.00
    07:21:14.807358 [|ether]
            0x0000:  5061 636b 6574 2030 31                   Packet.01
    07:21:14.820227 [|ether]
            0x0000:  5061 636b 6574 2030 32                   Packet.02
    07:21:14.824279 [|ether]
            0x0000:  5061 636b 6574 2030 33                   Packet.03
    07:21:14.838598 [|ether]
            0x0000:  5061 636b 6574 2030 34                   Packet.04
    07:21:14.852139 [|ether]
            0x0000:  5061 636b 6574 2030 35                   Packet.05
    07:21:14.853679 [|ether]
            0x0000:  5061 636b 6574 2030 36                   Packet.06
    07:21:14.855394 [|ether]
            0x0000:  5061 636b 6574 2030 37                   Packet.07
    07:21:14.856594 [|ether]
            0x0000:  5061 636b 6574 2030 38                   Packet.08
    07:21:14.857184 [|ether]
            0x0000:  5061 636b 6574 2030 39                   Packet.09
  • Q1

    How did you structure your transmit implementation? In particular, what do you do if the transmit ring is full?

    循环队列检测下个描述符是否合法;通过多次循环等待直到空闲。

Part B: Receiving packets and the web server

Receiving Packets

接收队列与发送队列的唯一区别是发送队列一开始为空而接收队列是包含空接收缓冲区的满载队列。

E1000 在收到包后首先检查是否匹配网卡的过滤配置,符合的话就获取空余的接收描述符,若队列已经满了则丢包,否则把包数据复制到缓冲区并设置描述符 DD 跟 EOP 两个状态值,随后自增 RDH。

若接收到的包比缓冲区设定的单个缓冲大小还大,则获取多个接收描述符来存储这个包。

  • Exercise 10

    根据下图实现接受过程的初始化。(跳过中断和 CRC 部分)

    由于网卡会过滤所有数据包,所以要配置接收地址寄存器一个 MAC 地址,让数据包能让该网卡接收。这里使用 QEMU 的默认 MAC 地址 52:54:00:12:34:56参考地址) ,而 MAC 地址的字节序从低序到高序。

    Figure 9: 接收过程初始化

    Figure 9: 接收过程初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    static void init_desc(void) {
      // ...
      for (i = 0; i < RXRING_LEN; ++i) {
        memset(&rx_desc_arr[i], 0, sizeof(struct e1000_rx_desc));
        memset(&rx_buffer_arr[i], 0, RX_PACKAGE_SIZE);
        rx_desc_arr[i].addr = PADDR(&rx_buffer_arr[i]);
        rx_desc_arr[i].status = E1000_RXD_STAT_DD | E1000_RXD_STAT_EOP;
      }
    }
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    int e1000_receive_init(void) {
      /* QEMU 默认 MAC: 52 : 54 : 00 : 12 : 34 : 56 */
      mmio_e1000[E1000_RAL] = 0x12005452;
      mmio_e1000[E1000_RAH] = 0x00005634 | E1000_RAH_AV;
      mmio_e1000[E1000_RDBAL] = PADDR(rx_desc_arr);
      mmio_e1000[E1000_RDBAH] = 0;
      mmio_e1000[E1000_RDLEN] = VALUEMASK(RXRING_LEN, E1000_RDLEN_LEN);
      mmio_e1000[E1000_RDH] = 0;
      mmio_e1000[E1000_RDT] = RXRING_LEN;
      mmio_e1000[E1000_RCTL] = E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_SECRC;
    
      return 0;
    }

    运行 make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput 得到如下结果:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    e1000: tx disabled
    e1000: RCTL: 128, mac_reg[RCTL] = 0x4008002
    [00000000] new env 00001000
    [00000000] new env 00001001
    FS is running
    FS can do I/O
    Device 1 presence: 1
    [00001001] new env 00001002
    block cache is good
    [00001001] new env 00001003
    superblock is good
    bitmap is good
    Sending ARP announcement...
    e1000: index 0: 0x2e5000 : 100002a 0
    e1000: unicast match[0]: 52:54:00:12:34:56
    [00001003] exiting gracefully
  • Exercise 11

    完成 E1000 接收数据包并添加对应的系统调用。

    由于文档说明 RDH 寄存器不可以信赖,所以需要自身维护接收队列的指针状态。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    int e1000_receive(void *addr, size_t *len) {
      static int32_t current = 0;
    
      if ((rx_desc_arr[current].status & E1000_RXD_STAT_DD) != E1000_RXD_STAT_DD) {
        return -E_RECEIVE_RETRY;
      }
    
      if (rx_desc_arr[current].errors) {
        return -E_RECEIVE_RETRY;
      }
    
      *len = rx_desc_arr[current].length;
      memcpy(addr, &rx_buffer_arr[current], *len);
    
      mmio_e1000[E1000_RDT] = (mmio_e1000[E1000_RDT] + 1) % RXRING_LEN;
      current = (current + 1) % RXRING_LEN;
    
      return 0;
    }

Receiving Packets: Network Server

网络服务输入进程中,通过接收系统调用接收数据包并用 NSREQ_INPUT IPC 消息传递到核心进程中。

  • Exercise 12

     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
    
    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", (int)now);
    
      while (sys_time_msec() < end)
        sys_yield();
    }
    
    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.
    
      size_t len;
      char buf[RX_PACKET_SIZE];
    
      while (1) {
        while (sys_packet_recv(buf, &len) < 0) {
          sys_yield();
        }
    
        memcpy(nsipcbuf.pkt.jp_data, buf, len);
        nsipcbuf.pkt.jp_len = len;
    
        ipc_send(ns_envid, NSREQ_INPUT, &nsipcbuf, PTE_P | PTE_U | PTE_W);
    
        // 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.
        sleep(50);
      }
    }

    运行 make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput 有如下结果:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    e1000: tx disabled
    e1000: RCTL: 127, mac_reg[RCTL] = 0x4008002
    [00000000] new env 00001000
    [00000000] new env 00001001
    [00001001] new env 00001002
    FS is running
    FS can do I/O
    Device 1 presence: 1
    [00001001] new env 00001003
    Sending ARP announcement...
    e1000: index 0: 0x2f8020 : 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
    
    Waiting for packets...
  • Q2

    How did you structure your receive implementation? In particular, what do you do if the receive queue is empty and a user environment requests the next incoming packet?

    结构体根据手册定义;如果队列已经满了让进程在下一个时间片周期再检测数据包

The Web Server

  • Exercise 13

    send_file()

     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
    
    static int
    send_file(struct http_request *req)
    {
      int r;
      off_t file_size = -1;
      int fd;
      struct Stat stat;
    
      // 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
    
      // LAB 6: Your code here.
      // if the file does not exist, send a 404 error using send_error
      if ((fd = open(req->url, O_RDONLY)) < 0) {
        send_error(req, 404);
        r = fd;
        goto end;
      }
    
      if ((r = fstat(fd, &stat)) < 0) {
        goto end;
      }
    
      // if the file is a directory, send a 404 error using send_error
      if (stat.st_isdir) {
        send_error(req, 404);
        r = -1;
        goto end;
      }
    
      // set file_size to the size of the file
      file_size = stat.st_size;
    
      // ...
    }

    send_data()

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    static int
    send_data(struct http_request *req, int fd)
    {
      // LAB 6: Your code here.
      struct Stat stat;
      if (fstat(fd, &stat) < 0) {
        return -1;
      }
    
      // read from file
      void *buf = malloc(stat.st_size);
      if (readn(fd, buf, stat.st_size) != stat.st_size) {
        panic("send_data(): readn(): Read requested file ERROR.");
      }
    
      // write to socket
      if (write(req->sock, buf, stat.st_size) != stat.st_size) {
        panic("send_data(): write(): Send bytes to client ERROR.");
      }
    
      free(buf);
      return 0;
    }

    运行 make run_httpd_nox 并打开浏览器看到页面

    Figure 10: run_httpd_nox 页面

    Figure 10: run_httpd_nox 页面

  • Q3

    What does the web page served by JOS’s web server say?

    Title: jhttpd on JOS body Body: This file came from JOS Cheesy web page!