mit 6.828學習筆記不知道幾--lab6
Part A
exercise 1:
為kern/trap.c中的每個時鐘中斷新增對time_tick的呼叫。實現sys_time_msec並將其新增到kern/syscall.c中的syscall中,以便使用者空間能夠訪問時間。
在kern/trap.c中新增分支
注意不要傻fufu的重新新增一個分支,在lab4中我們已經添加了時鐘中斷,此處只要修改一下即可
else if(tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER){ lapic_eoi(); time_tick(); sched_yield();return; }
在kern/syscall.c中給syscall新增分支:
case(SYS_time_msec): return sys_time_msec();
實現sys_time_msec
在此之前,首先看一下kern/time.c中的幾個函式:
static unsigned int ticks; void time_init(void) { ticks = 0; } // This should be called once per timer interrupt. A timer interrupt // fires every 10 ms.void time_tick(void) { ticks++; if (ticks * 10 < ticks) panic("time_tick: time overflowed"); } unsigned int time_msec(void) { return ticks * 10; }
ticks在time.c進行了定義。
所以不能在sys_time_msec中直接ticks * 10,而是要呼叫time_msec(),否則會報錯ticks未定義。
// Return the current time. static int sys_time_msec(void) { // LAB 6: Your code here. return time_msec(); //panic("sys_time_msec not implemented"); }
鍵入make INIT_CFLAGS=-DTEST_NO_NS run-testtime
來測試你的程式碼. 你應當看到程序在1秒內從5開始倒數,“-DTEST_NO_NS”會禁用啟動網路伺服器程序,因為它會引起panic。
exercise 2:
閱讀文件
exercise 3:
首先看看kern/pci.c中的幾個函式:
// pci_attach_class matches the class and subclass of a PCI device struct pci_driver pci_attach_class[] = { { PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI, &pci_bridge_attach }, { 0, 0, 0 }, }; // 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
//第一引數應該是venderID,第二個引數是deviseID
struct pci_driver pci_attach_vendor[] = { { PCI_E1000_VENDOR, PCI_E1000_DEVICE, &pci_e1000_attach }, { 0, 0, 0 }, };
pci_attach_class和pci_attach_vendor2個數組就是裝置陣列
對於pci裝置而言
PCI是外圍裝置互連(Peripheral Component Interconnect)的簡稱,是在目前計算機系統中得到廣泛應用的通用匯流排介面標準:
--- 在一個PCI系統中,最多可以有256根PCI匯流排,一般主機上只會用到其中很少的幾條。
--- 在一根PCI總線上可以連線多個物理裝置,可以是一個網絡卡、顯示卡或者音效卡等,最多不超過32個。
--- 一個PCI物理裝置可以有多個功能,比如同時提供視訊解析和聲音解析,最多可提供8個功能。
--- 每個功能對應1個256位元組的PCI配置空間。
//負責設定需要讀寫的具體裝置
static void pci_conf1_set_addr(uint32_t bus, uint32_t dev, uint32_t func, uint32_t offset) { assert(bus < 256); //8位 最多可以有256根PCI匯流排,一般主機上只會用到其中很少的幾根 assert(dev < 32); //5位 一根PCI匯流排可以連線多個物理裝置,可以是一個網絡卡、顯示卡或音效卡等,最多不超過32個 assert(func < 8); //3位 一個PCI物理裝置可以有多個功能,比如同時提供視訊解析和聲音解析,最多可提供8個功能。 assert(offset < 256); //8位 每個功能對應1個256位元組的PCI配置空間。 assert((offset & 0x3) == 0);//最後兩位必須為00? uint32_t v = (1 << 31) | // config-space (bus << 16) | (dev << 11) | (func << 8) | (offset); outl(pci_conf1_addr_ioport, v); }
//讀取PCI配置空間中特定位置的配置值
static uint32_t pci_conf_read(struct pci_func *f, uint32_t off) { pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off); return inl(pci_conf1_data_ioport); } //設定PCI配置空間中特定位置的配置值 static void pci_conf_write(struct pci_func *f, uint32_t off, uint32_t v) { pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off); outl(pci_conf1_data_ioport, v); }
初始化PCI的大致流程:
在pci_init函式中,root_bus被全部清0,然後交給pci_scan_bus函式來掃描這條總線上的所有裝置,說明在JOS中E1000網絡卡是連線在0號總線上的。pci_scan_bus函式來順次查詢0號總線上的32個裝置,如果發現其存在,那麼順次掃描它們每個功能對應的配置地址空間,將一些關鍵的控制引數讀入到pci_func中進行儲存。得到pci_func函式後,被傳入pci_attach函式去查詢是否為已存在的裝置,並用相應的初始化函式來初始化裝置。
在手冊的5.2節中可以找到venderID和deviceID:
82540EM-A 8086h 100E Desktop(桌上型電腦)
在kern/ PCI.c中的pci_attach_vendor陣列中新增一個條目,以便在找到匹配的PCI裝置時觸發函式(請確保將它放在表示 末尾的{0,0,0}條目之前)
struct pci_driver pci_attach_vendor[] = { { PCI_VENDOR_ID, PCI_DEVICE_ID, &e1000_init }, { 0, 0, 0 }, };
前兩個引數PCI_VENDOR_ID, PCI_DEVICE_ID的定義放在kern/pcireg.h檔案中,這個檔案是用來存放PCI的配置資訊的
#define PCI_VENDOR_ID 0x8086 #define PCI_DEVICE_ID 0x100E
第三個引數應該是他初始化的時候應該呼叫的函式,可以定義在kern/e1000.c中
int e1000_init(struct pci_func *pcif) { pci_func_enable(pcif); return 1; }
然後在kern/e1000.h中宣告這個函式,讓pic.c進行呼叫
#include <kern/pci.h> int e1000_init(struct pci_func *pcif);
exercise 4 :
在kern/e1000.c中的函式新增:
int e1000_init(struct pci_func *pcif) { pci_func_enable(pcif); pci_e1000 = mmio_map_region(pcif->reg_base[0], pcif->reg_size[0]); return 1; }
我們使用變數pci_e1000來記錄對映的位置,以便稍後訪問剛才對映的暫存器。
如果要嘗試列印裝置狀態暫存器
在QEMU's e1000_hw.h 中有關於狀態暫存器的定義。
#define E1000_STATUS 0x00008 /* Device Status - RO */
在kern/e1000.h中新增
#define E1000_STATUS 0x00008 /* Device Status - RO */ #define e1000_print_status(offset) \ cprintf("the E1000 status register: [%08x]\n", *(pci_e1000+(offset>>2))); // 由於pci_e1000是uint32_t的,如果直接加offset,就相當於加了offset*sizeof(uint32_t) // 例如the E1000:[ef804000] offset:[00000008] sum:[ef804020]
// 並且定義 pci_e1000
uint32_t *pci_e1000;
修改
int e1000_init(struct pci_func *pcif) { pci_func_enable(pcif); pci_e1000 = mmio_map_region(pcif->reg_base[0], pcif->reg_size[0]); e1000_print_status(E1000_STATUS); return 1; }
exercise5:
在kern/e1000.c中:
int e1000_init(struct pci_func* pcif) { pci_func_enable(pcif); //pci_e1000 是1個指標,指向對映地址, uint32_t* pci_e1000; //他建立了一個虛擬記憶體對映 pci_e1000 = mmio_map_region(pcif->reg_base[0], pcif->reg_size[0]); e1000_print_status(E1000_STATUS); //清零兩個陣列 memset(tx_list, 0, sizeof(struct tx_desc) * TX_MAX); memset(tx_buf, 0, sizeof(struct packets) * TX_MAX); //每一個傳輸描述符都對應著一個packet for (int i = 0; i < TX_MAX; i++) { tx_list[i].addr = PADDR(tx_buf[i].buffer); //不太懂為什麼可以用PADDR //24=4(16進位制相對於2進位制)*6 tx_list[i].cmd = (E1000_TXD_CMD_RS >> 24) | (E1000_TXD_CMD_EOP >> 24); //因為RSV除了82544GC/EI之外,所有乙太網控制器都應該保留這個位,並將其程式設計為0b。 //而LC,EC在全雙工沒有意義, tx_list[i].status = E1000_TXD_STAT_DD; } //(TDBAL/TDBAH)指向傳輸描述符佇列的base和high //將pci_e1000視為陣列的話,他存放的元素應該是32位的, //乙太網控制器中的暫存器都是32位的, pci_e1000[E1000_TDBAL >> 2] = PADDR(tx_list); pci_e1000[E1000_TDBAH >> 2] = 0; pci_e1000[E1000_TDT >> 2] = 0; pci_e1000[E1000_TDLEN >> 2] = TX_MAX * sizeof(struct tx_desc); pci_e1000[E1000_TDH >> 2] = 0; //pci_e1000[E1000_TDT >> 2] = 0; pci_e1000[E1000_TCTL >> 2] |= (E1000_TCTL_EN | E1000_TCTL_PSP | (E1000_TCTL_CT & (0x10 << 4)) | (E1000_TCTL_COLD & (0x40 << 12))); pci_e1000[E1000_TIPG >> 2] |= (10) | (4 << 10) | (6 << 20); return 1; }
在kern/e1000.h中加入:
#include <inc/string.h> //以下定義來自 QEMU's e1000_hw.h #define E1000_TCTL 0x00400 /* TX Control - RW */ #define E1000_TDBAL 0x03800 /* TX Descriptor Base Address Low - RW */ #define E1000_TDBAH 0x03804 /* TX Descriptor Base Address High - RW */ #define E1000_TDLEN 0x03808 /* TX Descriptor Length - RW */ #define E1000_TDH 0x03810 /* TX Descriptor Head - RW */ #define E1000_TDT 0x03818 /* TX Descripotr Tail - RW */ #define E1000_TIPG 0x00410 /* TX Inter-packet gap -RW */ #define E1000_TCTL_EN 0x00000002 /* enable tx */ #define E1000_TCTL_BCE 0x00000004 /* busy check enable */ #define E1000_TCTL_PSP 0x00000008 /* pad short packets */ #define E1000_TCTL_CT 0x00000ff0 /* collision threshold */ #define E1000_TCTL_COLD 0x003ff000 /* collision distance */ #define E1000_TXD_CMD_RS 0x08000000 /* Report Status */ #define E1000_TXD_CMD_EOP 0x01000000 /* End of Packet */ #define E1000_TXD_STAT_DD 0x00000001 /* Descriptor Done */ #define TX_MAX 64 //legacy transmit descriptor format struct tx_desc { uint64_t addr; //Address of the transmit descriptor in the host memory uint16_t length; //The Checksum offset field indicates where to insert a TCP checksum if this mode is enabled. uint8_t cso; uint8_t cmd; uint8_t status; uint8_t css; //The Checksum start field (TDESC.CSS) indicates where to begin computing the checksum. uint16_t special; }__attribute__((packed));//告訴編譯器取消結構在編譯過程中的優化對齊,按照實際佔用位元組數進行對齊, //傳輸描述符陣列 struct tx_desc tx_list[TX_MAX]; //乙太網資料包的最大大小為1518位元組,將其限制在2048方便對齊 struct packets { char buffer[2048]; }__attribute__((packed)); //緩衝區陣列 struct packets tx_buf[TX_MAX];
執行make E1000_DEBUG=TXERR,TX qemu
在設定TDT register
時,應該會看到一條“e1000: tx disabled”訊息(因為這是在設定TCTL.EN之前發生的),並且不再有“e1000”訊息
exercise 6:
在kern/e1000.c中加入
int e1000_transmit(void* addr, int length) { //TDT是傳輸描述符陣列的索引 int tail = pci_e1000[E1000_TDT >> 2]; //得到下一個描述符 struct tx_desc* tx_next = &tx_list[tail]; //如果包長度超出了,就直接截住 if (length > sizeof(struct packets)) length = sizeof(struct packets); //如果DD位被設定了,就說明這個傳輸描述符安全回收,可以傳輸下一個包,也就是佇列還沒有滿 if ((tx_next->status & E1000_TXD_STAT_DD) == E1000_TXD_STAT_DD) { //沒有滿,把包複製到緩衝區裡面去 memmove(KADDR(tx_next->addr), addr, length); tx_next->status &= !E1000_TXD_STAT_DD; //表示現在該描述符還沒被處理 tx_next->length = (uint16_t)length; //更新TDT,注意是迴圈佇列 pci_e1000[E1000_TDT >> 2] = (tail + 1) % TX_MAX; cprintf("my message:%s, %d, %02x\n", tx_buf[tail].buffer, tx_list[tail].length, tx_list[tail].status); return 0; } //DD位沒有被設定,傳輸佇列滿了 return -1; }
在kern/e1000.h中宣告這個函式
在kern/monitor.c中新增kern/e1000.h標頭檔案
然後:
void monitor(struct Trapframe *tf) { char *buf; cprintf("Welcome to the JOS kernel monitor!\n"); cprintf("Type 'help' for a list of commands.\n"); e1000_transmit("I'm here", 10); if (tf != NULL) print_trapframe(tf); while (1) { buf = readline("K> "); if (buf != NULL) if (runcmd(buf, tf) < 0) break; } }
在qemu中測試:
make E1000_DEBUG=TXERR,TX qemu
能得到以下結果:
e1000: index 0: 0x2b02c0 : 900000a 0 my message:I'm here, 10, 00
在qemu中測試:
tcpdump -XXnr qemu.pcap
得到以下結果:
reading from file qemu.pcap, link-type EN10MB (Ethernet) 16:09:40.990305 [|ether] 0x0000: 4927 6d20 6865 7265 004b I'm.here.K
exercise 7:
在kern/syscall.c中新增系統呼叫函式
//network packet send static int sys_packet_try_send(void *addr, size_t len){ user_mem_assert(curenv, addr, len, PTE_U); //考慮沒這麼周全 return e1000_transmit(addr, len); }
在diapstach中新增分支:
case (SYS_packet_try_send): return sys_packet_try_send((void *)a1,a2);
新增宣告和定義:
//在kern/syscall.c中 #include<kern/e1000.h> //lib/syscall.c中 int sys_packet_try_send(void* buf, size_t len) { return syscall(SYS_packet_try_send, 0,(uint32_t)buf, len, 0,0,0); } //inc/syscall.h中要宣告 SYS_packet_try_send, //inc/lib.h中
intsys_packet_try_send(void *data_va,size_t len);
exercise 8:
# include <inc/lib.h> void output(envid_t ns_envid) { binaryname = "ns_output"; int r; int perm; // LAB 6: Your code here: // - read a packet from the network server // - send the packet to the device driver envid_t from_env; while(1){ if( ipc_recv(&from_env, &nsipcbuf, &perm) != NSREQ_OUTPUT) continue; while((r = sys_packet_try_send(nsipcbuf.pkt.jp_data, nsipcbuf.pkt.jp_len)<0)) sys_yield(); } }
執行 make grade 可以看到part a的測試全部通過