DPDK 收发包处理流程(一)(网卡驱动注册)

本文解析DPDK-1.8.0版本中网卡驱动的注册过程与系统PCI设备扫描流程。通过GCC属性实现网卡驱动在main函数前注册,并介绍如何通过读取系统文件确定PCI设备的具体信息。

转自:http://blog.youkuaiyun.com/xy010902100449/article/details/47282895

本文基于DPDK-1.8.0分析。

网卡驱动模型一般包含三层,即,PCI总线设备、网卡设备以及网卡设备的私有数据结构,即将设备的共性一层层的抽象,PCI总线设备包含网卡设备,网卡设备又包含其私有数据结构。在DPDK中,首先会注册设备驱动,然后查找当前系统有哪些PCI设备,并通过PCI_ID为PCI设备找到对应的驱动,最后调用驱动初始化设备。

一、网卡驱动注册

以e1000网卡驱动为例说明。

在1.8.0版本中,网卡驱动的注册使用了一种奇技淫巧的方法,使用GCC attribute扩展属性的constructor属性,使得网卡驱动的注册在程序MAIN函数之前就执行了。

复制代码
static struct rte_driver pmd_igb_drv = {
        .type = PMD_PDEV,
        .init = rte_igb_pmd_init,
};

static struct rte_driver pmd_igbvf_drv = {
        .type = PMD_PDEV,
        .init = rte_igbvf_pmd_init,
};

PMD_REGISTER_DRIVER(pmd_igb_drv);
PMD_REGISTER_DRIVER(pmd_igbvf_drv);
复制代码

其中PMD_REGISTER_DRIVER()宏的定义如下:

#define PMD_REGISTER_DRIVER(d)\
void devinitfn_ ##d(void);\
void __attribute__((constructor, used)) devinitfn_ ##d(void)\
{\
        rte_eal_driver_register(&d);\
}

使用attribute的constructor属性,在MAIN函数执行前,就执行rte_eal_driver_register()函数,将pmd_igb_drv驱动挂到全局dev_driver_list链表上。

二、扫描当前系统有哪些PCI设备

调用rte_eal_init()--->rte_eal_pci_init()函数,查找当前系统中有哪些网卡,分别是什么类型,并将它们挂到全局链表pci_device_list上。

1、首先初始化全局链表pci_driver_list、pci_device_list。用于挂载PCI驱动及PCI设备。

2、pci_scan()通过读取/sys/bus/pci/devices/目录下的信息,扫描当前系统的PCI设备,并初始化,并按照PCI地址从大到小的顺序挂在到pci_debice_list上。

复制代码
int
rte_eal_pci_init(void)
{
        TAILQ_INIT(&pci_driver_list);
        TAILQ_INIT(&pci_device_list);
        pci_res_list = RTE_TAILQ_RESERVE_BY_IDX(RTE_TAILQ_PCI,
                        mapped_pci_res_list);

        /* for debug purposes, PCI can be disabled */
        if (internal_config.no_pci)
                return 0;

        if (pci_scan() < 0) {
                RTE_LOG(ERR, EAL, "%s(): Cannot scan PCI bus\n", __func__);
                return -1;
        }
#ifdef VFIO_PRESENT
        pci_vfio_enable();

        if (pci_vfio_is_enabled()) {

                /* if we are primary process, create a thread to communicate with
                 * secondary processes. the thread will use a socket to wait for
                 * requests from secondary process to send open file descriptors,
                 * because VFIO does not allow multiple open descriptors on a group or
                 * VFIO container.
                 */
                if (internal_config.process_type == RTE_PROC_PRIMARY &&
                                pci_vfio_mp_sync_setup() < 0)
                        return -1;
        }
#endif
        return 0;
}

pcai_scan()通过读取/sys/bus/pci/devices/目录下相关PCI设备的如下文件,获取对应的信息,初始化struct rte_pci_device数据结构,并将其按照PCI地址从大到小的顺序挂到pci_device_list链表上。

