DPDK收发包梳理

news/2025/2/23 21:10:27/文章来源:https://www.cnblogs.com/yghr/p/18732861

DPDK

  • eal初始化
  • 内存管理:大页,内存池
  • 驱动

开启调试信息
make config T=x86_64-native-linuxapp-gcc
export EXTRA_CFLAGS='-O0 -g3 -ggdb'
make -j8

dpdk通过makefile编译
meson + ninja没学过,太麻烦了,可以参考dpdk17的文档,里面有介绍make编译方式。
https://doc.dpdk.org/guides-17.11/index.html

x86_64-native-linuxapp-gcc的含义:ARCH-MACHINE-EXECENV-TOOLCHAIN

  • ARCH:i686, x86_64, ppc_64, arm64
  • MACHINE:native, power8, armv8a
  • EXECENV:linuxapp, bsdapp
  • TOOLCHAIN:gcc, icc

配置
make config -T=x86_64-native-linuxapp-gcc

安装
make install -T=x86_64-native-linuxapp-gcc

注意:构建的igb_uio和kni必须与运行dpdk的内核版本一致,如果编译的内核版本和运行dpdk的内核版本不一致,可以通过RTE_KERNELDIR环境变量指定具体内核版本的代码的目录

修改对应的dpdk配置
cd x86_64-native-linuxapp-gcc
vi .config
make

外部应用使用makefile添加dpdk依赖,必须指定以下2个环境变量
RTE_SDK:指向dpdk目录
RTE_TARGET:用于编译的目标,例如:x86_64-native-linuxapp-gcc
参考:

export RTE_SDK=/path/to/DPDK
export RTE_TARGET=x86_64-native-linuxapp-icc
make -f /path/to/my_app/Makefile S=/path/to/my_app O=/path/to/build_dir
S=是指定源码位置
O=是输出位置

  • ${RTE_SDK}/mk/rte.extapp.mk:构建二进制可执行文件
  • ${RTE_SDK}/mk/rte.extlib.mk:构建静态库(.a)
  • ${RTE_SDK}/mk/rte.extobj.mk:构建objects(.o)

run-to-complete模式:特定端口的RX描述符环通过API进行了针对数据包进行轮询。然后将数据包在同一核心上处理,并通过API放置在端口的TX描述符环上以进行传输。
pipeline模式:一个核心通过API进行了一个或多个端口的RX描述符环。收到数据包并通过ring传递给另一个核心。另一个核心继续处理数据包,然后可以通过API将其放置在端口的TX描述符ring上。
Receive Side Scaling (RSS) :接收侧缩放(RSS)
Data Center Bridging (DCB):数据中心桥接(DCB)

cpu初始化
根据/sys/devices/system/cpu/cpu%u/topology/core_id来确认cpu%u对应的coreid,注意coreid != cpuid,同时也根据此文件判断是否存在此cpu(是否启用)。
根据/sys/devices/system/node/node%u/cpu%u文件是否存在来确认cpuid对应cpu所在的numa节点。(RTE_MAX_NUMA_NODES和RTE_MAX_LCORE是编译时就配置好的在.config文件中)
初始化lcore_config的cpuid、对应的socket id等,以及设置了rte_config中numa_nodes映射(numa到socket id的映射)、cpu数量、numa节点数量。

日志输出
全局变量rte_logs,记录当前输出的日志文件,默认rte_logs.file为null,如果default_log_stream不为null会取此值(通过为null,通过eal_log_set_default方法设置),如果default_log_stream也为null则返回stderr。
如果当前记录的level大于rte_logs.level会忽略此条日志,如果logtype(例如EAL)无效,大于等于rte_logs.dynamic_types_len的值也会忽略此条日志,如果level大于rte_logs.dynamic_types[logtype].loglevel对应的日志级别也会忽略此条日志。
最后记录前把level和logtype写到per_lcore_log_cur_msg线程变量中。
然后写到对应文件,然后fflush一下返回。

dpdk的日志级别,总共8个,默认为RTE_LOG_DEBUG(值为8,看rte_log_init方法),还有固定32个动态日志类型,默认日志级别为RTE_LOG_INFO(值为7)。

