Linux 内核协议栈中报文接收的设计思路:

NAPI接口和旧接口两者有一下相同点:

(1)、对报文的处理都应该放在软中断中处理。

(2)、两者都有存储报文的队列,NAPI的队列是由网卡来管理的,旧接口的队列是由内核管理的。

每个NAPI设备都有一个轮询函数来由软中断调用,来进行轮询处理报文。我们可以建立一个虚拟的NAPI设备,让她的轮询函数来轮询的处理旧接口的报文队列,这样NAPI和旧接口的软中断处理函数就可以共用一个。收包软中断处理函数只是进行调用相应NAPI的轮询函数进行处理,并不关心是虚拟NAPI还是非虚拟NAPI。具体轮询细节由相关NAPI的轮询函数自己实现。

如上所述,我们现在可以知道每个CPU上需要有如下几个元素:

1、一个报文的接收队列,由旧接口来使用。

2、一个虚拟的NAPI设备。

3、一个NAPI的链表,上面挂着有报文需要处理的NAPI设备。

Linux内核具体实现:

结构体定义如下

1
2
3
4
5
6
struct softnet_data
{
  struct sk_buff_head   input_pkt_queue; //旧接口的输入队列
  struct list_head  poll_list; //有需要处理报文的NAPI设备
  struct napi_struct    backlog;//虚拟的NAPI设备 backlog
};

定义一个per_cpu变量 softnet_data

1
DECLARE_PER_CPU(struct softnet_data,softnet_data);

softnet_data 的初始化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int __init net_dev_init(void)
{
    int i, rc = -ENOMEM;
    /*
     *  Initialise the packet receive queues.
     */
    for_each_possible_cpu(i)
    {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        /*初始化输入队列*/
        skb_queue_head_init(&queue->input_pkt_queue);
        queue->completion_queue = NULL;
        /*初始化pool 链表头*/
        INIT_LIST_HEAD(&queue->poll_list);
        /*把backlog的轮询函数初始化为 process_backlog
         该函数来处理传统接口使用的输入队列的报文*/
        queue->backlog.poll = process_backlog;
        /*backlog 轮询函数一次可以处理的报文上限个数*/
        queue->backlog.weight = weight_p;
        queue->backlog.gro_list = NULL;
        queue->backlog.gro_count = 0;
    }
}

NAPI的数据结构:

每个NAPI需要如下几个元素:

1、一个轮询函数,来轮询处理报文。

2、需要有一个状态,来标识NAPI的调度状态,是正在被调度还是没有被调度。

3、每个NAPI的一次轮询能处理的报文个数应该有一个上限,不能无限制的处理下去,防止独占CPU资源导致其他进程被饿死。

4、每个NAPI应该和一个网络设备进行关联。

5、NAPI设计时考虑了多队列的情况。一个网络设备可以有多个报文队列,这样一个网络设备可以关联多个NAPI,每个NAPI只处理特定的队列的报文。这样在多核情况下,多个CPU核可以并行的进行报文的处理。一般专用的网络多核处理器存在这种情况。

6、每个NAPI在有报文的情况下应该挂到softnet_data的pool_list上去。

如上所述,NAPI的结构体定义如下

1
2
3
4
5
6
7
8
9
10
struct napi_struct
{
    struct list_head  poll_list;//挂到softnet_data的pool_list上
    unsigned long  state;//NAPI的调度状态
    int  weight;//一次轮询的最大处理报文数
    int  (*poll)(struct napi_struct *, int);//轮询函数
    struct net_device   *dev;//指向关联的网络设备
    struct list_head  dev_list;//对应的网络设备上关联的NAPI链表节点
        /*其他字段是gso功能用,这里先不讨论*/
};

NAPI的调度状态:

NAPI_STATE_SCHED设置时表示该NAPI有报文 需要接收。即把NAPI挂到softnet_data 时要设置该状态,处理完从softnet_data 上摘除该NAPI时要清除该状态。

一些NAPI的函数详解:

