如何将 NAPI 轮询功能卸载到工作队列

Posted

技术标签:

【中文标题】如何将 NAPI 轮询功能卸载到工作队列【英文标题】:How to offload NAPI poll function to workqueue 【发布时间】:2015-06-08 08:30:46 【问题描述】:

我正在使用 linux 3.3,用于 smsc911x 的以太网驱动程序。我想将 NAPI 轮询函数移动到工作队列。

我的问题是: 1. 如何将 NAPI 轮询函数参数传递给 work_struct? 2. 如何从 work_struct 中取回 NAPI 轮询函数参数? (与上述 Q.1 相关) 3. 如何将 npackets 值返回给原始 NAPI 轮询函数调用者?

这里有一些解释:

当前 NAPI 轮询函数直接读取接收 FIFO,我想将其更改为使用 DMA 控制器执行此操作。对于这个 DMA,我触发 DMA,使用 wait_event_interruptible 休眠,并通过带有 wake_up_interruptible 的 DMA 的 ISR 唤醒。如您所知,NAPI 轮询函数位于中断上下文 (softirq) 中,因此我无法在那里睡觉以完成 DMA。我想将 NAPI 轮询函数(读取 RX FIFO)移动到等待队列(进程上下文)使用 work_struct。

问题是,内核调用 NAPI poll 函数时有两个参数:struct napi_struct *napi 和 int budget。 我想将这些参数传递给 work_struct 并将 work_struct 排队到工作队列(使用 queue_work 函数)。

work_struct 如下所示。 (包括/linux/workqueue.h)

struct work_struct  
        atomic_long_t data;
        struct list_head entry; 
        work_func_t func;
#ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
#endif
;

我认为 atomic_long_t 数据用于将参数传递给 work_struct。如何将参数传递给 work_struct? 我试过这个(我在设备驱动程序 struct smsc911x_data 的结构中添加了一个成员 struct work_struct rx_work 用于传递工作。):

struct work_arg  // a new struct for pass the arguments
        struct napi_struct *napi;
        int budget;
;

/* NAPI poll function */
static int smsc911x_poll(struct napi_struct *napi, int budget) 
        struct smsc911x_data *pdata =
                container_of(napi, struct smsc911x_data, napi);
        struct net_device *dev = pdata->dev;
        int npackets = 0;
if (enable_rx_use_dma == 1)   // when using DMA for FIFO read
        prom_printf("moving it to workqueue\n");
        struct work_arg *p;
        p = kzalloc(sizeof(struct work_arg), GFP_KERNEL);
        p->napi = napi;
        p->budget = budget;
        pdata->rx_work.data = (atomic_long_t) p; // <== THIS LINE
        prom_printf("queue work, with napi = %x, budget = %d\n", napi, budget);
        queue_work(rx_work_workqueue, &pdata->rx_work); // smsc911x_poll_work  else 
        -- original NAP poll function, reads FIFO until it's empty and enables the RX interrupt and 
        -- keeps the number of processed packets to npackets.
        return npackets;

对于上面的“THIS LINE”,我在编译过程中遇到错误。

with pdata->rx_work.data = p; , I get error: incompatible types when assigning to type 'atomic_long_t' from type 'struct work_arg *'
with pdata->rx_work.data = (atomic_long_t) p; , I get error: conversion to non-scalar type requested.

另外,在新的工作函数中,如何提取原始参数?我在下面尝试了这个,这给了我错误。

/* New work function called by the default worker thread */ static int smsc911x_poll_work(struct work_struct *work) 
        struct smsc911x_data *pdata =
                container_of(work, struct smsc911x_data, rx_work);
        struct net_device *dev = pdata->dev;
        int npackets = 0;
        struct napi_struct *napi = (struct work_struct *)work->data.napi;  // <== THIS LINE
        int budget = (struct work_struct *)work->data.budget;  // <== THIS LINE ..

从上面的“这条线”,我得到下面的错误。

错误:“atomic_long_t”没有名为“napi”的成员 错误:“atomic_long_t”没有名为“budget”的成员

而且我不知道如何将返回值传递给原始 NAPI 轮询函数调用者。

我不确定这种转换(从 NAPI 轮询到工作队列)是否可行。 很抱歉问题很长,但我们将不胜感激。