解析参数

解析完参数后,如果internal_config.no_shconf为0,则创建dpdk运行时目录/var/run/dpdk/rte。
如果没有指定lcore参数,则会通过pthread_getaffinity_np获取当前线程的cpu亲和的cpu掩码,并减少rte_config的lcore_count的值(减少dpdk可用cpu数量)。
如果master的lcore没有指定,则设置rte_config->master_lcore为第1个可用lcore(一般是0)。
设置ctrl线程的亲和性,默认是取除了可用lcore之外的cpu,如果全部lcore都使用了(就是role为ROLE_RTE,不指定lcore就是全部lcore的role都为ROLE_RTE),就使用master lcore同一个。
把参数中设置的socket_mem累加到internal_config->memory中。
检查配置是否合法,并将dpdk的参数去掉,后面会返回dpdk参数的偏移值给调用方。

初始化插件
不知道干嘛的,忽略

解析设备参数
不知道干嘛的,忽略

rte_config初始化
PRIMARY进程:

  • 创建配置文件:/var/run/dpdk/rte/config,fd设置到mem_cfg_fd,设置文件大小为配置文件结构体大小(等于sizeof(rte_config.mem_config)),加文件锁(fcntl(mem_cfg_fd, F_SETLK, &wr_lock))。
  • 映射配置文件:如果没有设置base_virtaddr,默认为0x100000000ULL(linux环境,查看eal_get_baseaddr、eal_get_virtual_area方法),通过尝试mmap获取一块可用的地址(最多尝试5次,每次在原来的基础上加pagesize大小),然后madvise设置对应内存地址DONT_DUMP(不进行coredump,pmap可以查看标志位为dd),然后再重新映射1遍为/var/run/dpdk/rte/config文件。(mapped_mem_cfg_addr = mmap(rte_mem_cfg_addr, cfg_len_aligned, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mem_cfg_fd, 0)😉,接着设置rte_config.mem_config = rte_mem_cfg_addr,rte_config.mem_config->mem_cfg_addr = rte_mem_cfg_addr(secondary进程使用),接着设置RTE_VERSION到mfcg(rte_config.mem_config->version)中。


中断初始化
rte_eal_intr_init方法,创建eal-intr-thread线程,线程id:intr_thread,线程运行方法为eal_intr_thread_main。初始化intr_pipe管道fd,用于中断线程通信,初始化intr_sources链表。

告警初始化
intr_handle.type = RTE_INTR_HANDLE_ALARM;
/* create a timerfd file descriptor */
intr_handle.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);

多进程socket初始化
rte_mp_channel_init方法,对/var/run/dpdk/rte文件夹加文件锁(flock(dir_fd, LOCK_EX))创建/var/run/dpdk/rte/mp_socket文件为unix套接字mp_fd,创建rte_mp_handle线程,线程id:mp_handle_tid,线程运行方法:mp_handle,创建完线程,解开文件锁(flock(dir_fd, LOCK_UN)),关闭/var/run/dpdk/rte文件夹fd。

热拔插初始化
(不重要,忽略)
注册了mp方法:eal_dev_mp_request,对应方法:handle_secondary_request

总线扫描
重点看rte_pci_bus的scan方法:rte_pci_scan。

rte_pci_scan

  • 检查是否pci设备?internal_config.no_pci为false就是有,没有的话直接返回。
  • 检查是否有vfio是否启用?就是看/sys/module/vfio_pci模块是否加载。
  • 打开/sys/bus/pci/devices文件夹,遍历对应下面的文件,解析pci地址(格式:domain🚌devid.func),然后调用pci_scan_one继续解析子文件夹(例如:/sys/bus/pci/devices/0000:52:00.1)