复制代码
struct rte_pci_device {
        TAILQ_ENTRY(rte_pci_device) next;       /**< Next probed PCI device. */
        struct rte_pci_addr addr;               /**< PCI location. */
        struct rte_pci_id id;                   /**< PCI ID. */
        struct rte_pci_resource mem_resource[PCI_MAX_RESOURCE];   /**< PCI Memory Resource */
        struct rte_intr_handle intr_handle;     /**< Interrupt handle */
        const struct rte_pci_driver *driver;    /**< Associated driver */
        uint16_t max_vfs;                       /**< sriov enable if not zero */
        int numa_node;                          /**< NUMA node connection */
        struct rte_devargs *devargs;            /**< Device user arguments */
};
复制代码
复制代码
root@Ubuntu:~# ls -ltr /sys/bus/pci/devices/0000\:00\:09.0/
total 0
-rw-r--r-- 1 root root   4096 Nov 15 12:18 uevent
lrwxrwxrwx 1 root root      0 Nov 15 12:18 subsystem -> ../../../bus/pci
-r--r--r-- 1 root root   4096 Nov 15 12:19 class
-r--r--r-- 1 root root   4096 Nov 15 12:19 vendor
-r--r--r-- 1 root root   4096 Nov 15 12:19 device
-rw-r--r-- 1 root root    256 Nov 15 12:19 config
-r--r--r-- 1 root root   4096 Nov 15 12:19 local_cpus
-r--r--r-- 1 root root   4096 Nov 15 12:19 irq
-r--r--r-- 1 root root   4096 Nov 15 12:20 resource
drwxr-xr-x 2 root root      0 Nov 15 12:20 power
-r--r--r-- 1 root root   4096 Nov 19 14:33 subsystem_vendor
-r--r--r-- 1 root root   4096 Nov 19 14:33 subsystem_device
-r--r--r-- 1 root root   4096 Nov 19 14:33 numa_node
-rw------- 1 root root      8 Nov 19 14:58 resource2
-rw------- 1 root root 131072 Nov 19 14:58 resource0
--w------- 1 root root   4096 Nov 19 14:58 reset
--w--w---- 1 root root   4096 Nov 19 14:58 rescan
--w--w---- 1 root root   4096 Nov 19 14:58 remove
-rw-r--r-- 1 root root   4096 Nov 19 14:58 msi_bus
-r--r--r-- 1 root root   4096 Nov 19 14:58 modalias
-r--r--r-- 1 root root   4096 Nov 19 14:58 local_cpulist
-rw------- 1 root root   4096 Nov 19 14:58 enable
-r--r--r-- 1 root root   4096 Nov 19 14:58 dma_mask_bits
-r--r--r-- 1 root root   4096 Nov 19 14:58 consistent_dma_mask_bits
-rw-r--r-- 1 root root   4096 Nov 19 14:58 broken_parity_status
drwxr-xr-x 3 root root      0 Nov 19 15:31 uio
lrwxrwxrwx 1 root root      0 Nov 19 15:31 driver -> ../../../bus/pci/drivers/igb_uio
-rw-r--r-- 1 root root   4096 Nov 19 15:32 max_vfs
复制代码

目录名:就是PCI设备的地址,记录在struct rte_pci_addr数据结构中。

vendor文件:获取PCI_ID.vendor_id。

device文件:获取PCI_ID.device_id。

subsystem_vendor文件:获取PCI_ID.subsystem_vendor_id。

subsystem_device文件:获取PCI_ID.subsystem_device_id。

numa_node文件:获取PCI设备属于哪个CPU socket。

resource文件:获取PCI设备的在地址总线上的物理地址,以及物理地址空间的大小,记录在struct rte_pci_resouce数据结构中。


