本文主要内容:下半部的实现,分析数据包从上半部结束后到L3的处理过程。

内核版本:2.6.37

Author:zhangskd @ csdn blog

下半部的实现

接收数据包的下半部处理流程为:

net_rx_action // 软中断

|--> process_backlog() // 默认poll

|--> __netif_receive_skb() // L2处理函数

|--> ip_rcv() // L3入口

net_rx_action

软中断(NET_RX_SOFTIRQ)的处理函数net_rx_action()主要做了:

遍历sd->poll_list,对于每个处于轮询状态的设备,调用它的poll()函数来处理数据包。

如果设备NAPI被禁止了,则把设备从sd->poll_list上删除,否则把设备移动到sd->poll_list的队尾。

每次软中断最多允许处理netdev_budget(300)个数据包,最长运行时间为2jiffies(2ms)。

每个设备一次最多允许处理weight_p(64)个数据包(非NAPI)。

如果在这次软中断中没处理玩,则再次设置NET_RX_SOFTIRQ标志触发软中断。

static void net_rx_action(struct softirq_action *h)
{
struct softnet_data *sd = &__get_cpu_var(softnet_data); /* 当前CPU的softnet_data实例 */
unsigned long time_limit = jiffies + 2; /* 一次软中断的最长处理时间(2ms) */
int budget = netdev_budget; /* 一次软中断最多能够处理的skb个数(300) */
void *have;
local_irq_disable(); /* 禁止本地中断 */
/* 如果有处于轮询状态的设备 */
while(! list_empty(&sd->poll_list)) {
struct napi_struct *n;
int work, weight;
/* If softirq window is exhuasted 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(jiffies, time_limit)))
goto softnet_break;
local_irq_enable(); /* 开启本地中断 */
/* Even though interrupts have been re-enabled, this access is safe because
* interrupts can only add new entries to the tail of this list, and only ->poll()
* calls can remove this head entry from the list.
*/
/* 获取链表上的第一个napi_struct实例 */
n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
have = netpoll_poll_lock(n);
weight = n->weight; /* 这个设备每次能poll的数据包上限 */
/* This NAPI_STATE_SCHED test is for avoiding a race with netpoll's
* poll_napi(). Only the entity which obtains the lock and sees NAPI_STATE_SCHED
* set will actually make the ->poll() call. Therefore we avoid accidently calling ->poll()
* when NAPI is not scheduled.
*/
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
/* 调用napi_struct的poll方法,返回处理的数据包个数 */
work = n->poll(n, weight); /* 默认为process_backlog() */
trace_napi_poll(n);
}
WARN_ON_ONCE(work > weight);
budget -= work; /* 总预算减去本次处理的数据包数 */
local_irq_disable(); /* 禁止本地中断 */
if (unlikely(work == weight)) {
/* 如果NAPI被禁止了,则把当前napi_struct从poll_list中删除 */
if (unlikely(napi_disable_pending(n))) {
local_irq_enable();
napi_complete(n);
local_irq_disable();
} else
/* 把当前napi_struct移动到poll_list的队尾 */
list_move_tail(&n->poll_list, &sd->poll_list);
}
netpoll_poll_unlock(have);
}
out:
net_rps_action_and_irq_enable(sd); /* 开启本地中断 */
#ifdef CONFIG_NET_DMA
...
#endif
return;
softnet_break:
sd->time_squeeze++; /* 跑满2ms,或处理了300个包 */
__raise_softirq_irqoff(NET_RX_SOFTIRQ); /* 因为没处理完,再次触发软中断 */
goto out;
}

当调用napi_struct的poll()来处理数据包时,本地中断是开启的,这意味着新的数据包可以继续添加到

输入队列中。

process_backlog

如果网卡驱动不支持NAPI,则默认的napi_struct->poll()函数为process_backlog()。

process_backlog()的主要工作:

1. 处理sd->process_queue中的数据包

分别取出每个skb,从队列中删除。

开本地中断,调用__netif_rx_skb()把skb从L2传递到L3,然后关本地中断。

这说明在处理skb时,是允许网卡中断把数据包添加到接收队列(sd->input_pkt_queue)中的。

2. 如果处理完sd->process_queue中的数据包了,quota还没用完

把接收队列添加到sd->process_queue处理队列的尾部后,初始化接收队列。