pci_scan_one

  • malloc分配内存struct rte_pci_device *dev,把dev->device.bus = &rte_pci_bus.bus、dev->addr = *addr设置好,dev设置对应总线和pci地址。
  • 分别读取/sys/bus/pci/devices/${pci_id}目录下的文件vendor_id、device、subsystem_vendor、subsystem_device、class的值,设置到dev->id.vendor_id、dev->id.device_id、dev->id.subsystem_vendor_id、dev->id.subsystem_device_id、dev->id.class_id。
  • 如果存在/sys/bus/pci/devices/\({pci_id}/max_vfs文件读取值,设置到dev->max_vfs;否则如果存在/sys/bus/pci/devices/\){pci_id}/sriov_numvfs文件则读取值,设置到dev->max_vfs;都没有则dev->max_vfs为0。
  • 如果存在/sys/bus/pci/devices/${pci_id}/numa_node文件读取值,设置到dev->device.numa_node,否则设置为0。
  • 把pci id设置为dev->name的名称,例如:0000:52:00.1。
  • 然后根据名称查看是否对应的设备参数,有就设置到dev->device.devargs,如果dev->device.devargs != NULL,就设置dev->device.name = dev->device.devargs->name,否则dev->device.name = dev->name。
  • 打开/sys/bus/pci/devices/${pci_id}/resource文件,遍历文件每一行(格式:"起始地址 结束地址 标志位"),如果标志位 & 0x200就表示为内存bar,设置到dev->mem_resource[i].phys_addr(起始地址)、dev->mem_resource[i].len(起始地址和结束地址做差)、dev->mem_resource[i].addr为null。
  • 打开/sys/bus/pci/devices/${pci_id}/driver文件,没有就设置dev->kdrv = RTE_KDRV_NONE,否则读取对应文件的软链路径的最后1个文件名,vfio-pci设置dev->kdrv = RTE_KDRV_VFIO、igb_uio设置dev->kdrv = RTE_KDRV_IGB_UIO、uio_pci_generic设置dev->kdrv = RTE_KDRV_UIO_GENERIC、都不匹配设置dev->kdrv = RTE_KDRV_UNKNOWN。
  • 把设置加到rte_pci_bus.device_list链表中。

判断物理地址是否能获取到?
通过读取/proc/self/pagemap对应值进行判断(参考rte_mem_virt2phy方法),且大页可用(internal_config.no_hugetlbfs为false)。

iommu模式确认
如果没有设置参数--iova-mode=<pa|va>,那么默认为RTE_IOVA_DC(调用rte_bus_get_iommu_class确认)

rte_pci_bus对应方法为rte_pci_get_iommu_class。
遍历设备列表rte_pci_bus.device_list,针对第一个设备调用pci_device_iommu_support_va方法判断设备是否支持虚拟地址映射。如果不支持设置iommu_no_va为0,否则设置iommu_no_va为1。接着判断是否设备被过滤(黑名单、白名单、dev->kdrv的值是否为RTE_KDRV_UNKNOWN或者RTE_KDRV_NONE),没忽略,则遍历rte_pci_bus.driver_list判断是否设备对应驱动是否匹配(参考rte_pci_match方法),接着调用pci_device_iova_mode方法获取iova的值。如果存在VA就返回RTE_IOVA_VA,存在PA就返回RTE_IOVA_PA,否则打印告警日志返回RTE_IOVA_DC。对于总线也是优先VA、然后VA、最后DC。

设置rte_config.iova_mode。
如果rte_config.iova_mode为RTE_IOVA_PA,且无法获取物理地址就返回-1。

pci_device_iommu_support_va方法
读取文件/sys/bus/pci/devices/${pci_id}/iommu/intel-iommu/cap的值。
先忽略,不重要

rte_pci_match方法
把驱动pci_drv->id_table(struct rte_pci_id *数组)跟pci_dev->id(struct rte_pci_id)进行匹配。

pci_device_iova_mode方法
pdev->kdrv为RTE_KDRV_IGB_UIO、RTE_KDRV_UIO_GENERIC则是RTE_IOVA_PA,为RTE_KDRV_VFIO则进一步判断(忽略),其他的则判断是否pdrv->drv_flags & RTE_PCI_DRV_NEED_IOVA_AS_VA != 0,为真就是RTE_IOVA_VA,否则为RTE_IOVA_DC。

大页初始化
先忽略,明天看看,中优先级‼️

内存初始化
先忽略,明天看看,中优先级‼️

总线探测
rte_bus_probe方法,遍历rte_bus_list链表,对每个bus调用对应的probe方法。

rte_pci_bus对应的probe方法为pci_probe。