添加:因为 struct smsc911x_data 既有 struct napi napi;和 struct work_struct rx_work;作为成员,我可以通过以下方式轻松地从 work_struct *work(工作函数的参数)中获取 struct napi *napi:

结构 smsc911x_data *pdata = container_of(工作,结构 smsc911x_data,rx_work); struct napi_struct *napi = &pdata.napi;

所以也许我可以通过结构 smsc911x_data 中的新成员值传递 int 预算。我还想知道这个案例的正确做法。

【问题讨论】:

应该是atomic_long_t 指向napi_struct 的指针? @LPs LDD3 书说如果我们想在编译时填充 work_struct,我们使用 DECLARE_WORK(name, void (*function)(void *), void *data);我猜 void *data 进入 atomic_long_t 数据; work_struct 的。如果我们想在运行时做,我们使用 INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);这是我的猜测。 【参考方案1】:
    如何将 NAPI 轮询函数参数传递给 work_struct?

只需创建一个嵌入work_struct 的新结构并将您的参数添加到其中:

struct my_work 
    struct work_struct base_work;// Embedded work_struct
    struct napi_struct *napi; // Your arguments
    int budget;
;

static int smsc911x_poll(struct napi_struct *napi, int budget) 
    struct my_work* p = kmalloc(sizeof(*p), GFP_ATOMIC /* Flag usable for interrupt context */);
    INIT_WORK(&p->base_work, smsc911x_poll_work); // Initialize underliying structure.
    p->budget = budget; // Initialize your members
    p->napi = napi;
    ...

    如何从 work_struct 取回 NAPI 轮询函数参数? (与上述 Q.1 相关)

使用container_of

static int smsc911x_poll_work(struct work_struct *work) 
    struct my_work* p = container_of(work, struct my_work, base_work);
    ...

    如何将 npackets 值返回给原始 NAPI 轮询函数调用者?

正如我从描述中了解到的(参见,例如,http://www.linuxfoundation.org/collaborate/workgroups/networking/napi),这个函数应该处理准备好的数据包。并且这个处理应该在函数本身内完成,而不是推迟到workqueue 或类似的。

【讨论】:

谢谢,现在我发现我可以在 struct smsc911x_data 中添加参数,因为它包含 work_struct。 (我可以使用 container_of 来获取私有数据(smsc911x_data)及其包含的任何内容。 顺便说一句,你怎么能确定我不能将事情从 softirq 卸载到工作队列? (从你引导我到的链接?)我无法从链接中找到这样的含义。 @ChanKim:我没有使用过网络驱动程序,但我正在集中精力使用文件系统驱动程序和类似的东西。它们中的所有回调都应该完成它们的工作,直到明确指定相反的内容(如function should begin ...)。不过你可以试试。 我现在几乎放弃了这个想法。我至少可以将函数卸载到工作队列,但问题是内核需要返回值,所以我必须通过返回一个假值来欺骗内核(这很困难,因为返回值实际上取决于实际处理。)。工人无法将值返回给原始调用者。【参考方案2】:

这种方法似乎非常无效,因为您需要两个中断,一个在收到数据包时,另一个在 DMA 传输完成时。

我认为这是支持 DMA 的网络接口的工作方式: 当数据包到达时,套接字缓冲区已经分配并映射到 DMA 内存缓冲区,并且 DMA 已启用。

    数据包通过 DMA 从 NIC 传输到 Socket 缓冲区 NIC 引发硬件中断(当 DMA 传输完成时)。 硬件中断处理程序安排数据包接收软件中断 (SOFTIRQ) SOFTIRQ 执行 NAPI poll() 以进行进一步处理。 NAPI poll() 处理 DMA 缓冲区中的数据包,并将其作为sk_buff 传递给上层,并初始化新的 DMA 缓冲区。如果处理了所有数据包(配额),则启用 IRQ 并告知 NAPI 停止轮询。

【讨论】:

以上是关于如何将 NAPI 轮询功能卸载到工作队列的主要内容,如果未能解决你的问题,请参考以下文章

RabbitMq之工作队列(轮询发送消息)

RabbitMQ学习第二记:工作队列的两种分发方式,轮询分发(Round-robin)和 公平分发(Fair dispatch)

conductor FAQ

四RabbitMQ中模式—工作队列(Work Queues)模式

在多个进程之间拆分工作队列

如何配置 Azure 服务总线队列以将消息推送到客户端而不进行轮询?