1、netif_napi_add(),把网络设备net_device 和NAPI结构相绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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->poll = poll;
    napi->weight = weight;
    /*把NAPI加入到网络设备相关联的NAPI链表上去。*/
    list_add(&napi->dev_list, &dev->napi_list);
    napi->dev = dev;
    /*绑定时设置NAPI是已调度状态,禁用该NAPI,以后手动的来清除该标识来使
    能NAPI.*/
    set_bit(NAPI_STATE_SCHED, &napi->state);
}

2、使能和禁用NAPI:

1
2
3
4
5
6
7
8
9
10
11
12
static inline void napi_disable(struct napi_struct *n)
{
    /*先设置NAPI状态为DISABLE*/
    set_bit(NAPI_STATE_DISABLE, &n->state);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
    /*循环的等待NAPI被调度完成,变成可用的,设置成SCHED状态*/
    while (test_and_set_bit(NAPI_STATE_SCHED, &n->state))
        msleep(1);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
    /*清除DISABLE状态*/
    clear_bit(NAPI_STATE_DISABLE, &n->state);
}

3、调度NAPI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __napi_schedule(struct napi_struct *n)
{
    unsigned long flags;
    /*链表操作必须在关闭本地中断的情况下操作,防止硬中断抢占*/
    local_irq_save(flags);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    /*把NAPI加入到本地CPU的softnet_data 的pool_list 链表上*/
    list_add_tail(&n->poll_list,
                  &__get_cpu_var(softnet_data).poll_list);
    /*调度收包软中断*/
    __raise_softirq_irqoff(NET_RX_SOFTIRQ);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    local_irq_restore(flags);
}

4、NAPI执行完毕,如果NAPI一次轮询处理完队列的所以报文,调用该函数。

1
2
3
4
5
6
7
8
9
10
11
12
void __napi_complete(struct napi_struct *n)
{
    BUG_ON(!test_bit(NAPI_STATE_SCHED, &n->state));
    BUG_ON(n->gro_list);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    /*把NAPI从softnet_data的pool_list上摘除下来*/
    list_del(&n->poll_list);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
    /*使能NAPI,允许它下次可以被再次调度*/
    smp_mb__before_clear_bit();
    clear_bit(NAPI_STATE_SCHED, &n->state);
}

一般网卡驱动的NAPI的步骤:

1、不同的网卡驱动都会为网络设备定义自己的结构体。有的是自己的结构体中即包括net_device和napi,有的是把自己的结构体放到net_device的priv部分。

2、实现NAPI的轮询函数 rx_pool(struct napi_struct *napi,int weigh);

int rx_pool(struct napi_struct *napi,int weigh)

{

int rx_cnt = 0;

struct sk_buff *skb;

while(rx_cnt < weigh)

{

skb = netdev_alloc_skb();

copy_skb_form_hw(skb);//把报文从硬件缓存中读到内存skb中*/

rx_cnt ++;

netif_receive_skb(skb);//送协议栈直接处理

}

if(rx_cnt < weigh)

{

/*处理报文小与允许处理最大个数,表示一次轮询处理完全部的报文。

使能收包中断*/

napi_complete(napi)

enable_rx_irq();

}

return rx_cnt;

}

3、把网络设备跟NAPI相关联,netif_napi_add(netdev,napi,rx_pool,weigh);

4、注册网卡的收包中断:例:

request_irq(irq,&rx_irq_handle, 0, netdev->name, netdev);

在中断处理函数中

static irqreturn_t rx_irq_handle(int irq, void *data)

{

struct net_device *netdev = data

struct napi_struct *napi = netdev_priv(netdev)->napi;

disable_rx_irq();

__napi_schedule(napi);

}