pci_probe方法。
遍历rte_pci_bus.device_list链表,然后对每个pci设备都调用一次pci_probe_all_drivers。

pci_probe_all_drivers方法。
遍历rte_pci_bus.driver_list链表,然后对每个驱动都调用rte_pci_probe_one_driver方法,传入设备,判断是否探测成功,成功探测则返回0,1没探测到,<0就是探测失败。

rte_pci_probe_one_driver方法。
调用rte_pci_match方法,匹配驱动的drv->id_tables和设备的pci地址:dev->addr,匹配失败返回1。如果设备pci地址被黑名单匹配到也表示匹配失败返回1。判断设备是否匹配过dev->drvier不为null,已经探测过且启动不支持再次探测,就返回-EEXIST(探测失败)。

  • 没探测过,调用pci_device_iova_mode方法判断使用什么地址模式?igb_uio和uio是pa,vfio没有iommu就是pa、有就是va。获取当前eal的iova模式,rte_config.iova_mode。如果不匹配则返回-EINVAL。否则匹配成功,设置dev->driver = dr。如果驱动存在(dr->drv_flags & RTE_PCI_DRV_NEED_MAPPING) != 0标志,就调用rte_pci_map_device方法映射,igb_uio和uio会调用pci_uio_map_resource方法进行映射,从/sys/bus/pci/devices/\({pci_id}/uio/uio%u路径中获取对应的uio number,读取/sys/bus/pci/devices/\){pci_id}/uio/uio%u/dev文件的major:minor,调用makedev(major, minor)方法和mknod("/dev/uio%u", S_IFCHR | S_IRUSR | S_IWUSR, dev)方法创建uio设备。接着打开uio文件设置dev->intr_handle.fd = open("/dev/uio%u", O_RDWR),和打开uio配置dev->intr_handle.uio_cfg_fd = open("/sys/class/uio/uio%u/device/config", O_RDWR),如果内核驱动dev->kdrv为RTE_KDRV_IGB_UIO,设置dev->intr_handle.type = RTE_INTR_HANDLE_UIO,否则dev->intr_handle.type = RTE_INTR_HANDLE_UIO_INTX并调用写入dev->intr_handle.uio_cfg_fd文件16字节设置1个标志位PCI_COMMAND_MASTER。接着调用uio_res = rte_zmalloc("UIO_RES", sizeof(struct mapped_pci_resource), 0),设置(uio_res)->path为/dev/uio%u,(*uio_res)->pci_addr为dev->addr。接着根据从/sys/bus/pci/devices/\({pci_id}/resource文件读取到的内存bar,也就是dev->mem_resource[i].phys_addr != 0的bar调用pci_uio_map_resource_by_index方法进行处理,打开/sys/bus/pci/devices/\){pci_id}/resource%d文件,然后就通过mmap进行映射(mmap(requested_addr, size, PROT_READ | PROT_WRITE, MAP_SHARED | additional_flags, fd, offset)),并且将映射后的地址保存到dev->mem_resource[res_idx].addr = mapaddr中。最后把uio_res插入到全局链表uio_res_list中。接着调用驱动的dr->probe(dr, dev)方法,探测成功设置dev->device.driver = &dr->driver。

ixgbe驱动探测

eth_ixgbe_pci_probe方法

  • 调用rte_eth_dev_create方法,调用rte_eth_dev_allocate方法分配struct rte_eth_dev(抽象的以太网设备),调用rte_eth_dev_find_free_port方法分配port id(按调用顺序从0开始分,最大port数量为32,可以在.config文件中修改),按照port id对应获取struct rte_eth_dev rte_eth_devices[RTE_MAX_ETHPORTS]的设备(struct rte_eth_dev *eth_dev = &rte_eth_devices[port_id]),并把eth_dev->data = &rte_eth_dev_shared_data->data[port_id]。再把网卡驱动私有的数据分配ethdev->data->dev_private = rte_zmalloc_socket(name, priv_data_size, RTE_CACHE_LINE_SIZE, device->numa_node)。并把pci的设备挂到rte设备上ethdev->device = device。然后调用eth_ixgbe_dev_init方法进行初始化。

