包分析)
前言上次看了 lan9118 收發(fā)包過程PIO 模式現(xiàn)代網(wǎng)卡基本千兆cpu直接搬運(yùn)性能不太行了所以需要DMA開始看帶dma的pci千兆網(wǎng)卡了能用qemu跑起來的重點(diǎn)看dma搬運(yùn)用e1000經(jīng)過查詢發(fā)現(xiàn)有個 MIT 的著名操作系統(tǒng)課程 6.828 / 6.S081 的 Lab 就是讓學(xué)生為 E1000 編寫驅(qū)動網(wǎng)上有大量可以參考的學(xué)習(xí)資源就用這個吧vx6 網(wǎng)絡(luò)實(shí)驗(yàn)?zāi)繕?biāo)DMA (Direct Memory Access)和描述符環(huán) (Descriptor Ring)vx6 網(wǎng)絡(luò)實(shí)驗(yàn)概念6.828 操作系統(tǒng)課xv6 操作系統(tǒng)做什么在 xv6 教學(xué)操作系統(tǒng)上親手為一個E1000 網(wǎng)卡實(shí)現(xiàn)設(shè)備驅(qū)動。需要完成的核心任務(wù)有兩個實(shí)現(xiàn)發(fā)送 (e1000_transmit)實(shí)現(xiàn)接收 (e1000_recv) 函數(shù)過程https://pdos.csail.mit.edu/6.828/2020/labs/net.html網(wǎng)頁是英文但已有不少優(yōu)秀的前輩做過這個實(shí)驗(yàn)還翻譯成了中文可以直接搜索參考按照這個網(wǎng)頁的步驟里面提供了一些參考資料如vx6的手冊e1000的手冊可以參考其中的哪些章節(jié)我看了然后發(fā)現(xiàn)自己還是不會寫接收和發(fā)送函數(shù)于是決定給自己降低難度查找做過這個實(shí)驗(yàn)的前輩的代碼我的任務(wù)是理解代碼環(huán)境VMwareUbuntu 20.04_64盡量用較新的版本不然risv gcc工具鏈不太好搞安裝庫sudo apt install git sudo apt install make sudo apt install gcc sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu sudo apt install vim下載 riscv64-elf-ubuntu-20.04-gcc-nightly-2023.09.27-nightly.tar.gz 這是編譯好的二進(jìn)制解壓可以了。太早的版本只能下載源碼編譯編譯尚未成功已放棄解壓bin路徑加入PATHtar -zxvf riscv64-elf-ubuntu-20.04-gcc-nightly-2023.09.27-nightly.tar.gz export PATH/....../riscv/bin:$PATH下載 vx6源碼切到 net 分支git clone git://g.csail.mit.edu/xv6-labs-2020 git checkout net開始編譯make clean make qemu然后就報錯了user/sh.c:58:1: error: infinite recursion detected [-Werrorinfinite-recursion] 58 | runcmd(struct cmd *cmd) | ^~~~~~修改vim Makefile CFLAGS ... -Wno-infinite-recursion再次 make qumu可以啦xv6 kernel is booting hart 2 starting hart 1 starting init: starting sh $已進(jìn)入 qemu退出 qemuctrla 松開再按x發(fā)包測試在一個終端cd xv6-labs-2020目錄運(yùn)行make server命令這啟動了一個服務(wù)器程序監(jiān)聽來自 xv6 的 UDP 數(shù)據(jù)包另一個終端運(yùn)行make qemu命令來啟動 xv6 操作系統(tǒng)進(jìn)到$后執(zhí)行nettests命令這會向 IP 地址為10.0.2.2的“主機(jī)”發(fā)送一個 UDP 數(shù)據(jù)包。在 QEMU 的虛擬網(wǎng)絡(luò)里10.0.2.2這個 IP 地址被映射到了真實(shí)主機(jī)Host。因此xv6 協(xié)議棧會調(diào)用e1000_transmit()函數(shù)來發(fā)送這個數(shù)據(jù)包直接執(zhí)行是不可能收到回復(fù)的因?yàn)楝F(xiàn)在還沒有實(shí)現(xiàn)發(fā)送函數(shù) e1000_transmit()也沒有實(shí)現(xiàn) 接收函數(shù)過程分析相關(guān)概念在實(shí)際看代碼之前先來看些基本概念DMADMA直接內(nèi)存訪問的核心原理CPU 不搬運(yùn)數(shù)據(jù)而是將“內(nèi)存緩沖區(qū)地址”和“描述符環(huán)”的物理地址告訴網(wǎng)卡網(wǎng)卡內(nèi)置的 DMA 控制器會通過 PCIe 總線自行讀寫這些物理內(nèi)存。mbuf 數(shù)據(jù)包倉庫tx/rx ring 地址簿 描述符發(fā)送描述符e1000 手冊 3.3.3 表 3.9vx6里的簡化相比 linux 內(nèi)核定義【kernel/e1000_dev.h】 struct tx_desc { uint64 addr; uint16 length; uint8 cso; uint8 cmd; uint8 status; uint8 css; uint16 special; } // [E1000 3.2.3] struct rx_desc { uint64 addr; /* Address of the descriptors data buffer */ uint16 length; /* Length of data DMAed into data buffer */ uint16 csum; /* Packet checksum */ uint8 status; /* Descriptor status */ uint8 errors; /* Descriptor Errors */ uint16 special; };mbuf【kernel/net.h】 struct mbuf { struct mbuf *next; // the next mbuf in the chain char *head; // the current start position of the buffer unsigned int len; // the length of the buffer char buf[MBUF_SIZE]; // the backing store };初始化vx6的代碼里提供了初始化函數(shù)e1000_init()e1000_init 把tx_ring數(shù)組首元素的地址寫入 TDBAL ? 數(shù)組長度寫入 TDLEN ? TDH寫0 ? TDT寫0這里我就不貼代碼了網(wǎng)上能直接搜索到只記錄過程。發(fā)包過程網(wǎng)卡硬件提供e1000手冊3.4? 發(fā)送描述符基地址寄存器 TDBAL? 發(fā)送描述符長度寄存器 TDLEN? 發(fā)送描述符頭存器 TDH? 發(fā)送描述符尾存器 TDT軟件? 定義 tx_ring[]? e1000_init? 把tx_ring數(shù)組首元素的地址寫入 TDBAL? 數(shù)組長度寫入 TDLEN? TDH寫0? TDT寫0? 發(fā)送函數(shù)? 接收協(xié)議棧組裝好的 mbuf? 讀TDT知道可用的描述位置即tx_ring數(shù)組下標(biāo)發(fā)第1個包是0一次加1? 用下標(biāo)在tx_ring數(shù)組里取到 描述符 desc? 檢查 desc 里 DD位1表示desc對應(yīng)的上一個數(shù)據(jù)包已處理完成完成? 釋放舊的 mbuf? 用mbuf里的信息填充 desc 數(shù)據(jù)包的內(nèi)存地址長度? mbufer地址保存到 tx_mbufs數(shù)組用于下次使用前釋放內(nèi)存? 更新 TDT硬件? **網(wǎng)卡硬件內(nèi)部的狀態(tài)機(jī)FSM**輪詢發(fā)現(xiàn) TDT ! TDH? 從當(dāng)前TDH指向的位置讀取描述符Descriptor的內(nèi)容地址、長度、CMD 位。? 解析出addr和length。? 發(fā)起PCIe 讀事務(wù)DMA 讀去內(nèi)存中讀取實(shí)際的mbuf數(shù)據(jù)。? 數(shù)據(jù)讀回來放入網(wǎng)卡內(nèi)部的 FIFO再通過網(wǎng)線發(fā)出去?回寫描述符的status字段: 置位E1000_TXD_STAT_DDDescriptor Done位。收包過程網(wǎng)卡硬件提供? RDH 硬件下一個要寫的位置?接收尾指針寄存器RDT指向的是“驅(qū)動已經(jīng)處理并回填好的最后一個描述符的索引”軟件? 定義 rx_ring[]? 定義 rx_mbufs[]? e1000_init? 把rx_ring數(shù)組首元素的地址寫入 RDBAL? 數(shù)組長度寫入 RDLEN? RDH寫0? RDT寫 RX_RING_SIZE - 1? 接收函數(shù)e1000_intr e1000_recv e1000_recv while(1) 根據(jù) RDT 得到 rx_index 用 rx_index 得到 rx_ring[rx_index] 描述符 判斷描述符的 DD //網(wǎng)卡硬件在將數(shù)據(jù)包 DMA 搬運(yùn)到內(nèi)存完成后由網(wǎng)卡硬件自動置 1 的標(biāo)志位 0 break 1 繼續(xù)向下執(zhí)行 用描述符-length 更新 rx_mbufs[rx_index]-len net_rx 把數(shù)據(jù) rx_mbufs[rx_index] 給到協(xié)議棧 分配新 mbuf 更新 desc-addr 清除 desc-status 0 更新 RDT總結(jié)接收包完整過程e1000驅(qū)動初始化基址預(yù)填所有空倉庫? rx_ring數(shù)組首元素的地址寫入 RDBAL? 用mbufalloc()分配好所有空mbuf把它們的物理地址head一個一個填進(jìn)rx_ring里每個描述符的addr字段。當(dāng)數(shù)據(jù)包從網(wǎng)線進(jìn)入網(wǎng)卡? 網(wǎng)卡硬件DMA根據(jù)RDH找到用rx_ring[]里哪個描述符? 從描述符里讀出物理地址把數(shù)據(jù)搬運(yùn)到對應(yīng)物理地址? 描述符DD寫1中斷通知cpucpu用注冊好的中斷函數(shù)處理e1000_intr調(diào)用接收函數(shù)接收函數(shù)? 傳遞 **mbuf結(jié)構(gòu)體的指針虛擬地址**給到了協(xié)議棧net.c而不是物理地址? 分配新的mbuf? 把新mbuf的物理地址填回同一個描述符。? 清空status去掉DD?更新RDT協(xié)議棧? 通過**mbuf結(jié)構(gòu)體的指針虛擬地址**去讀取mbuf-head里面存的才是物理地址但協(xié)議棧不需要知道因?yàn)?xv6 做了直接映射虛擬地址可以直接加減偏移得到物理地址參考vx6 網(wǎng)絡(luò)實(shí)驗(yàn) https://pdos.csail.mit.edu/6.828/2020/labs/net.html