报文的接收是整个协议栈的入口,负责从网卡中把报文接收并送往内核协议栈相应协议处理模块处理。

一种是网卡产生中断,通知内核进行接收报文。一次中断接收一个报文。在中断处理程序中把报文从硬件缓存中拷贝到内存中,并把报文加入到协议栈中对应的入口队列中,中断退出时调用收包软中断来从相应队列来读取报文进行处理。这种方式优点是内核对报文响应较快,在网卡上 有少量报文时效果较好。这样如果网卡有大量报文的话,会产生大量中断。中断会不断打断处理报文的软中断,这样先前的报文来不及处理,总是被打断,队列里的报文大量堆积导致不能被及时处理,导致报文性能下降。

另一种是中断加轮询的方式进行接收报文的。当一个报文到来时,产生中断通知内核,注册的中断处理程序首先禁止网卡的中断,内核开始轮询的从网卡硬件缓存或网卡硬件管理的内存队列中读取报文并处理,直到报文读完或超出了设置的允许读取最大报文个数,轮询函数停止处理报文。如果这次轮询处理完了全部报文,并接着使能网卡中断。如果轮询没有处理完全部报文,这时就重新调度软中断,等下次软中断被调度到后继续轮询处理报文。这样是为了防止轮询独占CPU时间。以后重复这样的接收报文步骤。这样内核处理一次中断可以处理多个报文,这样就避免了产生大量中断造成的系统开销。但支持这样的操作的网卡必须支持硬件能自动把报文存入内存中,或硬件缓存足够大。否则当一次轮询结束后并没有处理完全部的报文时,到下次软中断被调度到开始轮询的这段时间里,当网卡硬件内部的缓存满后会产生丢包。

Linux 提供对两种报文接收方式的支持,供不同网卡驱动来用。在本文中把第一种称为旧的方式(旧是相对于NAPI来说的),第二种方法linux内核中称为NAPI方式。

NAPI的另一个优点是设备处理更为公平,因为软中断顺序执行每个需要处理的设备的轮询函数,每个轮询函数执行时间是有限制的,超过最大的执行时间后就会执行下一个设备的轮询函数。这样就保证负载低的和负载高的设备被处理的机会是一样。

Linux 内核协议栈中报文接收的设计思路分析如下:
NAPI接口和旧接口两者有一下相同点:

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

(2)、两者都有存储报文的队列,NAPI的队列是由网卡驱动来管理的(一般是有网卡DMA到内存中,网卡驱动管理着存储报文的队列),而旧接口的队列是由Linux内核管理的,由网卡驱动负责往队列里加报文。

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

如上所述,我们现在可以知道每个CPU上需要有如下几个元素:
1、一个报文的接收队列,由旧接口来使用。
2、一个虚拟的NAPI设备,来有Linux协议栈自己创建并处理旧接口使用的队列中的报文。
3、一个NAPI的链表,上面挂着有报文需要处理的NAPI设备,由收包软中断来遍历该链表,顺序执行每个NAPI的轮询函数。

Linux内核具体实现
结构体定义如下

structsoftnet_data

{
  struct sk_buff_head   input_pkt_queue;//旧接口的输入队列
  struct list_head  poll_list;//有需要处理报文的NAPI设备链表
  struct napi_struct    backlog;//虚拟的NAPI设备 backlog
};

定义一个per_cpu变量 softnet_data

DECLARE_PER_CPU(struct softnet_data,softnet_data);

softnet_data 的初始化如下

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的结构体定义如下
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结构相绑定。

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
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

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一次轮询处理完队列的所以报文,调用该函数。
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_xxx(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();
    }
    else
    {
        /*否则轮询超时后没有处理完该处理的报文,就不用使能中断,也不用把NAPI从链表上摘下来。直接返回即可*/
    }
    return rx_cnt;
}

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

4、注册网卡的收包中断:例:
request_irq(irq,&rx_irq_handle, 0, netdev->name, netdev);

在中断处理函数中,把对应的NAPI加入到内核管理的链表中。
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);
}

网卡使用旧接口的方法:
在网卡收包中断处理程序中,把报文从网卡硬件缓存中拷贝到内存后,初始化好skb结构体,直接调用
netif_rx(skb)把报文加入到内核管理的队列中即可。