<think>我们使用DPDK(Data Plane Development Kit)时,网卡DPDK接管后,应用程序将直接与网卡交互,绕过了内核网络栈。要获取网卡的状态,我们可以使用DPDK提供的API。 常见的网卡状态信息括:链路状态(是否连接)、速度、双工模式、接收/发送的数、错误数等。 以下是使用DPDK获取网卡状态的基本步骤: 1. 初始化DPDK环境(EAL)。 2. 获取网卡端口(port)的数量。 3. 遍历每个端口,获取其信息。 具体来说,我们可以使用以下DPDK函数: - `rte_eth_dev_count_avail()`: 获取可用的网卡端口数量。 - `rte_eth_link_get()`: 获取网卡的链路状态(需要先调用`rte_eth_link_get_nowait`或`rte_eth_link_get`,前者是非阻塞的,后者已废弃,建议使用非阻塞版本)。 - `rte_eth_dev_info_get()`: 获取网卡的详细信息,驱动名称、最大接收/发送队列数等。 - `rte_eth_stats_get()`: 获取网卡的统计信息,如接收/发送的数、错误数等。 注意:在获取链路状态时,DPDK 20.11版本开始,推荐使用`rte_eth_link_get_nowait`,而在之前版本可能使用`rte_eth_link_get`(该函数在20.11中标记为废弃,并在21.11中移除)。因此,根据你的DPDK版本选择适当的函数。 以下是个示例代码,展示如何获取网卡的状态(括链路状态、速度、双工模式以及统计信息):</think>### DPDK 接管网卡后获取网卡状态的方法 在 DPDK 中,网卡(称为 **port**)被用户态驱动接管后,可通过以下 API 获取状态信息: #### 核心 API 函数 1. **链路状态**:`rte_eth_link_get_nowait()` 2. **统计信息**:`rte_eth_stats_get()` 3. **设备信息**:`rte_eth_dev_info_get()` 4. **MAC 地址**:`rte_eth_macaddr_get()` --- ### 完整示例代码 ```c #include <rte_ethdev.h> #include <rte_ether.h> void print_port_status(uint16_t port_id) { // 1. 获取链路状态(连接状态/速度/双工模式) struct rte_eth_link link; rte_eth_link_get_nowait(port_id, &link); printf("Port %u Link Status: %s\n", port_id, link.link_status ? "UP" : "DOWN"); printf("Speed: %u Mbps, Duplex: %s\n\n", link.link_speed, link.link_duplex == ETH_LINK_FULL_DUPLEX ? "Full" : "Half"); // 2. 获取统计信息(收发计数) struct rte_eth_stats stats; if (rte_eth_stats_get(port_id, &stats) == 0) { printf("Statistics:\n"); printf(" RX packets: %lu\n", stats.ipackets); printf(" TX packets: %lu\n", stats.opackets); printf(" RX errors: %lu\n", stats.ierrors); printf(" TX errors: %lu\n\n", stats.oerrors); } // 3. 获取设备信息 struct rte_eth_dev_info dev_info; rte_eth_dev_info_get(port_id, &dev_info); printf("Driver: %s\n", dev_info.driver_name); printf("Max RX Queues: %u\n", dev_info.max_rx_queues); printf("Max TX Queues: %u\n\n", dev_info.max_tx_queues); // 4. 获取 MAC 地址 struct ether_addr mac_addr; rte_eth_macaddr_get(port_id, &mac_addr); char mac_str[18]; ether_format_addr(mac_str, sizeof(mac_str), &mac_addr); printf("MAC Address: %s\n", mac_str); } int main() { // DPDK 环境初始化(需提前完成) // ... // 遍历所有端口 RTE_ETH_FOREACH_DEV(port_id) { print_port_status(port_id); } return 0; } ``` --- ### 关键代码解释 1. **链路状态获取** ```c rte_eth_link_get_nowait(port_id, &link); ``` - 非阻塞方式获取物理连接状态 - `link_status`:1=已连接,0=断开 - `link_speed`:连接速度(单位 Mbps) - `link_duplex`:双工模式(`ETH_LINK_FULL_DUPLEX` 或 `ETH_LINK_HALF_DUPLEX`) 2. **统计信息获取** ```c rte_eth_stats_get(port_id, &stats); ``` - `ipackets/opackets`:成功收/发的数据数量 - `ierrors/oerrors`:收/发错误计数 - 其他统计:丢计数、字节计数等(详见 `struct rte_eth_stats`) 3. **设备信息获取** ```c rte_eth_dev_info_get(port_id, &dev_info); ``` - 获取驱动名称、支持的最大队列数 - 获取设备能力标志(如支持 RSS、校验和卸载等) 4. **MAC 地址获取** ```c rte_eth_macaddr_get(port_id, &mac_addr); ether_format_addr(...); // 格式化为可读字符串 ``` --- ### 注意事项 1. **初始化要求** 需先完成 DPDK EAL 初始化 (`rte_eal_init()`) 和端口配置 (`rte_eth_dev_configure()`)。 2. **实时性** - 统计信息是累加值,需定期获取并计算差值得到实时流量 - 链路状态更新需调用 `rte_eth_link_get_nowait()`(或阻塞版 `rte_eth_link_get()`) 3. **错误处理** 所有 API 返回 `0` 表示成功,负数需用 `rte_strerror()` 解析错误原因。 4. **多线程安全** 在数据面线程中调用时需考虑锁机制(如使用 per-port 锁)。 --- ### 高级状态监控 - **动态统计重置**: `rte_eth_stats_reset(port_id)` 清零统计计数器 - **扩展统计**: 使用 `rte_eth_xstats_get()` 获取驱动特定的扩展统计项 - **队列状态**: `rte_eth_rx_queue_count()` 获取指定 RX 队列的已接收
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值