NAPI 方式的实现相关推荐

  1. 论网卡数据是如何从驱动到桥接/ip层(NAPI方式)

    前言 上一篇博客我们分析了普通的中断方式接收数据包的流程.并且在分析流程和代码的过程中看到napi的入口函数(napi_schedule).当时提到了该函数,大家应该还有印象.我们这里再次把代码截取一 ...

  2. Linux NAPI/非NAPI

    本文主要介绍二层收包流程,包括NAPI与非NAPI方式: NAPI:数据包到来,第一个数据包产生硬件中断,中断处理程序将设备的napi_struct结构挂在当前cpu的待收包设备链表softnet_d ...

  3. Linux Kernel TCP/IP Stack — L1 Layer — NIC Controller — NAPI

    目录 文章目录 目录 NAPI 中断方式与轮询方式 NAPI 值得注意的是,传统收包方式是每个报文都会触发中断,如果中断太频繁,CPU 就总是处理中断,其他任务无法得到调度,于是 NAPI(New A ...

  4. NAPI技术--原理和实现(一)

    概述 NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就叫NAPI(New API),在2.5之后引入. 简单来说,NAPI是综合中断方式与轮询方式的技术. 中断的好处是响 ...

  5. Linux网络协议栈:NAPI机制与处理流程分析(图解)

    Table of Contents NAPI机制 NAPI缺陷 使用 NAPI 先决条件 非NAPI帧的接收 netif_rx - 将网卡中收到的数据包放到系统中的接收队列中 enqueue_to_b ...

  6. linux 内核协议栈 NAPI机制与处理流程分析(图解)

    目录 1 NAPI 机制 1.1 NAPI 缺陷 1.2 使用 NAPI 先决条件 1.3 非NAPI帧的接收 1.3.1 netif_rx - 将网卡中收到的数据包放到系统中的接收队列中 1.3.2 ...

  7. NAPI之(一)——原理和实现

    概述 NAPI是Linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就叫NAPI(New API),在2.5之后引入. 简单来说,NAPI是综合中断方式与轮询方式的技术. 中断的好处是响 ...

  8. Linux内核中网络数据包的接收-第一部分 概念和框架

    与网络数据包的发送不同,网络收包是异步的的.由于你不确定谁会在什么时候突然发一个网络包给你.因此这个网络收包逻辑事实上包括两件事: 1.数据包到来后的通知 2.收到通知并从数据包中获取数据这两件事发生 ...

  9. linux 内核网络协议栈--数据从接收到IP层(二)

    此处主要讲的是从数据来到,中断到最终数据包被处理的过程. 首先来介绍一下IO端口访问问题,内核提供了这样一组函数处理: /kernel/io.c中 inb( ).inw( ).inl( )函数 分别从 ...

最新文章

  1. UpdatePanel控件的简单属性学习
  2. LeetCode 340. Longest Substring with At Most K Distinct Characters
  3. 最短路之 SPFA(判环+负权)
  4. springboot官网-application.properties文件
  5. Android Java 代码设置 layout_weight 属性
  6. 记录: 开发中的2个线程的使用问题
  7. Elasticsearch查询性能优化
  8. 库克发文纪念苹果成立45周年:伟大使命还有待实现
  9. 英语总结系列(二十二):Baby偶遇GCT
  10. 7 -- Spring的基本用法 -- 6... Spring 3.0 提供的Java配置管理
  11. linux环境变量介绍
  12. 不显示藏宝阁试穿服务器,梦幻西游:藏宝阁试穿功能的妙用,无限回档测试
  13. 用户空间缺页异常pte_handle_fault()分析--(下)--写时复制
  14. win7安装计算机的更新,解决win7系统更新升级教程
  15. BAT大牛亲授从零起步基于ElasticSearch的搜房网(前后端集成)实战(第二章需求分析和数据库设计)
  16. 音乐计算机锦鲤抄,锦鲤抄 (feat. 银临)
  17. 这是属于你我平凡人的荣耀
  18. 文章的DOI号是神马
  19. MVC、MVP与MVT
  20. 初步认识地图布局和指北针 - SuperMap iDesktop 8C

热门文章

  1. HuggingFace学习1:tokenizer学习与将文本编码为固定长度(pytorch)
  2. Linux赋予目录或文件任何人都可以读、写、执行的操作
  3. 计算机键盘大赛活动总结,参加技能大赛的活动总结
  4. python开发个人博客_[Web开发] Flask+Python 开发个人博客(一)
  5. 微信小程序实现轨迹回放
  6. java基础之-I/O流和File类解析
  7. 可用于wpf的图表控件:WPFTookit Chart
  8. Hibernate框架之HQL查询与Criteria 查询的区别
  9. Classic ASP 系列(3) Application 对象
  10. python 3_Python3 列表