精华内容
下载资源
问答
  • 网卡收包流程
    千次阅读
    2018-04-14 18:41:48

    关于收包流程的研究
    【提出问题】
    1、网卡收包流程是怎么样的?
    网卡收包流程是怎么样的?
    网络驱动收包大致存在3中情况:
    1、no NAPI: mac每收到一个以太网包,都会产生一个接收中断给cpu,即完全靠中断方式来收包。缺点就是当网络流量很大时,cpu大部分时间都耗在了处理mac中断上。
    2、netpoll:在网络和I/O子系统尚不能完整可用时,模拟了来自指定设备的中断,即轮询收包。(依次发出询问是否要收包)缺点是实时性差。
    3、NAPI:就是上面两种情况的折中。数据量少时采用中断,数据量多时采用轮询。mac收到一个包来后会产生接收中断,但是马上关闭。直到收够了netdev_max_backlog个包(默认300),或者收完mac上所有包后,才再打开接收中断。
    通过sysctl来修改 net.core.netdev_max_backlog
    或者通过proc修改 /proc/sys/net/core/netdev_max_backlog

    介绍三种关键数据结构:
    sk_buff
    功能:一种缓存,适配不同的协议,在不同的数据结构中传递。
    softnet_data
    功能:NIC和网络层之间的处理队列,从NIC和POLL之间传递信息。
    net_device
    功能:这个dev指针会指向收到数据包的网络设备。
    大致的流程如下图:
    这里写图片描述
    网卡多队列的情况
    http://note.youdao.com/noteshare?id=e08e41f79ffacedc792ef4444ab7ff1d&sub=A783644358AC4CA4B2912D2DBEEBB7E9
    【提出疑问】
    1、收包过程中深层次原理是怎样的?
    2、软中断中是如何使用NAPI的?
    收包过程中深层次原理是怎样的?
    softirq(软中断)
    此为中断处理“下半部”机制。中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。这也就是内核的上半部和下半部。
    top中si就是软中断,在ps -elf中ksoftirqd就是软中断的一个守护进程,且每个cpu一个守护进程。
    上半部处理硬中断
    1、接收到数据包后关闭硬中断
    2、标记NAPI_STATE_SCHED 状态
    3、将网络设备挂到轮询列表中
    4、触发网络接收软中断,开始下半部处理
    下半部处理软中断
    1、遍历轮询列表
    2、从轮询列表中取出napi_struct结构,执行钩子函数napi_struct.poll()
    3、若poll钩子函数处理完所有包,打开收包活动掩码,再来数据包的话会产生rx中断
    4、调用函数,结束napi
    5、将网络设备从轮询列表中移走,同时去掉NAPI_STATE_SCHED 状态的标记
    软中断中是如何使用NAPI的?
    在进入网卡中断时会初始化一个budget(系统预算),即最多处理的网络包个数,如果有多个网卡(放在poll_list里),是共享该budget,同时每个网卡也一个权重weight或者说是配额quota。若quota用完,则放到轮询队列尾部,重新设置一下quota的值以等待while轮询。若quota没有用完且收到的包不多,这时网卡比较空闲,那该设备就会从轮询列表中摘除。
    这里写图片描述
    【提出疑问】
    1、什么是钩子函数?
    什么是钩子函数?
    钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。

    更多相关内容
  • 分析了kernel下网卡收包流程,对比了DM9000网卡与e100网卡收包逻辑上的不同。

    由于本人工作内容主要集中于kernel的网络子系统,刚接触这个模块,于是想梳理一下网卡驱动的收包过程,以下内容为个人理解,如有不对,希望大家能够多多指正,相互成长~

    后续会持续更新有关kernel网络子系统相关的内容,坚持每周一更!
    原创不易,转载请注明出处~

    OK,进入正题。

    目前网卡收包可以分为两类:中断方式与轮询方式

    中断收包

    网卡作为数据收发设备,它将我们的数据转换成二进制信号通过媒介传输到核心网中去,那么当核心网有数据传输到本地,我们如何知道数据来了呢?最简单的方法就是网卡产生一个中断信号,通知CPU有数据包来临,CPU进入网卡的中断服务函数读取数据,构建skb然后递交到网络子系统中作进一步分析。

    中断收包的方式响应及时,数据包来临立马就能得到处理,但是此种方式在大量数据包来临时却有一个致命的缺点,那就是CPU被频繁中断,其它任务无法得到执行,这对于kernel而言是无法忍受的。因此,kernel提出了一种新的方式用于高网络负载情景下的收包方式,NAPI(new API)。

    NAPI收包

    NAPI收包其实就是驱动提供轮询函数,在有大量数据包的场景下,关闭网卡中断,在软中断中执行驱动的轮询函数进行收包,此种方式避免了CPU频繁进出中断,同时软中断也保证了系统的性能。

    但是NAPI方式对网卡的收包方式提出了要求,那就是需要支持ring buffer(即支持DMA)的网卡才能真正意义上的使用NAPI方式进行收包,不支持DMA的网卡还是需要通过中断的方式进行收包,下图描述了支持DMA方式的网卡是如何进行收包的:
    网卡ring buffer

    网卡驱动会申请一段ring buffer用于数据包的接收,ring buffer中存放的是描述符(注意是描述符,不是真正的数据),当网卡收到数据时,驱动申请skb并将skb的数据区地址存放到ring buffer的描述符中,且标记该描述符就绪,网卡会找到已就绪的描述符并通过DMA将数据写入到skb的数据区中去,同时网卡会标记该描述符已被使用,驱动读取ring buffer中的数据并维护ring buffer的状态。

    讲完ring buffer,那么NAPI收包方式就比较容易理解了,由于网卡支持DMA,当网卡中有数据来临时,通过中断通知一次CPU处理数据即可,接下来由DMA负责搬运数据到内存中去,CPU只要隔一段时间去清理ring buffer中未读的数据即可(调用驱动的poll函数),这便是NAPI的思想。

    那么不支持DMA的网卡呢?kernel为了统一使用NAPI的思想,对于不支持DMA的网卡,在网卡中断中仅仅负责将数据包挂接到input_pkt_queue链表中,然后kernel自己设计了一个poll函数(process_backlog函数)进行数据包的处理。

    数据结构分析

    前面讲到kernel为了统一使用NAPI方式,对于不支持DMA的网卡做了兼容,接下来我们从代码上自下而上整理一下网卡的收包方式。

    首先便是CPU收包的入口softnet_data数据结构:

    对于该结构体介绍几个重要的成员,见注释:

    struct softnet_data {
    	struct list_head	poll_list;   //轮询链表,各个驱动的poll方法都会挂接在此链表下
    	struct sk_buff_head	process_queue; 
    
    	/* stats */
    	unsigned int		processed;
    	unsigned int		time_squeeze;
    	unsigned int		received_rps;
    #ifdef CONFIG_RPS
    	struct softnet_data	*rps_ipi_list;
    #endif
    #ifdef CONFIG_NET_FLOW_LIMIT
    	struct sd_flow_limit __rcu *flow_limit;
    #endif
    	struct Qdisc		*output_queue;
    	struct Qdisc		**output_queue_tailp;
    	struct sk_buff		*completion_queue;
    
    #ifdef CONFIG_RPS
    	/* input_queue_head should be written by cpu owning this struct,
    	 * and only read by other cpus. Worth using a cache line.
    	 */
    	unsigned int		input_queue_head ____cacheline_aligned_in_smp;
    
    	/* Elements below can be accessed between CPUs for RPS/RFS */
    	struct call_single_data	csd ____cacheline_aligned_in_smp;
    	struct softnet_data	*rps_ipi_next;
    	unsigned int		cpu;
    	unsigned int		input_queue_tail;
    #endif
    	unsigned int		dropped;
    	struct sk_buff_head	input_pkt_queue;  //输入队列,不支持DMA的网卡,收到的包会挂接在此链表下
    	struct napi_struct	backlog;          //kernel为了统一NAPI方式收包,为不支持DMA的网卡构建的NAPI结构体
    
    };
    

    接下来便是napi_struct结构体了:

    该结构体中最重要的成员便是驱动需要注册的poll回调函数了。由于kernel目前统一采用了NAPI方式,所以对于每个网卡都需要构建一个属于自己的NAPI结构体(当然接收多队列下,一张网卡可能需要为每个cpu都构建一个napi实例)。

    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;  //挂接softnet_data结构下poll_list链表头下
    
    	unsigned long		state;
    	int			weight;   //收包权重
    	unsigned int		gro_count;
    	int			(*poll)(struct napi_struct *, int);  //驱动注册的poll回调函数
    #ifdef CONFIG_NETPOLL
    	spinlock_t		poll_lock;
    	int			poll_owner;
    #endif
    	struct net_device	*dev;
    	struct sk_buff		*gro_list;
    	struct sk_buff		*skb;
    	struct hrtimer		timer;
    	struct list_head	dev_list;
    	struct hlist_node	napi_hash_node;
    	unsigned int		napi_id;
    };
    

    收包流程分析与对比

    介绍完两个重要的数据结构后,下面以e100网卡(该网卡支持DMA)为例,介绍下该网卡收包时的函数调用。

    1. 首先在e100_probe函数中,构建了NAPI结构体:

      netif_napi_add(netdev, &nic->napi, e100_poll, E100_NAPI_WEIGHT);
      
    2. 在网卡中断中,禁止了本地中断,并开始了NAPI方式收包:

      	if (likely(napi_schedule_prep(&nic->napi))) {
      		e100_disable_irq(nic);
      		__napi_schedule(&nic->napi); //将驱动的napi结构体挂接到本地cpu softnet_data结构体下的poll_list链表下并开启NET_RX_SOFTIRQ软中断开始收包
      	}
      
    3. 软中断net_rx_action中,开始遍历cpu下的poll_list链表,并取出挂接在下面的napi结构体并执行驱动注册的poll函数进行收包:

      struct softnet_data *sd = this_cpu_ptr(&softnet_data);
      unsigned long time_limit = jiffies + 2;
      int budget = netdev_budget;
      LIST_HEAD(list);
      LIST_HEAD(repoll);
      
      local_irq_disable();
      list_splice_init(&sd->poll_list, &list); //取出挂接在poll_list下的所有链表,并重新初始poll_list链表
      local_irq_enable();
      for (;;) {
      		struct napi_struct *n;
      
      		if (list_empty(&list)) {
      			if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
      				return;
      			break;
      		}
      
      		n = list_first_entry(&list, struct napi_struct, poll_list); //取出第一个napi结构体
      		budget -= napi_poll(n, &repoll); //执行驱动注册的poll函数进行收包
      
      		/* If softirq window is exhausted then punt.
      		 * Allow this to run for 2 jiffies since which will allow
      		 * an average latency of 1.5/HZ.
      		 */
      		if (unlikely(budget <= 0 ||
      			     time_after_eq(jiffies, time_limit))) {
      			sd->time_squeeze++;
      			break;
      		}
      	}
      

    接下来看看不支持DMA的网卡是如何收包的,以DM9000网卡为例:

    1. 在DM9000网卡的中断函数里:以下显示了DM9000网卡中断里对于收包的函数调用,kernel最终调用了enqueue_to_backlog函数,该函数里有两个数据结构前面在介绍softnet_data结构体时有作注释,那便是input_pkt_queue和backlog,input_pkt_queue链表下挂接着中断里构建的skb,backlog便是kernel为了统一NAPI收包方式为不支持DMA的网卡所构建的napi_struct结构体实例。

      dm9000_interrupt
      	dm9000_rx
          	netif_rx
      			netif_rx_internal
      				enqueue_to_backlog
          
       static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
      			      unsigned int *qtail)
      {
      	struct softnet_data *sd;
      	unsigned long flags;
      	unsigned int qlen;
      
      	sd = &per_cpu(softnet_data, cpu);
      
      	local_irq_save(flags);
      
      	rps_lock(sd);
      	if (!netif_running(skb->dev))
      		goto drop;
      	qlen = skb_queue_len(&sd->input_pkt_queue);
      	if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
      		if (qlen) {
      enqueue:
      			__skb_queue_tail(&sd->input_pkt_queue, skb); //将skb挂接到input_pkt_queue链表中
      			input_queue_tail_incr_save(sd, qtail);
      			rps_unlock(sd);
      			local_irq_restore(flags);
      			return NET_RX_SUCCESS;
      		}
      
      		/* Schedule NAPI for backlog device
      		 * We can use non atomic operation since we own the queue lock
      		 */
      		if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
      			if (!rps_ipi_queued(sd))
      				____napi_schedule(sd, &sd->backlog);  //将backlog(NAPI)结构体挂接到本地cpu的poll_list链表下
      		}
      		goto enqueue;
      	}
      
      drop:
      	sd->dropped++;
      	rps_unlock(sd);
      
      	local_irq_restore(flags);
      
      	atomic_long_inc(&skb->dev->rx_dropped);
      	kfree_skb(skb);
      	return NET_RX_DROP;
      }
      
    2. 中断收到包了并且也将skb放入链表了,那接下来该如何处理呢,经过前面对NAPI收包方式的介绍,自然是在软中断里调用napi_struct结构体实例下注册的poll回调函数了,这里便是kernel实现的backlog下的poll函数,在net_dev_init函数里:

      static int __init net_dev_init(void)
      {
      	int i, rc = -ENOMEM;
      
      	BUG_ON(!dev_boot_phase);
      
      	if (dev_proc_init())
      		goto out;
      
      	if (netdev_kobject_init())
      		goto out;
      
      	INIT_LIST_HEAD(&ptype_all);
      	for (i = 0; i < PTYPE_HASH_SIZE; i++)
      		INIT_LIST_HEAD(&ptype_base[i]);
      
      	INIT_LIST_HEAD(&offload_base);
      
      	if (register_pernet_subsys(&netdev_net_ops))
      		goto out;
      
      	/*
      	 *	Initialise the packet receive queues.
      	 */
      
      	for_each_possible_cpu(i) {  //在这里完成对softnet_data结构体的初始化以及backlog实例的初始化
      		struct work_struct *flush = per_cpu_ptr(&flush_works, i);
      		struct softnet_data *sd = &per_cpu(softnet_data, i);
      
      		INIT_WORK(flush, flush_backlog);
      
      		skb_queue_head_init(&sd->input_pkt_queue);
      		skb_queue_head_init(&sd->process_queue);
      		INIT_LIST_HEAD(&sd->poll_list);
      		sd->output_queue_tailp = &sd->output_queue;
      #ifdef CONFIG_RPS
      		sd->csd.func = rps_trigger_softirq;
      		sd->csd.info = sd;
      		sd->cpu = i;
      #endif
      
      		sd->backlog.poll = process_backlog; //kernel实现的poll函数便是process_backlog
      		sd->backlog.weight = weight_p;
      	}
      
      	dev_boot_phase = 0;
      
      	/* The loopback device is special if any other network devices
      	 * is present in a network namespace the loopback device must
      	 * be present. Since we now dynamically allocate and free the
      	 * loopback device ensure this invariant is maintained by
      	 * keeping the loopback device as the first device on the
      	 * list of network devices.  Ensuring the loopback devices
      	 * is the first device that appears and the last network device
      	 * that disappears.
      	 */
      	if (register_pernet_device(&loopback_net_ops))
      		goto out;
      
      	if (register_pernet_device(&default_device_ops))
      		goto out;
      
      	open_softirq(NET_TX_SOFTIRQ, net_tx_action);
      	open_softirq(NET_RX_SOFTIRQ, net_rx_action);
      
      	hotcpu_notifier(dev_cpu_callback, 0);
      	dst_subsys_init();
      	rc = 0;
      out:
      	return rc;
      }
      
      
      
    3. 由前面的分析我们可知process_backlog函数会在软中断中得以执行,而该函数内会先将input_pkt_queue下的成员转移到process_queue链表下,然后在while里从process_queue链表里依次取出skb并通过__netif_receive_skb递交到网络子系统

      static int process_backlog(struct napi_struct *napi, int quota)
      {
      	struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
      	bool again = true;
      	int work = 0;
      
      	/* Check if we have pending ipi, its better to send them now,
      	 * not waiting net_rx_action() end.
      	 */
      	if (sd_has_rps_ipi_waiting(sd)) {
      		local_irq_disable();
      		net_rps_action_and_irq_enable(sd);
      	}
      
      	napi->weight = weight_p;
      	while (again) {
      		struct sk_buff *skb;
      
      		while ((skb = __skb_dequeue(&sd->process_queue))) {
      			rcu_read_lock();
      			__netif_receive_skb(skb);
      			rcu_read_unlock();
      			input_queue_head_incr(sd);
      			if (++work >= quota)
      				return work;
      
      		}
      
      		local_irq_disable();
      		rps_lock(sd);
      		if (skb_queue_empty(&sd->input_pkt_queue)) {
      			/*
      			 * Inline a custom version of __napi_complete().
      			 * only current cpu owns and manipulates this napi,
      			 * and NAPI_STATE_SCHED is the only possible flag set
      			 * on backlog.
      			 * We can use a plain write instead of clear_bit(),
      			 * and we dont need an smp_mb() memory barrier.
      			 */
      			napi->state = 0;
      			again = false;
      		} else {
      			skb_queue_splice_tail_init(&sd->input_pkt_queue,
      						   &sd->process_queue);
      		}
      		rps_unlock(sd);
      		local_irq_enable();
      	}
      
      	return work;
      }
      

    总结

    至此,介绍完了kernel下网卡收包的流程,并对两种类型网卡收包过程进行了函数调用分析。kernel为了操作系统的性能,在网络负载较大的情况下采用了轮询的方式进行收包(NAPI),每个网卡对应一个自己的napi_struct实例,对于支持DMA的网卡,该实例由driver提供,对于不支持DMA的网卡kernel为了统一NAPI收包方式实现了自己的backlog实例,并提供了自己的poll函数process_backlog。

    最后下面这张图对比了两种收包方式:
    两种网卡NAPI收包对比

    展开全文
  • DPDK 网卡收包流程

    千次阅读 多人点赞 2020-10-31 12:45:49
    Table of Contents ...2.2 dpdk 收包流程 1、构造收包队列 2.启动 3.收包 3、 PCIE 带宽调优 1)减少MMIO访问的频度。 (2)提高PCIe传输的效率。 (3)尽量避免Cache Line的部分写。 4、软..

    Table of Contents

    1、Linux网络收发包流程

    1.1 网卡与liuux驱动交互

    1.2  linux驱动与内核协议栈交互

    题外1: 中断处理逻辑

    题外2:中断的弊端

    2、linux发包流程

    2、DPDK 收发包流程

    2.1  网卡DMA描述符环形队列

    2.2 dpdk 收包流程

    1、构造收包队列

    2.启动

    3.收包

    3、 PCIE 带宽调优

    1)减少MMIO访问的频度。

    (2)提高PCIe传输的效率。

    (3)尽量避免Cache Line的部分写。

    4、软件调优

    1、dpdk的轮询模式收包

    参考链接:


    文章内容整理自https://mp.weixin.qq.com/s/FiNhgi316iiIEBFkbIbKHg

     

    1、Linux网络收发包流程

    1.1 网卡与liuux驱动交互

    NIC 在接收到数据包之后,首先需要将数据同步到内核中,这中间的桥梁是 rx ring buffer。它是由 NIC 和驱动程序共享的一片区域,事实上,rx ring buffer 存储的并不是实际的 packet 数据,而是一个描述符,这个描述符指向了它真正的存储地址,具体流程如下:

    • 1.驱动在内存中分配一片缓冲区用来接收数据包,叫做 sk_buffer
    • 2.将上述缓冲区的地址和大小(即接收描述符),加入到 rx ring buffer。描述符中的缓冲区地址是 DMA 使用的物理地址;
    • 3.驱动通知网卡有一个新的描述符;
    • 4.网卡从 rx ring buffer 中取出描述符,从而获知缓冲区的地址和大小;
    • 5.网卡收到新的数据包;
    • 6.网卡将新数据包通过 DMA 直接写到 sk_buffer 中。

    搞懂Linux零拷贝,DMAhttps://rtoax.blog.csdn.net/article/details/108825666

     

    1.2  linux驱动与内核协议栈交互

    当 NIC 把数据包通过 DMA 复制到内核缓冲区 sk_buffer 后,NIC 立即发起一个硬件中断。CPU 接收后,首先进入上半部分,网卡中断对应的中断处理程序是网卡驱动程序的一部分,之后由它发起软中断,进入下半部分,开始消费 sk_buffer 中的数据,交给内核协议栈处理。

    题外1: 中断处理逻辑

      Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部:

    • 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。特点快速执行;
    • 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。特点延迟执行;

    上半部分硬件中断会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU 编号”,比如说, 0 号 CPU 对应的软中断内核线程的名字就是 ksoftirqd/0

    通过下面命令可以查询软件中断线程:

     

    通过下面命令行可以查询软件中断或者硬件终端的运行情况。

    • /proc/softirqs 提供了软中断的运行情况;
    • /proc/interrupts 提供了硬中断的运行情况。

    Linux 硬中断和软中断

    《深入浅出DPDK》读书笔记(八):网卡性能优化(异步中断模式、轮询模式、混和中断轮询模式)

     

    题外2:中断的弊端

    中断,cpu能快速响应网卡的请求,但是大量数据包需要发送时,中断处理会降低cpu效率。

    为了解决这个问题,现在的内核及驱动都采用一种叫 NAPI(new API)的方式进行数据处理,其原理可以简单理解为 中断 + 轮询,在数据量大时,一次中断后通过轮询接收一定数量包再返回,避免产生多次中断。

    NAPI的精髓是在poll方式 / 纯中断方式之间自由灵活的游走切换。具体介绍下面文章查看。

    NAPI(New API)的一些浅见

    为什么系统调用会消耗较多资源?系统调用的三种方法:软件中断(分析过程)、SYSCALL指令、vDSO(虚拟动态链接对象linux-vdso.so.1)

     

    2、linux发包流程

    • 应用程序调用 Socket API(比如 sendmsg)发送网络包。由于这是一个系统调用,所以会陷入到内核态的套接字层中。套接字层会把数据包放到 Socket 发送缓冲区中。
    • 网络协议栈从 Socket 发送缓冲区中,取出数据包;再按照 TCP/IP 栈,从上到下逐层处理。比如,传输层和网络层,分别为其增加 TCP 头和 IP 头,执行路由查找确认下一跳的 IP,并按照 MTU 大小进行分片。
    • 分片后的网络包,再送到网络接口层,进行物理地址寻址,以找到下一跳的 MAC 地址。然后添加帧头和帧尾,放到发包队列中。这一切完成后,会有软中断通知驱动程序:发包队列中有新的网络帧需要发送。
    • 驱动程序通过 DMA ,从发包队列中读出网络帧,并通过物理网卡把它发送出去。

     

    2、DPDK 收发包流程

    2.1  网卡DMA描述符环形队列

    DMA(Direct Memory Access,直接存储器访问)是一种高速的数据传输方式,允许在外部设备和存储器之间直接读写数据。数据既不通过CPU,也不需要CPU干预。整个数据传输操作在DMA控制器的控制下进行。除了在数据传输开始和结束时做一点处理外,在传输过程中CPU可以进行其他的工作。

    网卡DMA控制器通过环形队列(网卡描述符队列)与CPU交互。环形队列由一组控制寄存器和一块物理上连续的缓存构成。主要的控制寄存器有Base、Size、Head和Tail。通过设置Base寄存器,可以将分配的一段物理连续的内存地址作为环形队列的起始地址,通告给DMA控制器。同样通过Size寄存器,可以通告该内存块的大小。Head寄存器往往对软件只读,它表示硬件当前访问的描述符单元。而Tail寄存器则由软件来填写更新,通知DMA控制器当前已准备好被硬件访问的描述符单元。

    以Intel® 82599网卡为例,一个描述符大小为16B,整个环形队列缓冲区的大小必须是网卡支持的最大Cache line(128B)的整数倍,所以描述符的总数是8的倍数。当然,环形队列的起始地址也需要对齐到最大Cacheline的大小。

    《深入浅出DPDK》读书笔记(二):网卡的读写数据操作

    搞懂Linux零拷贝,DMA

    性能优化点:

    1. 描述符环形队列的内存 必须是cacheline 对齐的,避免cache 一致性问题。
    2. IO带宽的效率,决定有多少数据包能进入cpu处理,应用层在PCIe TLP上的开销决定了有效的可利用带宽。宽带的瓶颈可能出现在PCIE总线上。

    lDMA操作可以利用Intel®处理器的Direct Data IO(DDIO)技术,利用LLC缓存,从而减少对内存的访问。

     

    2.2 dpdk 收包流程

    一个网络报文从网卡接收到被应用处理,中间主要需要经历两个阶段:

    • 阶段一:网卡通过其DMA硬件将收到的报文写入到收包队列中(入队)
    • 阶段二:应用从收包队列中读取报文(出队)。


    1、构造收包队列

    X710网卡由函数i40e_dev_rx_queue_setup完成收包函数的初始化。

    int i40e_dev_rx_queue_setup(struct rte_eth_dev *dev,
                                uint16_t queue_idx,
                                uint16_t nb_desc,
                                unsigned int socket_id,
                                const struct rte_eth_rxconf *rx_conf,
                                struct rte_mempool *mp);

    收包队列的结构体为i40e_rx_queue,该结构体里包含两个重要的环形队列rx_ring和sw_ring,两个都是动态申请的连续数组环形队列,两者大小一直,互相对应的。

    • rx_ring和sw_ring的关系可以简单如下认为。
    • rx_ring主要存储报文数据的物理地址,物理地址供网卡DMA使用,也称为DMA地址(硬件使用物理地址,将报文copy到报文物理位置上)。

    sw_ring主要存储报文数据的虚拟地址,虚拟地址供应用使用(软件使用虚拟地址,读取报文)。

    /* * Structure associated with each RX queue. */ 
    struct i40e_rx_queue {
        struct rte_mempool *mp; /**< mbuf pool to populate RX ring */
        volatile union i40e_rx_desc *rx_ring;/**< RX ring virtual address */
        uint64_t rx_ring_phys_addr; /**< RX ring DMA address */
        struct i40e_rx_entry *sw_ring; /**< address of RX soft ring */
        uint16_t nb_rx_desc; /**< number of RX descriptors */
        uint16_t rx_free_thresh; /**< max free RX desc to hold */
        uint16_t rx_tail; /**< current value of tail */
        uint16_t nb_rx_hold; /**< number of held free RX desc */
        .... 
    }

    rx_ring 描述符队列

    • pkt_addr:报文数据的物理地址,网卡DMA将报文数据通过该物理地址写入对应的内存空间。
    • hdr_addr:报文的头信息,hdr_addr的最后一个bit为DD位,因为是union结构,即status_error的最后一个bit也对应DD位。
    • DD位(Descriptor Done Status)用于标志标识一个描述符buf是否可用。

    网卡每次来了新的数据包,就检查rx_ring当前这个buf的DD位是否为0,如果为0那么表示当前buf可以使用,就让DMA将数据包copy到这个buf中,然后设置DD为1。如果为1,那么网卡就认为rx_ring队列满了,直接会将这个包给丢弃掉,记录一次imiss。

    对于应用而言,DD位使用恰恰相反,在读取数据包时,先检查DD位是否为1,如果为1,表示网卡已经把数据包放到了内存中,可以读取,读取完后,再放入一个新的buf并把对应DD位设置为0。如果为0,就表示没有数据包可读。

    union i40e_16byte_rx_desc {
        struct {
            __le64 pkt_addr; /* Packet buffer address */
            __le64 hdr_addr; /* Header buffer address */
        } read;
        struct {
            struct {
                struct {
                    union {
                        __le16 mirroring_status;
                        __le16 fcoe_ctx_id;
                    } mirr_fcoe;
                    __le16 l2tag1;
                } lo_dword;
    
                union {
                    __le32 rss; /* RSS Hash */
                    __le32 fd_id; /* Flow director filter id */
                    __le32 fcoe_param; /* FCoE DDP Context id */
                } hi_dword;
            } qword0;
            struct {
                /* ext status/error/pktype/length */
                __le64 status_error_len;
            } qword1;
        } wb;  /* writeback */ 
    };

    SW_RING

    mbuf:报文mbuf结构指针,mbuf用于管理一个报文,主要包含报文相关信息和报文数据。

    /*sw ring */ 
    struct i40e_rx_entry {
     struct rte_mbuf *mbuf; 
    };

    2.启动

    
    int
    rte_eth_dev_start(uint16_t port_id)
    {
    	struct rte_eth_dev *dev;
    	struct rte_eth_dev_info dev_info;
    	int diag;
    	int ret;
    
    	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL);
    
    	dev = &rte_eth_devices[port_id];
    
    	RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->dev_start, -ENOTSUP);
    
    	if (dev->data->dev_started != 0) {
    		RTE_ETHDEV_LOG(INFO,
    			"Device with port_id=%"PRIu16" already started\n",
    			port_id);
    		return 0;
    	}
    
    	ret = rte_eth_dev_info_get(port_id, &dev_info);
    	if (ret != 0)
    		return ret;
    
    	/* Lets restore MAC now if device does not support live change */
    	if (*dev_info.dev_flags & RTE_ETH_DEV_NOLIVE_MAC_ADDR)
    		rte_eth_dev_mac_restore(dev, &dev_info);
    
    	diag = (*dev->dev_ops->dev_start)(dev);
    	if (diag == 0)
    		dev->data->dev_started = 1;
    	else
    		return eth_err(port_id, diag);
    
    	ret = rte_eth_dev_config_restore(dev, &dev_info, port_id);
    	if (ret != 0) {
    		RTE_ETHDEV_LOG(ERR,
    			"Error during restoring configuration for device (port %u): %s\n",
    			port_id, rte_strerror(-ret));
    		rte_eth_dev_stop(port_id);
    		return ret;
    	}
    
    	if (dev->data->dev_conf.intr_conf.lsc == 0) {
    		RTE_FUNC_PTR_OR_ERR_RET(*dev->dev_ops->link_update, -ENOTSUP);
    		(*dev->dev_ops->link_update)(dev, 0);
    	}
    
    	rte_ethdev_trace_start(port_id);
    	return 0;
    }

    收包队列的启动主要是通过调用rte_eth_dev_start(dpdk rte_ethdev.h)函数完成,收包队列初始化的核心流程如下。

    static int __rte_cold
    ixgbe_alloc_rx_queue_mbufs(struct ixgbe_rx_queue *rxq)
    {
    	struct ixgbe_rx_entry *rxe = rxq->sw_ring;
    	uint64_t dma_addr;
    	unsigned int i;
    
    	/* Initialize software ring entries */
    	for (i = 0; i < rxq->nb_rx_desc; i++) {
    		volatile union ixgbe_adv_rx_desc *rxd;
    		struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool);
    
    		if (mbuf == NULL) {
    			PMD_INIT_LOG(ERR, "RX mbuf alloc failed queue_id=%u",
    				     (unsigned) rxq->queue_id);
    			return -ENOMEM;
    		}
    
    		mbuf->data_off = RTE_PKTMBUF_HEADROOM;
    		mbuf->port = rxq->port_id;
    
    		dma_addr =
    			rte_cpu_to_le_64(rte_mbuf_data_iova_default(mbuf));
    		rxd = &rxq->rx_ring[i];
    		rxd->read.hdr_addr = 0;
    		rxd->read.pkt_addr = dma_addr;
    		rxe[i].mbuf = mbuf;
    	}
    
    	return 0;
    }

    循环从mbuf pool中申请mbuf,从mbuf中得到报文数据对应的物理地址,物理地址存入rx_ring中,mbuf指针存入sw_ring中。其中通过rxd->read.hdr_addr = 0,完成了DD位设置为0。

    一切ok后,就可以开始收包了。

     

    3.收包

    收包由网卡入队和应用出队两个操作完成。

    3.1入队

    入队的操作是由网卡DMA来完成的,DMA(Direct Memory Access,直接存储器访问《搞懂Linux零拷贝,DMA》)是系统和网卡(外设)打交道的一种方式,该种方式允许在网卡(外部设备)和系统内存之间直接读写数据,这样能有效减轻CPU的工作。

    网卡收到报文后,先存于网卡本地的buffer-Rx(Rx FIFO)中,然后由DMA通过PCI总线将报文数据写入操作系统的内存中,即数据报文完成入队操作。(PS:PCIe总线可能成为网卡带宽的瓶颈)

     

    3.2.出队

    static inline uint16_t
    rte_eth_rx_burst(uint16_t port_id, uint16_t queue_id,
    		 struct rte_mbuf **rx_pkts, const uint16_t nb_pkts)

    应用调用rte_eth_rx_burst(dpdk rte_ethdev.h)函数开始批量收包,最大收包数量由参数nb_pkts决定(比如设置为64)。其核心流程由ixgbe_recv_pkts(dpdk ixgbe_rxtx.c)实现,从收包队列rx_tail位置开始收,循环读取一个报文、填空一个报文(空报文数据),读取64个后,重新标记rx_tail的位置,完成出队操作,将收取的报文作返回供应用处理。代码简化如下。

    struct rte_mbuf *rxm; 
    //从队列的tail位置开始取包 
    rx_id = rxq->rx_tail; 
    //循环获取nb_pkts个包 
    while (nb_rx < nb_pkts) { 
        ...... 
        rxdp = &rx_ring[rx_id]; 
        //检查DD位是否为1,是1则说明该位置已放入数据包,否则表示没有报文,退出 
        staterr=rxdp->wb.upper.status_error; 
        if(!(staterr&rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD))) 
            break; 
        rxd = *rxdp; 
        //申请一个mbuf(nmb),用于交换 
        nmb = rte_mbuf_raw_alloc(rxq->mb_pool); 
        //从sw_ring中读取一个报文mbuf(存入rxm) 
        rxe = &sw_ring[rx_id]; 
        rxm = rxe->mbuf; 
        //往sw_ring中填空一个新报文mbuf(nmb) 
        rxe->mbuf = nmb; 
        //新mbuf对应的报文数据物理地址填入rx_ring对应位置,并将hdr_addr置0(DD位置0) 
        dma_addr =rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb)); 
        rxdp->read.hdr_addr = 0; 
        rxdp->read.pkt_addr = dma_addr; 
        //对读取mbuf的报文信息进行初始化 
        rxm->pkt_len = pkt_len; 
        rxm->data_len = pkt_len; 
        rxm->port = rxq->port_id; 
        ...... 
        //读取的报文mbuf存入rx_pkts 
        rx_pkts[nb_rx++] = rxm; 
    } 
    //重新标记rx_tail位置 
    rxq->rx_tail = rx_id;

    3、 PCIE 带宽调优

    1)减少MMIO访问的频度。

    高频度的寄存器MMIO访问,往往是性能的杀手。接收包时,尾寄存器(tail register)的更新发生在新缓冲区分配以及描述符重填之后。只要将每包分配并重填描述符的行为修改为滞后的批量分配并重填描述符,接收侧的尾寄存器更新次数将大大减少。DPDK是在判断空置率小于一定值后才触发重填来完成这个操作的。发送包时,就不能采用类似的方法。因为只有及时地更新尾寄存器,才会通知网卡进行发包。但仍可以采用批量发包接口的方式,填充一批等待发送的描述符后,统一更新尾寄存器。

    (2)提高PCIe传输的效率。

    每个描述符的大小是固定的,例如16Byte。每次读描述符或者写描述符都触发一次PCIe事务,显然净荷小,利用率低。如果能把4个操作合并成整Cache Line大小来作为PCIe的事务请求(PCIe净荷为64Byte),带宽利用率就能得到提升。另外,在发送方向,发送完成后回写状态到描述符。避免每次发送完成就写回,使用批量写回方式(例如,网卡中的RS bit),可以用一次PCIe的事务来完成批量(例如,32个为一组)的完成确认。

    (3)尽量避免Cache Line的部分写。

    DMA引擎在写数据到缓冲区的过程中,如果缓冲区地址并不是Cache Line对齐或者写入的长度不是整个Cache Line,就会发生Cache Line的部分写。Cache Line的部分写会引发内存访问read-modify-write的合并操作,增加额外的读操作,也会降低整体性能。所以,DPDK在Mempool中分配buffer的时候,会要求对齐到Cache Line大小。

     

    4、软件调优

    1、dpdk的轮询模式收包

    DPDK PMD( Poll Mode Driver)轮询模式驱动程序

    DPDK纯轮询模式是指收发包完全不使用中断处理的高吞吐率的方式;物理端口上的每一个收包队列,都会有一个对应的由收包描述符组成的软件队列来进行硬件和软件的交互,以达到收包的目的。

    每一个收包队列,DPDK都会有一个对应的软件线程负责轮询里面的收包描述符的收包成功的标志。一旦发现某一个收包描述符的收包成功标志被硬件置位了,就意味着有一个包已经进入到网卡,并且网卡已经存储到描述符对应的缓冲内存块里面,这时候驱动程序会解析相应的收包描述符,提取各种有用的信息,然后填充对应的缓冲内存块头部。然后把收包缓冲内存块存放到收包函数提供的数组里面,同时分配好一个新的缓冲内存块给这个描述符,以便下一次收包。

    每一个发包队列,DPDK都会有一个对应的软件线程负责设置需要发送出去的包,DPDK的驱动程序负责提取发包缓冲内存块的有效信息,例如包长、地址、校验和信息、VLAN配置信息等。DPDK的轮询驱动程序根据内存缓存块中的包的内容来负责初始化好每一个发包描述符,驱动程序会把每个包翻译成为一个或者多个发包描述符里能够理解的内容,然后写入发包描述符。其中最关键的有两个,一个就是标识完整的包结束的标志EOP (End Of Packet),另外一个就是请求报告发送状态RS (Report Status)。由于一个包可能存放在一个或者多个内存缓冲块里面,需要一个或者多个发包描述符来表示一个等待发送的包,EOP就是驱动程序用来通知网卡硬件一个完整的包结束的标志。每当驱动程序设置好相应的发包描述符,硬件就可以开始根据发包描述符的内容来发包,那么驱动程序可能会需要知道什么时候发包完成,然后回收占用的发包描述符和内存缓冲块。基于效率和性能上的考虑,驱动程序可能不需要每一个发包描述符都报告发送结果,RS就是用来由驱动程序来告诉网卡硬件什么时候需要报告发送结果的一个标志。

     

    参考链接:

    ethtool 原理介绍和解决网卡丢包排查思路

    DPDK之网卡收包流程

    DPDK 网卡收包流程

    FD.io VPP:探究分段场景下vlib_buf在收发包的处理

    《深入浅出DPDK》读书笔记(二):网卡的读写数据操作

    搞懂Linux零拷贝,DMA

    《深入浅出DPDK》全书在线阅读(附录+推荐阅读)

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

    展开全文
  • Linux网卡接收数据包过程图详细总结

    千次阅读 2022-03-27 17:02:00
    收包过程总览 从TCP/IP网络分层模型中可以清楚当数据帧从网卡(物理层)接收到客户端(应用层)收到数据包的整个过程。 通过网卡进行网络数据接收一般要经历下面两个过程: 1、接收数据前的准备工作 1)网络子...

    简介

    本文对代码的详细实现过程不做过多的讲解,重点让读者熟悉数据的接收过程,如需进一步熟悉源码,可根据下面的链接做进一步学习:
    1、网卡驱动源码分析
    2、网卡结构和基础知识详解

    收包过程总览

    从TCP/IP网络分层模型中可以清楚当数据帧从网卡(物理层)接收到客户端(应用层)收到数据包的整个过程。
    在这里插入图片描述

    通过网卡进行网络数据接收一般要经历下面两个过程:
    1、接收数据前的准备工作

    		1)网络子系统的初始化;
    		2)协议栈的注册;
    		3)网卡驱动的初始化;
    		4)启动网卡;
    

    2、接收和传输网络数据:

    一、接收数据前的准备工作

    1、网络子系统的初始化:
    linux系统在加载内核到内存中后会进行大量的初始化工作,其中初始化各个内核子系统都是通过调用subsys_initcall函数来实现,具体初始化网络子系统主要有以下三个方面:
    1、申请一个softnet_data数据结构,在这个数据结构里的poll_list是等待驱动程序将其poll函数注册进来;
    2、将每一种软中断都注册一个处理函数;
    3、将软中断和对应的中断处理函数都放在softirq_vec数组中。
    具体流程如下图:
    在这里插入图片描述
    2、协议栈的注册:
    内核实现了网络层的ip协议,也实现了传输层的tcp协议和udp协议。 这些协议对应的实现函数分别是ip_rcv(),tcp_v4_rcv()和upd_rcv()。和我们平时写代码的方式不一样的是,内核是通过注册的方式来实现的,这些函数主要是将从物理层ring_buffer写到skb_buffer中的数据做进一步的解析处理并传至上一层;
    在这里插入图片描述
    3、网卡驱动的初始化:
    每一个驱动程序(不仅仅只是网卡驱动)会使用 module_init 向内核注册一个初始化函数,当驱动被加载时,内核会调用这个函数去完成驱动的注册、网卡设备的初始化和网卡驱动和设备的匹配等过程。
    在这里插入图片描述
    4、启动网卡:

    当完成前面的注册和初始化过程后,网卡就可以通过命令ifconfig eth0 up进行打开,网卡的打开过程就是通过调用驱动注册的open函数去实现的,igb_open函数执行过程中,还会启用msix机制和NAPI机制,其中msix机制可以具体理解为中断管理机制,通过该函数调用最终的中断处理函数,而NAPI机制是与最开始的软中断调用的poll函数相关的,具体流程如下图:
    在这里插入图片描述

    二、接收和传输网络数据

    整体过程梳理:
    首先网卡接收网络数据帧并通过DMA保存在ring_buffer中,此时网卡通过硬中断通知cpu接收到数据,cpu会生成一个软中断,然后通过创建一个ksoftirqd内核线程处理软中断,然后通过NAPI机制调用驱动注册的poll函数,poll函数会将ring_buffer中的数据保存在skb_buffer中然后调用netif_receive_skb()将数据发送到内核协议栈进行处理,也就会调用最开始协议栈注册的函数。
    具体流程如下图:
    在这里插入图片描述
    下面具体解析一个硬中断和ksoftirqd内核线程处理软中断:
    硬中断:
    首先当数据帧从网线到达网卡上的时候,第一站是网卡的接收队列。网卡在分配给自己的RingBuffer中寻找可用的内存位置,找到后DMA引擎会把数据DMA到网卡之前关联的内存里,这个时候CPU都是无感的。当DMA操作完成以后,网卡会像CPU发起一个硬中断,通知CPU有数据到达,具体流程如下图:
    在这里插入图片描述
    ksoftirqd内核线程处理软中断:
    网络子系统初始化部分, 我们看到我们为NET_RX_SOFTIRQ注册了处理函数net_rx_action,函数就是在这里被调用执行的。
    注意:
    硬中断中设置软中断标记,和ksoftirq的判断是否有软中断到达,都是基于smp_processor_id()的。这意味着只要硬中断在哪个CPU上被响应,那么软中断也是在这个CPU上处理的。所以说,如果你发现你的Linux软中断CPU消耗都集中在一个核上的话,做法是要把调整硬中断的CPU亲和性,来将硬中断打散到不通的CPU核上去。
    在这里插入图片描述最后讲述协议栈的处理过程:
    在ksoftirqd内核线程处理软中断最终调用的netif_receive_skb函数会根据包的协议,假如是upd包,会将包依次送到ip_rcv(),upd_rcv()协议处理函数中进行处理,最终将数据包传至传输层,函数处理流程如下:
    在这里插入图片描述

    总结

    接收数据前准备工作:
    1、创建ksoftirqd线程,为它设置好它自己的线程函数,后面就指望着它来处理软中断;
    2、协议栈注册,linux要实现许多协议,比如arp,icmp,ip,udp,tcp,每一个协议都会将自己的处理函数注册一下,方便包来了迅速找到对应的处理函数;
    3、网卡驱动初始化,每个驱动都有一个初始化函数,内核会让驱动也初始化一下。在这个初始化过程中,把自己的DMA准备好,把NAPI的poll函数地址告诉内核;
    4、启动网卡,分配RX,TX队列,注册中断对应的处理函数;
    以上是内核准备收包之前的重要工作,当上面都ready之后,就可以打开硬中断,等待数据包的到来了。

    数据接收:
    1、网卡将数据帧DMA到内存的RingBuffer中,然后向CPU发起中断通知;
    2、CPU响应中断请求,调用网卡启动时注册的中断处理函数;
    3、中断处理函数发起软中断请求;
    4、内核线程ksoftirqd线程发现有软中断请求到来,先关闭硬中断;
    5、ksoftirqd线程开始调用驱动的poll函数收包;
    5、poll函数将受到的包送到协议栈注册的ip_rcv函数中;
    6、ip_rcv函数再讲包送到upd_rcv函数中(对于tcp包就送到tcp_rcv)。

    读者有兴趣可以通过下面的链接对网卡其他的知识进行学习:
    1、网卡驱动源码分析
    2、网卡结构和基础知识详解

    展开全文
  • 介绍网卡的接受和发送过程,对网卡驱动编写提供参考
  • (1) 支持NAPI的网卡驱动必须提供轮询方法poll()。 (2) 非NAPI的内核接口为netif_rx(),NAPI的内核接口为napi_schedule()。 (3) 非NAPI使用共享的CPU队列softnet_data->input_pkt_queue,NAPI使用设备内存(或者...
  • 网卡收发包流程

    2019-07-21 21:46:55
    今天在进行发包部分调优的时候,发现对于从最开始调用系统函数,到最终的网卡发包过程不是很理解,就去特意学习了一下。 先是看了那本网络模块的书,但是因为他的内容属于比较细节的,基本上算是看不懂把。 这样的话...
  • linux 网络之数据包的接受过程

    千次阅读 2021-05-26 19:20:37
    目录 ...网卡收包从整体上是网线中的高低电平转换到网卡FIFO存储再拷贝到系统主内存(DDR3)的过程,其中涉及到网卡控制器,CPU,DMA,驱动程序,在OSI模型中属于物理层和链路层,如下图所示。 ...
  • 网卡驱动收发过程图解

    千次阅读 2021-12-22 19:19:06
    网卡 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片、Tx/Rx FIFO、DMA等组成,其中网线通过变压器接PHY芯片、PHY芯片通过MII接MAC芯片、MAC芯片接PCI总线 PHY芯片主要负责:CSMA/CD、模数转换、编解码、串并...
  • 每个网卡(MAC)都有自己的专用DMA Engine,如上图的 TSEC 和 e1000 网卡intel82546。 上图中的红色线就是以太网数据流,DMA与DDR打交道需要其他模块的协助,如TSEC,PCI controller 以太网数据在TSEC<-->DDR ...
  • Linux网络发包流程

    2021-05-09 08:29:26
    前段时间写了Linux网络收包流程一文,没想到很多人感兴趣,现上货网络发包流程。 sys_write()file->f_op->write()(sock_writev)(注0)sock_writev()sock_sendmsg()sock->ops->sendmsg()(inet_sendmsg)...
  • 文章目录目录数据包从物理网卡进入虚拟机的流程物理网卡处理如何将网卡收到的数据写入到内核内存?中断下半部分软中断处理将数据包交给内核态的 OvS Bridge(Datapath)处理Linux 虚拟 “网线”(veth)的工作原理...
  • 网卡接收数据流程

    千次阅读 2017-06-23 13:49:19
    4.网卡接收数据流程 (物理层)网卡接收来自其他节点的数据帧 (链路层)根据帧头信息获取数据报 (网络层)路由过滤,判断是否应该丢弃该数据报,并根据数据报头信息获取TCP/UDP数据段 (运输层)根据...
  • 网卡驱动:stmmac DMA接收流程

    千次阅读 2020-04-06 17:39:04
    1. dmabuffer及zero-copy ...在打开网卡时,stmmac_init_rx_buffers()函数负责分配dmabuffer。 static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct dma_desc *p, int i, gfp_t flags, u32 qu...
  • 原文链接:https://blog.csdn.net/jackywgw/article/details/78321226
  • 网卡适配器收发数据帧流程

    千次阅读 2021-08-20 11:04:40
    网络设配器的收包流程 传统方式和 NAPI 方式 中断方式与轮询方式 网络设配器的发包过程 sk_buff(Socket Buffer) DMA 与 Buffer descriptor 参考文档 前文列表 《Linux 内核网络协议栈》 网卡的工作...
  • linux 网络收包NAPI理解

    2021-12-24 21:18:55
    遍历list链表,调用驱动中注册的poll函数收包 2.1 收包需满足weight不能超过配置的64,时间不能超过2ms 2.2 如果收包低于64,并且时间不超过2ms就没有包的,将list加入到struct softnet_data->list队尾 2.3 如果...
  • 本文将介绍Linux系统中,基于RTL8139网卡驱动程序,是如何一步一步将接收到的数据包传送到内核的网络协议栈的。 下图展示了数据包(packet)如何进入内存,并被内核的网络模块开始处理: +-----+ | | Memroy +...
  • ethtool查看网卡统计信息的流程

    千次阅读 2022-04-19 15:28:59
    使用 ethtool 工具可以查看网卡硬件的统计信息,比如,收包数、发包数、单播数、多播数等等。具体操作如下: $ ethtool -S enp0s3f0 NIC statistics: mmc_tx_octetcount_gb: 224841119 mmc_tx_framecount_gb: ...
  • atheros无线网卡数据包接收流程

    千次阅读 2015-08-03 23:03:50
    本文主要分析atheros无线网卡数据包接收的流程,主要分析函数调用框架 以atheros的UMAC_SUPPORT_APONLY编译选项的开和关两种模式来分析 1. APONLY模式情况下: 从中断开始: ath_isr do_ath_isr ath_isr_...
  • linux内核网络收包
  • Linux网卡基本结构和传输流程总结

    千次阅读 2022-03-27 13:37:08
    本文介绍网卡的结构和基本的网卡传输流程网卡的结构 网络设备主要分为 PHY、MAC 和 DMA 三个硬件模块。以太网卡中数据链路层的芯片一般简称之为MAC控制器,物理层的芯片我们简称之为PHY,同时通过DMA进行数据的...
  • 网卡驱动收包

    千次阅读 2014-11-21 07:20:40
    NAPI方式处理数据包接收(中断与轮询配合,由硬件中断触发接收处理,之后关闭网卡硬中断,当...//e100网卡驱动中,在如下流程中设置中断回调 //netdev->open (当应用层执行ifup时,调用了设备回调netdev->open) //e1
  • struct rte_eth_dev 有四个重要的成员: rx_pkt_burst:网卡的 burst 收包函数; tx_pkt_burst:网卡的 burst 发包函数; dev_ops:网卡驱动注册函数表,类型为 struct eth_dev_ops; data:包含了网卡的主要信息,...
  • DPDK收发包流程分析(一)

    千次阅读 多人点赞 2020-10-13 18:09:43
    前言:DPDK是intel工程师开发的一款用来快速处理数据包的框架,最初的目的是为了证明传统网络数据包处理性能低不是intel处理器导致的,而是传统数据的处理流程导致。本篇文章主要介绍DPDK接收与发送报文的流程。 ...
  • 当一个网络帧到达网卡后,网卡会通过 DMA 方式,把这个网络包放到收包队列中;然后通过硬中断,告诉中断处理程序已经收到了网络包。 接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk...
  • 在开发模块过程中,遇到一个问题:在NF_INET_LOCAL_IN钩子处截获数据包后,如果操作失败,还要把这些截获的数据包重新传递到TCP层处理。...为了解决这个问题,决定将数据包的接收过程从驱动程序到TCP层的处理流程梳理了
  • 对Linux内核网络栈的收包和发包完整流程进行梳理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,279
精华内容 12,511
关键字:

网卡收包流程