XV6學習(16)Lab net: Network stack

最後一個實驗了,代碼在Github上。

這一個實驗其實挺簡單的,就是要實現網卡的e1000_transmite1000_recv函數。不過看以前的實驗好像還要實現上層socket相關的代碼,今年就只有網卡驅動了。

雖然實驗文檔裏面給了一本400多頁的網卡文檔,但其實也不需要怎麼讀這本厚厚的文檔,實驗的hints裏面就講的挺清楚了。

實驗

首先是e1000_transmit函數,按照hints一步步來就行了,唯一一個要查文檔的就是cmd域,但其實這個域的宏定義裏面就只給了E1000_TXD_CMD_RE1000_TXD_CMD_EOP這兩個,也就是說我們只要關注這兩個就行了:

int
e1000_transmit(struct mbuf *m)
{
  acquire(&e1000_lock);

  uint32 idx = regs[E1000_TDT];
  struct tx_desc* desc = &tx_ring[idx];

  if((desc->status & E1000_TXD_STAT_DD) == 0){
    release(&e1000_lock);
    printf("buffer overflow\n");
    return -1;
  }

  if(tx_mbufs[idx])
    mbuffree(tx_mbufs[idx]);
  
  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;
  __sync_synchronize();
  release(&e1000_lock);
  return 0;
}

然後是e1000_recv函數,這裡注意一次中斷應該把所有到達的數據都處理掉,剩下的按hints裏面的來就行了:

static void
e1000_recv(void)
{
  int idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
  struct rx_desc* desc = &rx_ring[idx];

  while(desc->status & E1000_RXD_STAT_DD){
    acquire(&e1000_lock);

    struct mbuf *buf = rx_mbufs[idx];
    mbufput(buf, desc->length);
    
    rx_mbufs[idx] = mbufalloc(0);
    if (!rx_mbufs[idx])
      panic("mbuf alloc failed");
    desc->addr = (uint64) rx_mbufs[idx]->head;
    desc->status = 0;

    regs[E1000_RDT] = idx;
    __sync_synchronize();
    release(&e1000_lock);

    net_rx(buf);
    
    idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
    desc = &rx_ring[idx];
  }
}

總結

到這裡整個XV6的實驗就完成了,趕在了過年之前寫完了。不得不說國外的實驗設計的真的好,實驗的代碼量都不大,每個實驗就是幾個函數,實驗的難度也設置的很合適。十一個實驗做完,配套講義看完之後就把XV6內核的絕大部分內容都看完了,對於操作系統的核心部分也都通過實驗有了更加深入的了解。知道了線程和進程切換之間的區別以及上下文切換是如何進行的;從以前只直到頁表這個概念到現在知道了整個分頁機構是如何運行的,親手實現了基於分頁和缺頁異常的COW fork,mmap,lazy allocation等技術;知道了系統調用是如何實現的以及操作系統是如何與硬件配合來對系統調用和中斷陷阱進行處理。

總而言之,強烈推薦學完了操作系統系統通過這個課程的實驗來加深和鞏固理解,而不是只停留在課本的那些概念上,一點實際的內核代碼都沒有接觸過。XV6內核的實現非常精簡,但是所有核心功能都實現了,而且編譯速度快,也不需要很多的配置,相比於Linux內核更加適合初學者來學習,在講義中也介紹了很多相比於Linux的可以改進的地方。