本文主要讲解了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内核二层数据包接收流程的更多相关文章

  1. linux 内核网络数据包接收流程

    转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...

  2. Linux内核网络数据包处理流程

    Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...

  3. 数据包接收系列 — NAPI的原理和实现

    本文主要内容:简单分析NAPI的原理和实现. 内核版本:2.6.37 Author:zhangskd @ csdn 概述 NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就 ...

  4. [转帖]Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点

    Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点 http://network.51cto.com/art/201909/603780.htm 可以毫不夸张的说现如今的互联网是基于TC ...

  5. Linux系统捕获数据包流程

    Linux系统捕获数据包流程 为了提高数据包的捕获效率,瓶颈问题是一个需要非常关注的焦点.减少在捕获数据包过程中的瓶颈,就能够提高数据包捕获的整体性能.下面本文将以Linux操作系统为平台,分析捕获数 ...

  6. linux内核打印数据到串口控制台,printk数据不打印问题

    linux内核打印数据到串口控制台问题 原文来源:http://i.cnblogs.com/EditPosts.aspx?opt=1 1.查看当前控制台的打印级别 cat /proc/sys/kern ...

  7. 利用wireshark抓取远程linux上的数据包

    原文发表在我的博客主页,转载请注明出处. 前言 因为出差,前后准备总结了一周多,所以博客有所搁置.出差真是累人的活计,不过确实可以学习到很多东西,跟着老板学习做人,学习交流的技巧.入正题~ wires ...

  8. linux内核数据包转发流程(三)网卡帧接收分析

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 每一个cpu都有队列来处理接收到的帧,都有其数据结构来处理入口和出口流量,因此,不 ...

  9. linux内核数据包转发流程(二):中断

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu.邮箱:shallnew*163.com] 内核在处理2层数据包之前,必须先处理中断系统.设立中断系统,才有可能每秒处理成千的 ...

随机推荐

  1. 机器学习(4):BP神经网络原理及其python实现

    BP神经网络是深度学习的重要基础,它是深度学习的重要前行算法之一,因此理解BP神经网络原理以及实现技巧非常有必要.接下来,我们对原理和实现展开讨论. 1.原理  有空再慢慢补上,请先参考老外一篇不错的 ...

  2. ios 得到目录大小 进率是1000

    - (CGFloat)folderSizeAtPath:(NSString *) folderPath {     NSFileManager * manager = [NSFileManager d ...

  3. SQL Structured Query Language(结构化查询语言) 数据库

    SQL是Structured Query Language(结构化查询语言)的缩写. SQL是专为数据库而建立的操作命令集,是一种功能齐全的数据库语言. 在使用它时,只需要发出“做什么”的命令,“怎么 ...

  4. Make a DAC with a microcontroller's PWM timer

    http://www.edn.com/design/analog/4337128/Make-a-DAC-with-a-microcontroller-s-PWM-timer Many embedded ...

  5. window server 2012 更改密钥 更改系统序列号

    由于在window server 2012当中,好像更改密钥的方法,给隐藏了,没办法激活,这里记录一下在网上查找到的一个命令行,如何在window server 2012 更改密钥 更改系统序列号 在 ...

  6. HDU 2686 Matrix(最大费用流)

    Matrix Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  7. MapReduce实现排序功能

    期间遇到了无法转value的值为int型,我採用try catch解决 str2 2 str1 1 str3 3 str1 4 str4 7 str2 5 str3 9 用的\t隔开,得到结果 str ...

  8. spring事务的隔离级别(透彻理解)

    1.spring 事务这个东西,是轮子,每个service,都需要用到.所以干脆就做在框架层实现. 2.spring是怎么给你的service方法加事务的呢?jdk动态代理,会针对每个service类 ...

  9. FT网站开发过程遇到的问题汇总

    1.jar包不兼容问题.主要是mybatis,spring jar包不兼容.同时jstl标签也需要jar包,是jstl.jar,standard.jar. 2.mybatis的mapper.xml映射 ...

  10. MSI failed, 不能卸载VMware

    解决方法; http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&ext ...