Linux网络子系统中报文的接收及NAPI的实现相关推荐

  1. linux 网络 指示灯 亮,Linux网络子系统中GRO的实现

    GRO (generic receive offload) GRO是在协议栈接收报文时进行减负的一种处理方式,该方式在设计上考虑了多种协议报文.主要原理是在接收端通过把多个相关的报文(比如TCP分段报 ...

  2. Linux网络子系统

    今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...

  3. 一文搞定 | Linux 网络子系统

    今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...

  4. linux 网络路径中网络协议栈有几种,linux网络路径中网络协议栈有几种

    网络路径有很多种,其中的linux网络路径是最常用的,也是最需要关注的.linux网络路径中网络协议栈有几种?电脑新装系统漏洞应不应该修复?了解网络安全常识,首先就要了解计算机网络安全有哪些基本注意事 ...

  5. Linux协议栈:基于ping流程窥探Linux网络子系统,及常用优化方法

    初识 Linux 网络栈及常用优化方法 RToax 2020年9月 初识 Linux 网络栈及常用优化方法 1. 文章简介 基于 ping 流程窥探 Linux 网络子系统,同时介绍各个模块的优化方法 ...

  6. Linux网络服务中,bond网络模式

    Linux网络服务中,bond网络模式 bond网络模式原理 多块网卡虚拟成一块网卡,实现冗余,多张网卡对外显示一张,具有同一个IP,网络配置都会使用Bonding技术做网口硬件层的冗余,防止单个网口 ...

  7. linux网络子系统研究:数据收发简略流程图

    Linux网络子系统十分庞大复杂,总想着等自己全部弄明白后再动笔写些笔记,但实在太耗时.后来想通了,先从宏观上掌握大体框图,然后再研究细节. 本文先给出一张自己画的网络数据收发简略流程图,每个路径都可 ...

  8. linux给网卡添加一个ip地址,linux网络配置中如何给一块网卡添加多个IP地址

    汤向峰每日一题-2017年3月16日: linux网络配置中如何给一块网卡添加多个IP地址 linux系统给网卡配置VIP的方法常见有两种:别名IP.以及辅助IP ================== ...

  9. Windows 10安装Linux子系统、可视化Linux、子系统中openfoam

    Windows 10安装Linux子系统.可视化Linux.子系统中openfoam安装 一. Windows 10安装Linux子系统 打开Windows PowerShell(管理员) 鼠标右键点 ...

  10. 深入理解 Cilium 的 eBPF(XDP)收发包路径:数据包在Linux网络协议栈中的路径

    Table of Contents 1 为什么要关注 eBPF? 1.1 网络成为瓶颈 1.2 eBPF 无处不在 1.3 性能就是金钱 2 eBPF 是什么? 3 为什么 eBPF 如此强大? 3. ...

最新文章

  1. torchvision.transforms 的CenterCrop():在图片的中间区域进行裁剪
  2. ubuntu部署java环境
  3. 自由自在休闲食品引领时尚潮流
  4. 桃李春蛋糕的配料之一 - 蛋糕预拌粉
  5. mysql-普通查询(General Query)慢查询(Slow Query)相关日志配置
  6. js微信小程序页面左上角返回跳转指定页面
  7. fullcalendar 显示的时间间隔只有四十五分钟_NHR系列智能显示控制仪表RS485通信中应用...
  8. RuoYi-Cloud [网关异常处理]请求路径:/code,异常信息:null
  9. 台式计算机年限怎么看,电脑使用年限_电脑使用年限查询
  10. 你不得不知道的流程规范@多方配合的需求质量控制
  11. JSP听课笔记(一)
  12. 远程摄像头软件mjpg-streamer使用指南
  13. Atitit it与互联网 的技术体系 目录 1. 概念范围 硬件 软件 应用 1 1.1. 职业分类 2 1.1.1. 软件类 2 1.1.2. 硬件类 2 1.1.3. 网络类 2 1.1.4.
  14. 关于flashdevelop测试flex项目时trace不出的解决方法
  15. linux 蓝牙串口 调试,linux 蓝牙串口 连接android手机调试
  16. Python之XML解析
  17. Git使用教程详解之四 服务器上的Git
  18. 微信软文的作用说到底就是营销的一种手段
  19. 【web开发 模拟ua调试】修改chrome浏览器的user agent
  20. 修复版拼团商城前端+后端微信小程序源码下载

热门文章

  1. Linux输入子系统浅析
  2. 在Idea中连接数据库并生成实体类(mybatis逆向生成实体类)
  3. 批量将ANSI文本txt文件转换成UTF8编码格式 (vbs方法)
  4. HighChart 体验之旅 (后台传递JSON参数和数据的方法)
  5. ashx 使用Session
  6. 数据库优化常用的途径(方法)
  7. 用pyqt5做个图书馆数据管理客户端!--pyqt+mysql的使用(入门必看!)
  8. STP重新收敛过程和补充内容
  9. weblogic11g 密码忘记肿么办?
  10. Android:Android Studio生成签名文件,自动签名,以及获取SHA1和MD5值