数据包接收系列 — NAPI的原理和实现

本文主要内容:简单分析NAPI的原理和实现。

内核版本:2.6.37

Author:zhangskd @ csdn

 

概述

 

NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就叫NAPI(New API),在2.5之后引入。

简单来说,NAPI是综合中断方式与轮询方式的技术。

中断的好处是响应及时,如果数据量较小,则不会占用太多的CPU事件;缺点是数据量大时,会产生过多中断,

而每个中断都要消耗不少的CPU时间,从而导致效率反而不如轮询高。轮询方式与中断方式相反,它更适合处理

大量数据,因为每次轮询不需要消耗过多的CPU时间;缺点是即使只接收很少数据或不接收数据时,也要占用CPU

时间。

NAPI是两者的结合,数据量低时采用中断,数据量高时采用轮询。平时是中断方式,当有数据到达时,会触发中断

处理函数执行,中断处理函数关闭中断开始处理。如果此时有数据到达,则没必要再触发中断了,因为中断处理函

数中会轮询处理数据,直到没有新数据时才打开中断。

很明显,数据量很低与很高时,NAPI可以发挥中断与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高

说低不低,则NAPI则会在两种方式切换上消耗不少时间,效率反而较低一些。

 

实现

 

来看下NAPI和非NAPI的区别:

(1) 支持NAPI的网卡驱动必须提供轮询方法poll()。

(2) 非NAPI的内核接口为netif_rx(),NAPI的内核接口为napi_schedule()。

(3) 非NAPI使用共享的CPU队列softnet_data->input_pkt_queue,NAPI使用设备内存(或者

设备驱动程序的接收环)。

 

(1) NAPI设备结构

/* Structure for NAPI scheduling similar to tasklet but with weighting */

struct napi_struct {
    /* The poll_list must only be managed by the entity which changes the
     * state of the NAPI_STATE_SCHED bit. This means whoever atomically
     * sets that bit can add this napi_struct to the per-cpu poll_list, and
     * whoever clears that bit can remove from the list right before clearing the bit.
     */
    struct list_head poll_list; /* 用于加入处于轮询状态的设备队列 */
    unsigned long state; /* 设备的状态 */
    int weight; /* 每次处理的最大数量,非NAPI默认为64 */
    int (*poll) (struct napi_struct *, int); /* 此设备的轮询方法,非NAPI为process_backlog() */

#ifdef CONFIG_NETPOLL
    ...
#endif

    unsigned int gro_count;
    struct net_device *dev;
    struct list_head dev_list;
    struct sk_buff *gro_list;
    struct sk_buff *skb;
};

(2) 初始化

初始napi_struct实例。

void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
        int (*poll) (struct napi_struct *, int), int weight)
{
    INIT_LIST_HEAD(&napi->poll_list);
    napi->gro_count = 0;
    napi->gro_list = NULL;
    napi->skb = NULL;
    napi->poll = poll; /* 设备的poll函数 */
    napi->weight = weight; /* 设备每次poll能处理的数据包个数上限 */

    list_add(&napi->dev_list, &dev->napi_list); /* 加入设备的napi_list */
    napi->dev = dev; /* 所属设备 */

#ifdef CONFIG_NETPOLL
    spin_lock_init(&napi->poll_lock);
    napi->poll_owner = -1;
#endif
    set_bit(NAPI_STATE_SCHED, &napi->state); /* 设置NAPI标志位 */
}

 

(3) 调度

在网卡驱动的中断处理函数中调用napi_schedule()来使用NAPI。

/**
 * napi_schedule - schedule NAPI poll
 * @n: napi context
 * Schedule NAPI poll routine to be called if it is not already running.
 */

static inline void napi_schedule(struct napi_struct *n)
{
    /* 判断是否可以调度NAPI */
    if (napi_schedule_prep(n))
        __napi_schedule(n);
}

判断NAPI是否可以调度。如果NAPI没有被禁止,且不存在已被调度的NAPI,

则允许调度NAPI,因为同一时刻只允许有一个NAPI poll instance。

/**
 * napi_schedule_prep - check if napi can be scheduled
 * @n: napi context
 * Test if NAPI routine is already running, and if not mark it as running.
 * This is used as a condition variable insure only one NAPI poll instance runs.
 * We also make sure there is no pending NAPI disable.
 */

static inline int napi_schedule_prep(struct napi_struct *n)
{
    return !napi_disable_pending(n) && !test_and_set_bit(NAPI_STATE_SCHED, &n->state);
}
 
static inline int napi_disable_pending(struct napi_struct *n)
{
    return test_bit(NAPI_STATE_DISABLE, &n->state);
} 

enum {
    NAPI_STATE_SCHED, /* Poll is scheduled */
    NAPI_STATE_DISABLE, /* Disable pending */
    NAPI_STATE_NPSVC, /* Netpoll - don't dequeue from poll_list */
};

NAPI的调度函数。把设备的napi_struct实例添加到当前CPU的softnet_data的poll_list中,