eth_ixgbe_dev_init方法

  • ixgbe_get_bus_info获取总线信息,看着也没获取啥。
  • ixgbe_swfw_lock_reset解锁pending信号量,不知道干啥的。
  • ixgbe_dcb_init,初始化dcb配置,不知道干啥的。
  • 不重要忽略
  • 中断处理方法初始化:rte_intr_callback_register(intr_handle, ixgbe_dev_interrupt_handler, eth_dev),用于链路状态更新,就是创建1个回调结构体加到intr_sources中

配置设备信息等
rte_eth_dev_configure方法,通过port_id也就是数组下标,从rte_eth_devices[port_id]数组获取对应rte_eth_device。
调ixgbe_dev_info_get方法获取对应配置信息。
调ixgbe_dev_configure方法进行配置。

对应发送和接收队列:dev->data->tx_queues数组、dev->data->rx_queues数组(数组是在rte_eth_dev_configure方法里创建的,但是在rte_eth_tx_queue_setup/rte_eth_rx_queue_setup方法中为队列分配内存进行dma映射操作)

ixgbe对应的发送队列是struct ixgbe_tx_queue,txq->tx_ring对应的元素是union ixgbe_adv_tx_desc,txq->sw_ring对应的元素是struct ixgbe_tx_entry。
ixgbe对应的接收队列是struct ixgbe_rx_queue,rxq->rx_ring对应的元素是union ixgbe_adv_rx_desc,rxq->sw_ring对应的元素是struct ixgbe_rx_entry。

rte_eth_tx_queue_setup方法
-> ixgbe_dev_tx_queue_setup
-> rte_eth_dma_zone_reserve从大页分配1块满足大小的连续内存(txq->tx_ring)

rte_eth_rx_queue_setup方法
-> ixgbe_dev_rx_queue_setup
-> rte_eth_dma_zone_reserve从大页分配1块满足大小的连续内存(rxq->rx_ring)

rte_eth_dev_start方法
-> ixgbe_dev_start
-> ixgbe_dev_tx_init:设置队列内存
-> ixgbe_dev_rx_init:设置队列内存
-> ixgbe_dev_rxtx_start
-> ixgbe_dev_tx_queue_start:写入控制寄存器启动队列
-> ixgbe_dev_rx_queue_start:写入控制寄存器启动队列
-> ixgbe_alloc_rx_queue_mbufs:预先填充好rxq->rx_ring
-> ixgbe_enable_rx_dma_generic:写寄存器开启接收

将分配好的大页内存的物理地址,写入网卡dma传输寄存器:内存物理地址、内存长度、头尾指针


rte_eth_rx_burst方法
-> ixgbe_recv_pkts_vec
-> _recv_raw_pkts_vec

rte_eth_tx_burst方法
-> ixgbe_xmit_pkts_vec
-> ixgbe_xmit_fixed_burst_vec

以上都是向量化的版本,可以看下不向量化的版本ixgbe_xmit_pkts、ixgbe_recv_pkts。


参考:https://blog.csdn.net/u014787815/article/details/109019273

预取是把数据从内存提前加载到L1、L2等cache中,提高缓存命中率,减少从内存中加载的时间,提高IPC(1个时钟周期内的指令数)。

问题:

  • 内存管理?内存池初始化
  • rx、tx释放阈值如何确认?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/888567.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

空气流量和空气压力参数解耦系统simulink建模与仿真

1.课题概述空气流量和空气压力参数解耦系统simulink建模与仿真,在许多系统中,空气流量(Q)和压力(P)之间存在耦合关系,这意味着改变一个参数会影响到另一个参数。通过解耦系统解决这种问题,从而提高系统的控制稳定性。2.系统仿真结果 (完整程序运行后无水印)3.核心程序…

【库】Coravel Cache缓存

Coravel 通过使高级应用程序功能(如任务/作业调度、排队、缓存、邮件(以及更多!))易于访问且易于使用,帮助开发人员快速启动并运行 .NET 应用程序。具有简单、富有表现力和直接的语法。Coravel非常简单,通过Rember来保存缓存数据,同时可以设定缓存的时长,然后通过Get来…

4.优化器 - 模型评估

