ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

e1000收发包分析

e1000收发包分析 前言上次看了 lan9118 收发包过程PIO 模式现代网卡基本千兆cpu直接搬运性能不太行了所以需要DMA开始看带dma的pci千兆网卡了能用qemu跑起来的重点看dma搬运用e1000经过查询发现有个 MIT 的著名操作系统课程 6.828 / 6.S081 的 Lab 就是让学生为 E1000 编写驱动网上有大量可以参考的学习资源就用这个吧vx6 网络实验目标DMA (Direct Memory Access)和描述符环 (Descriptor Ring)vx6 网络实验概念6.828 操作系统课xv6 操作系统做什么在 xv6 教学操作系统上亲手为一个E1000 网卡实现设备驱动。需要完成的核心任务有两个实现发送 (e1000_transmit)实现接收 (e1000_recv) 函数过程https://pdos.csail.mit.edu/6.828/2020/labs/net.html网页是英文但已有不少优秀的前辈做过这个实验还翻译成了中文可以直接搜索参考按照这个网页的步骤里面提供了一些参考资料如vx6的手册e1000的手册可以参考其中的哪些章节我看了然后发现自己还是不会写接收和发送函数于是决定给自己降低难度查找做过这个实验的前辈的代码我的任务是理解代码环境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 这是编译好的二进制解压可以了。太早的版本只能下载源码编译编译尚未成功已放弃解压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 $已进入 qemu退出 qemuctrla 松开再按x发包测试在一个终端cd xv6-labs-2020目录运行make server命令这启动了一个服务器程序监听来自 xv6 的 UDP 数据包另一个终端运行make qemu命令来启动 xv6 操作系统进到$后执行nettests命令这会向 IP 地址为10.0.2.2的“主机”发送一个 UDP 数据包。在 QEMU 的虚拟网络里10.0.2.2这个 IP 地址被映射到了真实主机Host。因此xv6 协议栈会调用e1000_transmit()函数来发送这个数据包直接执行是不可能收到回复的因为现在还没有实现发送函数 e1000_transmit()也没有实现 接收函数过程分析相关概念在实际看代码之前先来看些基本概念DMADMA直接内存访问的核心原理CPU 不搬运数据而是将“内存缓冲区地址”和“描述符环”的物理地址告诉网卡网卡内置的 DMA 控制器会通过 PCIe 总线自行读写这些物理内存。mbuf 数据包仓库tx/rx ring 地址簿 描述符发送描述符e1000 手册 3.3.3 表 3.9vx6里的简化相比 linux 内核定义【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的代码里提供了初始化函数e1000_init()e1000_init 把tx_ring数组首元素的地址写入 TDBAL ​ 数组长度写入 TDLEN ​ TDH写0 ​ TDT写0这里我就不贴代码了网上能直接搜索到只记录过程。发包过程网卡硬件提供e1000手册3.4​ 发送描述符基地址寄存器 TDBAL​ 发送描述符长度寄存器 TDLEN​ 发送描述符头存器 TDH​ 发送描述符尾存器 TDT软件​ 定义 tx_ring[]​ e1000_init​ 把tx_ring数组首元素的地址写入 TDBAL​ 数组长度写入 TDLEN​ TDH写0​ TDT写0​ 发送函数​ 接收协议栈组装好的 mbuf​ 读TDT知道可用的描述位置即tx_ring数组下标发第1个包是0一次加1​ 用下标在tx_ring数组里取到 描述符 desc​ 检查 desc 里 DD位1表示desc对应的上一个数据包已处理完成完成​ 释放旧的 mbuf​ 用mbuf里的信息填充 desc 数据包的内存地址长度​ mbufer地址保存到 tx_mbufs数组用于下次使用前释放内存​ 更新 TDT硬件​ **网卡硬件内部的状态机FSM**轮询发现 TDT ! TDH​ 从当前TDH指向的位置读取描述符Descriptor的内容地址、长度、CMD 位。​ 解析出addr和length。​ 发起PCIe 读事务DMA 读去内存中读取实际的mbuf数据。​ 数据读回来放入网卡内部的 FIFO再通过网线发出去​回写描述符的status字段: 置位E1000_TXD_STAT_DDDescriptor Done位。收包过程网卡硬件提供​ RDH 硬件下一个要写的位置​接收尾指针寄存器RDT指向的是“驱动已经处理并回填好的最后一个描述符的索引”软件​ 定义 rx_ring[]​ 定义 rx_mbufs[]​ e1000_init​ 把rx_ring数组首元素的地址写入 RDBAL​ 数组长度写入 RDLEN​ RDH写0​ RDT写 RX_RING_SIZE - 1​ 接收函数e1000_intr e1000_recv e1000_recv while(1) 根据 RDT 得到 rx_index 用 rx_index 得到 rx_ring[rx_index] 描述符 判断描述符的 DD //网卡硬件在将数据包 DMA 搬运到内存完成后由网卡硬件自动置 1 的标志位 0 break 1 继续向下执行 用描述符-length 更新 rx_mbufs[rx_index]-len net_rx 把数据 rx_mbufs[rx_index] 给到协议栈 分配新 mbuf 更新 desc-addr 清除 desc-status 0 更新 RDT总结接收包完整过程e1000驱动初始化基址预填所有空仓库​ rx_ring数组首元素的地址写入 RDBAL​ 用mbufalloc()分配好所有空mbuf把它们的物理地址head一个一个填进rx_ring里每个描述符的addr字段。当数据包从网线进入网卡​ 网卡硬件DMA根据RDH找到用rx_ring[]里哪个描述符​ 从描述符里读出物理地址把数据搬运到对应物理地址​ 描述符DD写1中断通知cpucpu用注册好的中断函数处理e1000_intr调用接收函数接收函数​ 传递 **mbuf结构体的指针虚拟地址**给到了协议栈net.c而不是物理地址​ 分配新的mbuf​ 把新mbuf的物理地址填回同一个描述符。​ 清空status去掉DD​更新RDT协议栈​ 通过**mbuf结构体的指针虚拟地址**去读取mbuf-head里面存的才是物理地址但协议栈不需要知道因为 xv6 做了直接映射虚拟地址可以直接加减偏移得到物理地址参考vx6 网络实验 https://pdos.csail.mit.edu/6.828/2020/labs/net.html
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表