以便于接下来进行轮询。然后设置NET_RX_SOFTIRQ标志位来触发软中断。

void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;
    local_irq_save(flags);
    ____napi_schedule(&__get_cpu_var(softnet_data), n);
    local_irq_restore(flags);
}

static inline void ____napi_schedule(struct softnet_data *sd, struct napi_struct *napi)
{
    /* 把napi_struct添加到softnet_data的poll_list中 */
    list_add_tail(&napi->poll_list, &sd->poll_list);
    __raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 设置软中断标志位 */
}

 

(4) 轮询方法

NAPI方式中的POLL方法由驱动程序提供,在通过netif_napi_add()加入napi_struct时指定。

在驱动的poll()中,从自身的队列中获取sk_buff后,如果网卡开启了GRO,则会调用

napi_gro_receive()处理skb,否则直接调用netif_receive_skb()。

POLL方法应该和process_backlog()大体一致,多了一些具体设备相关的部分。

 

(5) 非NAPI和NAPI处理流程对比

以下是非NAPI设备和NAPI设备的数据包接收流程对比图:

NAPI方式在上半部中sk_buff是存储在驱动自身的队列中的,软中断处理过程中驱动POLL方法调用

netif_receive_skb()直接处理skb并提交给上层。

/**
 * netif_receive_skb - process receive buffer from network
 * @skb: buffer to process
 * netif_receive_skb() is the main receive data processing function.
 * It always succeeds. The buffer may be dropped during processing
 * for congestion control or by the protocol layers.
 * This function may only be called from softirq context and interrupts
 * should be enabled.
 * Return values (usually ignored):
 * NET_RX_SUCCESS: no congestion
 * NET_RX_DROP: packet was dropped
 */

int netif_receive_skb(struct sk_buff *skb)
{
    /* 记录接收时间到skb->tstamp */
    if (netdev_tstamp_prequeue)
        net_timestamp_check(skb);
 
    if (skb_defer_rx_timestamp(skb))
        return NET_RX_SUCCESS;

#ifdef CONFIG_RPS
    ...
#else
    return __netif_receive_skb(skb);
#endif
}

__netif_receive_skb()在上篇blog中已分析过了,接下来就是网络层来处理接收到的数据包了。

 

转载于:https://www.cnblogs.com/aiwz/p/6333291.html

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. napi_create_int32: 用于创建一个 int32 类型的 JavaScript 数值。函数原型为:napi_status napi_create_int32(napi_env env, int32_t value, napi_value *result)。其中,env 表示当前的 napi 环境,value 表示要创建的 int32 类型的数值,result 用于存储创建的 JavaScript 数值。 2. napi_get_reference_value: 用于从一个 napi 引用中获取对应的 JavaScript 对象。函数原型为:napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value *result)。其中,env 表示当前的 napi 环境,ref 表示要获取的引用对象,result 用于存储获取到的 JavaScript 对象。 3. napi_call_function: 用于调用一个 JavaScript 函数函数原型为:napi_status napi_call_function(napi_env env, napi_value recv, napi_value func, size_t argc, const napi_value *argv, napi_value *result)。其中,env 表示当前的 napi 环境,recv 表示函数接收者,func 表示要调用的 JavaScript 函数,argc 表示参数个数,argv 表示参数列表,result 用于存储函数调用的结果。 4. NAPI_CALL_BASE: 用于检查 napi 方法返回的错误码,如果出现错误则将错误信息打印到日志中。函数原型为:#define NAPI_CALL_BASE(env, call) \ do { \ napi_status status = (call); \ if (status != napi_ok) { \ const napi_extended_error_info* error_info = 0; \ napi_get_last_error_info((env), &error_info); \ const char* error_message = (error_info->error_message != NULL) ? \ error_info->error_message : "empty error message"; \ printf("NAPI_CALL_BASE failed at %s:%d status=%d, error_message=%s\n", \ __FILE__, __LINE__, status, error_message); \ } \ } while(0) 5. AsyncTask::Schedule: 用于将一个异步任务添加到事件循环中执行。函数原型为:napi_status AsyncTask::Schedule(napi_env env, AsyncTaskExecuteCallback execute, AsyncTaskCompleteCallback complete, void *data, napi_async_context *context)。其中,env 表示当前的 napi 环境,execute 表示异步任务的执行函数,complete 表示异步任务执行完成后的回调函数,data 表示异步任务的数据,context 表示异步任务的上下文。 使用注意: 1. napi_create_int32 和 napi_get_reference_value 的返回值应该被检查,以确保正确地创建和获取 JavaScript 对象。 2. 在调用 napi_call_function 之前,需要确保传入的参数正确,包括函数接收者、函数本身和参数列表。 3. 需要注意异步任务的执行时间,以免阻塞主线程。 4. 在使用 NAPI_CALL_BASE 宏时,需要注意错误信息的输出方式,可以根据需要进行修改。 5. 在使用 AsyncTask::Schedule 时,需要注意异步任务的数据,以确保异步任务可以正确地访问和修改数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值