接下来会继续处理sd->process_queue中的数据包。

3. 如果本次能处理完sd->process_queue和sd->input_pkt_queue中的所有数据包

把napi_struct从sd->poll_list队列中删除掉,清除NAPI_STATE_SCHED标志。

static int process_backlog(struct napi_struct *napi, int quota)
{
int work = 0;
struct softnet_data *sd = container_of(napi, struct softnet_data, backlog);
#ifdef CONFIG_RPS
...
#endif
napi->weight = weight_p; /* 每次处理的最大数据包数,默认为64 */
local_irq_disable(); /* 禁止本地中断 */
while(work < quota) { /* 配额允许时 */
struct sk_buff *skb;
unsigned int qlen;
/* 从sd->process_queue队列取出第一个skb,并把它从队列中删除。
* sd->process_queue用于存储即将处理的数据包。
*/
while((skb = __skb_dequeue(&sd->process_queue))) {
local_irq_enable(); /* 开启本地中断 */
__netif_receive_skb(skb); /* 进行二层处理后转发给网络层 */
local_irq_disable();
input_queue_head_incr(sd);
if (++work >= quota) { /* 处理的数据包个数超过上限了,返回 */
local_irq_enable();
return work;
}
}
rps_lock(sd);
qlen = skb_queue_len(&sd->input_pkt_queue); /* 接收队列的长度 */
/* 把接收队列添加到sd->process_queue的尾部,然后初始化接收队列 */
if (qlen)
skb_queue_splice_tail_init(&sd->input_pkt_queue, &sd->process_queue);
/* 如果能在本次处理完接收队列的数据包 */
if (qlen < quota - work) {
/* 把napi_struct从sd->poll_list队列中删除,因为马上要全部处理完了 */
list_del(&napi->poll_list);
napi->state = 0; /* 清除掉NAPI_STATE_SCHED标志 */
quota = work + qlen; /* 减小quota,使接下来处理完process_queue的qlen个包即退出 */
}
rps_unlock(sd);
}
local_irq_enable();
return work;
}

从sk_buff_head队列中取出第一个skb,并把它从队列中删除。

/**
* __skb_dequeue - remove from the head of the queue
* @list: list to dequeue from
* Remove the head of the list.
* The head item is returned or %NULL if the list is empty.
*/
static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list)
{
struct sk_buff *skb = skb_peek(list); /* 取出队列的第一个元素 */
if (skb)
__skb_unlink(skb, list); /* 把skb从sk_buff_head队列中删除 */
return skb;
}

把list添加到head的队尾,然后把list重新初始化。

/**
* skb_queue_splice_tail - join two skb lists and reinitialise the emptied list
* @list: the new list to add
* @head: the place to add it in the first list
* Each of the lists is a queue.
* The list at @list is reinitialised
*/
static inline void skb_queue_splice_tail_init(struct sk_buff_head *list, struct sk_buff_head *head)
{
if (! skb_queue_empty(list)) {
__skb_queue_splice(list, head->prev, (struct sk_buff *)head);
head->qlen += list->qlen;
__skb_queue_head_init(list);
}
}

__netif_receive_skb

__netif_receive_skb()的主要工作为:

处理NETPOLL、网卡绑定、入口流量控制、桥接、VLAN。

遍历嗅探器(ETH_P_ALL)链表ptype_all。对于每个注册的sniffer,调用它的处理函数

packet_type->func(),例如tcpdump。

赋值skb->network_header,根据skb->protocol从三层协议哈希表ptype_base中找到对应的

三层协议。如果三层协议是ETH_P_IP,相应的packet_type为ip_packet_type, 协议处理函数为ip_rcv()。

