DPDK默认的轮询模式在没有接收到报文的空载状态下也会占用100%的CPU,非常浪费电力。通过在低速率和空载状态下启用中断收包,在高速率下使用轮询收包便能兼顾性能与节能的目标。
以下代码基于DPDK 21.11.3的示例代码,examples/l3fwd-power/main.c,该程序提供了多种CPU、网卡节能的工作模式,以及动态CPU频率调节等节能手段。这里仅关注其中处理中断的部分(指定--legacy运行的模式),并将代码简化为网卡上一个端口一个接收队列的处理。
#define SUSPEND_THRESHOLD 300
static inline void turn_on_off_intr(uint16_t port_id, uint8_t queue_id, bool on) {
if(on)
rte_eth_dev_rx_intr_enable(port_id, queue_id);
else
rte_eth_dev_rx_intr_disable(port_id, queue_id);
}
// num 为接收队列数量
static int sleep_until_rx_interrupt(int num, int lcore) {
static unsigned int sleep_count = 0;
struct rte_epoll_event event[num];
printf("lcore %u sleeps until interrupt triggers, times: %u\n",
lcore, sleep_count);
sleep_count++;
// timeout = -1, 代表不超时,在收到报文前一直等待
// event[i].epdata.data 获取rte_eth_dev_rx_intr_ctl_q()设置的用户数据
rte_epoll_wait(RTE_EPOLL_PER_THREAD, event, num, -1);
return 0;
}
int main() {
int ret;
int port_id = 0;
int queue_id = 0;
struct rte_eth_conf port_conf;
port_conf.intr_conf.rxq = 1; // 允许接收中断队列
rte_eth_dev_configure(portid, nb_rx_queue, n_tx_queue, &port_conf);
rte_eth_dev_start(port_id);
// data 参数用于传递用户数据给中断唤醒时处理,可将port和queue id传递过去,这里暂时设为NULL
ret = rte_eth_dev_rx_intr_ctl_q(port_id, queue_id,
RTE_EPOLL_PER_THREAD, RTE_INTR_EVENT_ADD, NULL);
if(ret < 0) {
rte_exit(EXIT_FAILURE, "rte_eth_dev_rx_intr_ctl_q failed = %d\n", ret);
}
uint16_t nb_rx;
uint16_t nb_tx;
int idle = 0; // 空闲计数
// 收发包流程
while(1) {
nb_rx = rte_eth_rx_burst(...);
nb_tx = rte_eth_tx_burst(...);
if(nb_rx == 0) {
if(idle++ < SUSPEND_THRESHOLD) {
rte_delay_us(1); // 也可以使用 rte_delay_us_sleep()
}
else {
turn_on_off_intr(port_id, queue_id, 1); // 开中断
sleep_unti_rx_interrupt(1, rte_lcore_id()); // 等待收到报文,触发中断
turn_on_off_intr(port_id, queue_id, 0); // 关中断
idle = 0;
}
}
}
}
其他细节请查看DPDK的l3fwd-power示例代码,主要关注
main(), main_legacy_loop() / main_intr_loop(), event_register()函数,以及APP_MODE_LEGACY标志。