Linux内核二层数据包接收流程
本文主要讲解了Linux内核二层数据包接收流程,使用的内核的版本是2.6.32.27
为了方便理解,本文采用整体流程图加伪代码的方式从内核高层面上梳理了二层数据包接收的流程,希望可以对大家有所帮助。阅读本文章假设大家对C语言有了一定的了解
整体流程如下:
数据报文接收流程伪代码分析如下
/*在基于中断收发报文的网卡设备驱动中,
* 当有数据报文进来的时候,使用net_interrupt()进行中断触发
*如 isa-skeleton设备驱动中*/ static int __init netcard_probe1(struct net_device *dev, int ioaddr)
{
/*注册net_interrupt为中断处理历程*/
int irqval = request_irq(dev->irq, &net_interrupt, 0, cardname, dev);
if (irqval) {
printk("%s: unable to get IRQ %d (irqval=%d).\n",
dev->name, dev->irq, irqval);
goto out;
} //......
return err;
} static irqreturn_t net_interrupt(int irq, void *dev_id)
{
//......
if (status & RX_INTR) {
/* Got a packet(s). */
/*使用NET_RX实现进行发送数据报文*/
net_rx(dev);
}
#if TX_RING
if (status & TX_INTR) {
/* Transmit complete. */
net_tx(dev);
np->stats.tx_packets++;
netif_wake_queue(dev);
}
#endif return IRQ_RETVAL(handled);
} /* We have a good packet(s), get it/them out of the buffers. */
static void
net_rx(struct net_device *dev)
{ /*使用dev_alloc_skb来分配skb,并把数据报文复制到skb中*/
skb = dev_alloc_skb(pkt_len);
if (skb == NULL) {
//......
}
skb->dev = dev; /* 'skb->data' points to the start of sk_buff data area. */
memcpy(skb_put(skb,pkt_len), (void*)dev->rmem_start, pkt_len);
/* or */
insw(ioaddr, skb->data, (pkt_len + 1) >> 1); /*调用netif_rx将数据报文交给上层处理*/
netif_rx(skb); return;
} DEFINE_PER_CPU(struct netif_rx_stats, netdev_rx_stat) = { 0, }; /*完成中断处理过程*/
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags; /*取得当前时间存储在skb->tstamp中*/
if (!skb->tstamp.tv64)
net_timestamp(skb); /*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
/*取得当前CPU的softnet_data,*/
queue = &__get_cpu_var(softnet_data); if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
enqueue:
/*将SKB放入到softnet_data[CPU].input_pkt_queue中
*一旦数据包出于该对列,中断就处理完成了*/
__skb_queue_tail(&queue->input_pkt_queue, skb);
local_irq_restore(flags);
return NET_RX_SUCCESS;
} /*如果queue->input_pkt_queue.qlen中已经有上次的数据包,
*发起NET_RX_SOFTIRQ软中断,由软中断的处理函数net_rx_action进行发送*/
napi_schedule(&queue->backlog);
{
__napi_schedule(n)
{
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
}
goto enqueue;
} __get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags); kfree_skb(skb);
return NET_RX_DROP;
} /*注册软中断NET_RX_SOFTIRQ的处理函数为net_rx_action*/
static int __init net_dev_init(void)
{
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
} /*必须要有NAPI的POLL么?没有NAPI的POLL回调怎么送往协议栈*/
static void net_rx_action(struct softirq_action *h)
{
struct list_head *list = &__get_cpu_var(softnet_data).poll_list; while (!list_empty(list)) {
struct napi_struct *n; n = list_entry(list->next, struct napi_struct, poll_list); /*调用每款驱动对NAPI注册的POLL函数,如pcnet32_poll
*在POLL函数的RX部分里面,会调用netif_receive_skb将
*数据包交给协议栈处理*/
work = n->poll(n, weight); WARN_ON_ONCE(work > weight); budget -= work; local_irq_disable(); /* Drivers must not modify the NAPI state if they
* consume the entire weight. In such cases this code
* still "owns" the NAPI instance and therefore can
* move the instance around on the list at-will.
*/
if (unlikely(work == weight)) {
if (unlikely(napi_disable_pending(n))) {
local_irq_enable();
napi_complete(n);
local_irq_disable();
} else
list_move_tail(&n->poll_list, list);
} netpoll_poll_unlock(have);
}
out:
local_irq_enable(); #ifdef CONFIG_NET_DMA
/*
* There may not be any more sk_buffs coming right now, so push
* any pending DMA copies to hardware
*/
dma_issue_pending_all();
#endif return; softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
} /*在RX部分里,会调用*/
static int pcnet32_poll(struct napi_struct *napi, int budget)
{
/*RX部分*/
work_done = pcnet32_rx(dev, budget);
{
pcnet32_rx_entry()
{
netif_receive_skb(skb);
}
} /*TX部分*/
pcnet32_tx(dev); return work_done;
} int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev; pt_prev = NULL; /*看看ptype_all中有没有相应的协议进行相应的协议处理,一般这里没有注册的协议,但是可以加入我们的分析钩子函数*/
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;
}
} /*处理网桥配置的数据报文*/
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out; skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out; /*对ptype_base表中的协议进行遍历,如果找到对应的协议,送往对应的协议栈进行处理*/
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)) {
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
{
kfree_skb(skb);
ret = NET_RX_DROP;
} out:
rcu_read_unlock();
return ret;
} /*调用相应协议的func进行处理*/
static inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev)
{
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} /*在af_inet.c文件中对IPV4的处理注册为ip_rcv,所以IPV4对应的FUNC为ip_rcv*/
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
}; /*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
//......
}
从分析的伪代码可以看出,数据包接受的时候,可以基于2中方式触发:1 收发中断 2 NAPI的轮询机制
这里没有分析驱动代码对硬件的操作,这部分代码在设备驱动程序中,本文举例了2款网卡代码 pcnet32 和 isa-skeleton,当硬件接受完毕之后就进入dev层面进行内核的总体调度,这也是上面伪代码分析的重点。当软中断被触发后,内核会回调每款驱动注册的poll函数钩子,进而进行首发处理,
在POLL的RX阶段中,会对报文进行分类送往不同的协议进行处理,这里举例ipv4的处理入口ip_rcv(),但是没有深入进去,后面的文章中将进行细致讲解。最后在POLL的TX阶段里面,对已经处理好的发送队列中的数据进行发送,在该阶段中会将数据报文映射到PCI DMA的发送ring中,并且调用netif_wake_queue(dev),来通知高层调用device注册的 ndo_hard_start_xmit函数进行硬件发送,后面发送的处理流程请参考我的上一篇博客
<<Linux内核数据包的发送传输>>(http://blog.csdn.net/eric_liufeng/article/details/10252857)
Linux内核二层数据包接收流程的更多相关文章
- linux 内核网络数据包接收流程
转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...
- Linux内核网络数据包处理流程
Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...
- 数据包接收系列 — NAPI的原理和实现
本文主要内容:简单分析NAPI的原理和实现. 内核版本:2.6.37 Author:zhangskd @ csdn 概述 NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就 ...
- [转帖]Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点
Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点 http://network.51cto.com/art/201909/603780.htm 可以毫不夸张的说现如今的互联网是基于TC ...
- Linux系统捕获数据包流程
Linux系统捕获数据包流程 为了提高数据包的捕获效率,瓶颈问题是一个需要非常关注的焦点.减少在捕获数据包过程中的瓶颈,就能够提高数据包捕获的整体性能.下面本文将以Linux操作系统为平台,分析捕获数 ...
- linux内核打印数据到串口控制台,printk数据不打印问题
linux内核打印数据到串口控制台问题 原文来源:http://i.cnblogs.com/EditPosts.aspx?opt=1 1.查看当前控制台的打印级别 cat /proc/sys/kern ...
- 利用wireshark抓取远程linux上的数据包
原文发表在我的博客主页,转载请注明出处. 前言 因为出差,前后准备总结了一周多,所以博客有所搁置.出差真是累人的活计,不过确实可以学习到很多东西,跟着老板学习做人,学习交流的技巧.入正题~ wires ...
- linux内核数据包转发流程(三)网卡帧接收分析
[版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 每一个cpu都有队列来处理接收到的帧,都有其数据结构来处理入口和出口流量,因此,不 ...
- linux内核数据包转发流程(二):中断
[版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 内核在处理2层数据包之前,必须先处理中断系统.设立中断系统,才有可能每秒处理成千的 ...
随机推荐
- ASP.NET 构建高性能网站 第3篇
HTTP请求的优化 在一个网页的请求过程中,其实整个页面的html结构(就是页面的那些html骨架)请求的时间是很短的,一般是占整个页面的请求时间的10%-20%.在页面加载的其余的时间实际上就是在加 ...
- LR下监控windows系统资源方法
1. 通过客户端与服务器进行网络测试,保证通信畅通.(测试主机本身) 2. 在运行中输入,service.msc打开系统的服务设置,开启服务器端Windows中的如 ...
- composer安装Workerman报错:Installation failed, reverting ./composer.json to its original content.
今天想在TP5上安装workerman,实现一个后台消息提醒功能. 第一步就卡住了,根据手册里说的首先通过composer安装 $ composer require topthink/think-wo ...
- 解决idea 控制台中文乱码
打开IntelliJ IDEA 14.0安装路径,小编的安装路径为:D:\Program Files\JetBrains\IntelliJ IDEA 14.0\bin 找到idea.exe.vmopt ...
- SQL Server 中添加表注释
今天在创建完表之后,发现没有办法给表添加注释说明,字段的注释可以在建表的时候就添加,上网查了一下使用SQL给表添加注释的方法,方法如下: -- 表加注释 EXEC sys.sp_addextended ...
- Remon Spekreijse CSerialPort串口类的修正版2014-01-10
转自:http://m.blog.csdn.net/blog/itas109/18358297# 2014-1-16阅读691 评论0 如需转载请标明出处:http://blog.csdn.net/i ...
- 解决Fragment每次进入都加载的问题
1.首先了解一下fragment的生命周期 onCreate是指创建该fragment类似于Activity.onCreate,你可以在其中初始化除了view之外的东西,onCreateView是创建 ...
- python文本 maketrans和translate
python文本 maketrans和translate 场景: 过滤字符串的某些字符,我们从例子出发 >>> tb=str.maketrans ('abc','123') & ...
- 【docker】docker启动、重启、关闭命令,附带:docker启动容器报错:docker: Error response from daemon: driver failed programming external connectivity on endpoint es2-node
在关闭并放置centos 的防火墙重启之后[操作:https://www.cnblogs.com/sxdcgaq8080/p/10032829.html] 启动docker容器就发现开始报错: [ro ...
- .NET:CLR via C# Shared Assemblies and Strongly Named Assemblies
Two Kinds of Assemblies, Two Kinds of Deployment A strongly named assembly consists of four attribut ...