static int __netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
rx_handler_func_t *rx_handler;
struct net_device *orig_dev;
struct net_device *master;
struct net_device *null_or_orig;
struct net_device *orig_or_bond;
int ret = NET_RX_DROP;
__be16 type;
if (! netdev_tstamp_prequeue)
net_timestamp_check(skb); /* 记录接收时间到skb->tstamp */
trace_netif_receive_skb(skb);
/* If we've gotten here through NAPI, check netpoll */
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
if (! skb->skb_iif)
skb->skb_iif = skb->dev->ifinex; /* 记录设备编号 */
/* 处理网卡绑定(bonding) */
null_or_orig = NULL;
orig_dev = skb->dev;
master = ACCESS_ONCE(orig_dev->master);
if (skb->deliver_no_wcard)
null_or_orig = orig_dev;
else if (master) {
if (skb_bond_should_drop(skb, master)) {
skb->deliver_no_wcard = 1;
null_or_orig = orig_dev; /* deliver only exact match */
} else
skb->dev = master;
}
__this_cpu_inc(softnet_data.processed); /* 增加本cpu处理过的数据包个数 */
skb_reset_network_header(skb); /* 赋值skb->network_header */
skb_reset_network_header(skb); /* 赋值skb->transport_header */
skb->mac_len = skb->network_header - skb->mac_header; /* MAC头的长度,一般为14 */
pt_prev = NULL;
rcu_read_lock();
/* 入口流量控制 */
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
/* 遍历嗅探器(ETH_P_ALL)链表ptype_all。对于每个注册的sniffer,
* 调用它的处理函数packet_type->func(),例如tcpdump。
*/
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev); /* 嗅探器的处理函数 */
pt_prev = ptype;
}
}
#ifdef CONFIG_NET_CLS_ACT
skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
if (! skb)
goto out;
ncls:
#endif
/* Handle special case of bridge or macvlan,接收的特殊过程 */
rx_handler = rcu_dereference(skb->dev->rx_handler);
if (rx_handler) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
skb = rx_handler(skb);
if (! skb)
goto out;
}
/* VLAN虚拟局域网 */
if (vlan_tx_tag_present(skb)) {
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
if (vlan_hwaccel_do_receive(&skb)) {
ret = __netif_receive_skb(skb);
goto out;
} else if (unlikely(! skb))
goto out;
}
/* Make sure frames received on VLAN interfaces stacked on bonding
* interfaces still make their way to any base bonding device that may
* have registered for a specific ptype. The handler may have to adjust
* skb->dev and orig_dev.
*/
orig_or_bond = orig_dev;
if ((skb->dev->priv_flags & IFF_802_1Q_VLAN) &&
(vlan_dev_real_dev(skb->dev)->priv_flags & IFF_BONDING)) {
orig_or_bond = vlan_dev_real_dev(skb->dev);
}
type = skb->protocol; /* 三层协议类型 */
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type && (ptype->dev == null_or_orig || ptype->dev == skb->dev
|| ptype->dev == orig_dev || ptype->dev == orig_or_bond)) {
/* 如果三层协议是ETH_P_IP,相应的packet_type为ip_packet_type,
* 协议处理函数为ip_rcv()。
*/
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else { /* 说明没找到对应的三层协议 */
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}

L3协议处理函数

#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)
static DEFINE_SPINLOCK(ptype_lock);
static struct list_head ptype_base[PTYPE_HASH_SIZE]; /* 协议哈希表 */
static struct list_head ptype_all; /* 嗅探器(ETH_P_ALL)的链表 */

packet_type用于描述一个协议:

struct packet_type {
__be16 type; /* This is really htons(ether_type). 协议代码 */
struct net_device *dev; /* NULL is wildcarded here */
/* 协议处理函数,如ip_rcv() */
int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);
...
struct list_head list;
}

IP协议:

/* IP protocol layer initialiser */
static struct packet_type ip_packet_type = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
...
};
#define ETH_P_IP 0x0800 /* Internet Protocol packet */