优化器 - optimizer优化器就是在深度学习反向传播过程中,指引损失函数(目标函数)的各个参数往正确的方向更新合适的大小,使得更新后的各个参数损失函数(目标函数)值不断逼近全局最小优化器不计算梯度,他只是梯度的更新者,它决定了以什么样的形式更新参数如果损失函数是…

【软件开发】CMake学习笔记

【软件开发】CMake 学习笔记 CMake 是什么? 是构建系统(如 Visual Studio)的文件(如 .vcxproj .sln)的创建器,具体要生成的构建系统可以通过 CMakePresets 文件中的 generator 指定。 构建系统一般不是跨平台的,但 CMake 支持在不同的操作系统上生成不同的构建系统文件,…

Python糖尿病数据分析:深度学习、逻辑回归、K近邻、决策树、随机森林、支持向量机及模型优化训练评估选择

全文链接:https://tecdat.cn/?p=39864 原文出处:拓端数据部落公众号 分析师:Weilong Zhang 本研究旨在利用机器学习和深度学习模型对糖尿病数据进行分析和预测。通过对糖尿病数据集的读取、预处理、特征分析,运用多种机器学习算法如逻辑回归、K近邻、决策树、随机森林、支…

使用MyBatis框架时Mapper传参是否需要使用@Param注解

在使用MyBatis作为Java项目的ORM框架时,在Mapper接口中传递参数需要通过@Param注解指定参数名称,这样才能在Mapper接口对应的xml文件中引用到对应名称的参数。如果不在Mapper接口中明确使用@Param注解时将会报错:找不到指定名称的参数。 追根溯源,这要从MyBatis获取Mapper接…

关于在阿里云服务器上搭建简单的keepalived主备服务器时出现的问题

问题:在进行keepalived主备服务器配置时,仅配置了RID,状态,通讯端口,VRID,优先级,通告报文发送时间,密码认证部分,VIP。在启动服务时,发现两台设备均跳转状态为MASTER。原因:出现这问题的场景是在阿里VPS云服务器网络环境中,因为路由交换层禁用了ARP的广播限制,造…

子串分值

‌输入和初始化‌: 读取字符串 str,并从索引 1 开始存储(C++ 中字符串索引从 0 开始,但这里为了简化计算,从 1 开始)。 n 存储字符串的长度。 数组 l[i] 存储字符 str[i] 上一次出现的位置。 数组 r[i] 存储字符 str[i] 下一次出现的位置。 数组 p 用于临时存储每个字符最…

【专题】2024年新能源汽车市场年度竞争报告汇总PDF洞察(附原数据表)

原文链接: https://tecdat.cn/?p=39740 在当下快速变革的时代,新能源汽车市场正处于关键的发展十字路口。过去几年间,市场经历了一系列深刻的结构性调整,从市场份额的重新分配到消费者行为模式的显著转变,每一个变化都蕴含着巨大的市场信号。深入分析这些变化背后的数据逻…

pikachu靶场搭建教程

详细介绍了pikachu靶场的搭建,并且附有安装包需要的东西phpStudy: 链接: https://pan.baidu.com/s/1fJ-5TNtdDZGUf5FhTm245g 提取码:0278 pikachu-master: Github链接:Github 链接 链接: https://pan.baidu.com/s/1lDdlxNaa3YjhIEj-WWB3qw 提取码:0278打开 phpstudy ,…

2.17周报

一、本周内容总结本周主要进行了蓝桥和天梯的训练,训练了3场蓝桥、2场天梯,剩余时间的就是赛后补题 补题的过程也重新理清了很多知识,包括gcd和lcm的应用,多项式除法的过程等等 对于蓝桥和天梯的赛制,还重新背了下很多算法的板子,包括求最短路的多种方法,不同范围求组合…

来点树链剖分

树链剖分树链剖分学习笔记 引入 给你一棵树,先单点加,再路径求和,你觉得很简单,用树上差分解决了这个问题。 再给你一棵树,先路径加,再单点查询,你觉得很简单,用树上差分解决了这个问题。 又给你一棵树,上述操作都有,而且顺序不分先后,你发现树上差分不能解决这个问…