数据包接收系列 — 下半部实现(软中断)相关推荐

  1. 数据包接收系列 — 数据包的接收过程(宏观整体)

    本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面参考里的两篇文章,里面介绍的更详细. 本文只讨论以太网的物理网卡,不涉及虚拟设备,并且以一个 ...

  2. 数据包接收系列 — IP协议处理流程(一)

    本文主要内容:在接收数据包时,IP协议的处理流程. 内核版本:2.6.37 Author:zhangskd @ csdn blog IP报头 IP报头: struct iphdr { #if defi ...

  3. 网卡驱动和队列层中的数据包接收

    一.从网卡说起 这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析.这里以Intel的e100驱动为例进行分析. 大多数网卡都是一个PCI设备,PCI设备都包含了一个标准 ...

  4. UDP数据包接收逻辑的优化修改以及对性能的影响

    UDP数据包接收逻辑的优化修改以及对性能的影响 #include <stdio.h> #include <stdlib.h> #include <unistd.h> ...

  5. Linux网络数据包接收处理过程

    因为要对百万.千万.甚至是过亿的用户提供各种网络服务,所以在一线互联网企业里面试和晋升后端开发同学的其中一个重点要求就是要能支撑高并发,要理解性能开销,会进行性能优化.而很多时候,如果你对Linux底 ...

  6. linux tcp 包大小,Linux TCP数据包接收处理 --- 转

    在接收流程一节中可以看到数据包在读取到用户空间前,都要经过tcp_v4_do_rcv处理,从而在receive queue中排队. 在该函数中,我们只分析当连接已经建立后的数据包处理流程,也即tcp_ ...

  7. JAVA实现udp接收文件数据,java – 播放以UDP数据包接收的原始PCM音频

    以下是获取输出线并在其上播放PCM的简单示例.在运行时,它会播放大约一秒长的恼人的哔哔声. import javax.sound.sampled.AudioFormat; import javax.s ...

  8. linux内核网络协议栈--数据包的接收过程(二十二)

    与其说这篇文章分析了网卡驱动中中数据包的接收,还不如说基于Kernel:2.6.12,以e100为例,对网卡驱动编写的一个说明.当然,对数据包的接收说的很清楚. 一.从网卡说起 这并非是一个网卡驱动分 ...

  9. Linux网络-数据包的接收流程(基于RTL8139网卡驱动程序)

    本文将介绍Linux系统中,基于RTL8139网卡驱动程序,是如何一步一步将接收到的数据包传送到内核的网络协议栈的. 下图展示了数据包(packet)如何进入内存,并被内核的网络模块开始处理: +-- ...

  10. 串口协议的制定以及串口中怎样接收一个完整数据包的解析

    里以串口作为传输媒介,介绍下怎样来发送接收一个完整的数据包.过程涉及到封包与解包.设计一个良好的包传输机制很有利于数据传输的稳定性以及正确性.串口只是一种传输媒介,这种包机制同时也可以用于SPI,I2 ...

最新文章

  1. 诸法无我-悉达多 乔达摩
  2. 深度学习核心技术精讲100篇(十六)-搜索引擎Indri系列之如何建立索引 (Indexing)检索评价 (Evaluation)
  3. C语言开发笔记(二)volatile
  4. .NET(C#)有哪些主流的ORM框架
  5. 程序员面试金典 - 面试题 17.23. 最大黑方阵(DP)
  6. 洛谷P1087 FBI树
  7. Loadrunner 第一个场景设计Controller
  8. 幼儿园调查过程怎么写_幼儿园对孩子的重要性你真的清楚吗?
  9. Python scrapy 将mmjpg图片下载到本地
  10. Adobe Premiere基础-常用的视频特效(裁剪,黑白,剪辑速度,镜像,镜头光晕)(十五)
  11. php rabbit pie broke,英语拟声词大全
  12. photoshop调人像冷色
  13. zk实现主从选举-java
  14. 使用memcpy函数的耗时测试(拷贝不同大小数据量耗时不同)
  15. 计算机应用 网络管理开发,基于XML的iBAC网络管理系统的研究与开发-计算机应用技术专业论文.docx...
  16. 2021-11-26学习总结
  17. PHP匿名在线聊天室系统源码
  18. 告诉你为什么数据要取对数
  19. 怎么用svg画一个圆圈(一)
  20. 告别疫情红利Q1同比转亏,声网的护城河为何护不住盈利?

热门文章

  1. 【微信公众号入门到精通 一】发送模板消息(业务通知)
  2. Vue 获取最近一周、当前周的日期
  3. 单细胞分析之标准化处理
  4. 20个Java的最佳实践(一)
  5. 关于Java自带的签名工具-Keytool的使用
  6. IE,FF浏览器下无法切换图片的问题
  7. 轻松学Pytorch – 构建生成对抗网络
  8. 【英语学习】【医学】Unit 04 The Endocrine System
  9. 美国伊利诺伊大学香槟分校AI医疗实验室招收暑期远程实习生
  10. Flask—上下文、g对象、请求钩子、flask_script脚本扩展、渲染模板、过滤器