本文主要讲解了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. Codeforces Round #354 (Div. 2) B. Pyramid of Glasses 模拟

    B. Pyramid of Glasses 题目连接: http://www.codeforces.com/contest/676/problem/B Description Mary has jus ...

  2. Set常用子类特点

    HashSet:       重写   hashCode和equals方法                                        特点:无序,唯一      底层结构是:    ...

  3. Spring-4.0 + Quartz-2.2.1 集群实例(Tomcat+Memcached+Quartz集群session共享)还是没有解决Serializable序列化

  4. 为Eclipse添加C/C++开发工具

    如果你是一个C / C ++开发人员,正在为你的工作寻找一款开源的IDE工具,你可以考虑基于Eclipse的IDE. Eclipse中拥有一个可扩展的插件系统定制让你轻松的来定制你的IDE,它可以支持 ...

  5. TR069协议小结

        也称为CWMP,是在Internet网上通过wan口控制通信终端设备的协议.其协议流程如下图所示: 具体网上有很多资料.其主要的两个内容是:HTTP Client模型.DATA模型.      ...

  6. css3 实现圆角边框的border-radius属性和实现阴影效果的box-shadow属性

    首先我要介绍的是border-radius属性,它的作用是实现圆角边框,其中border-radius:20px;表示,一个’体‘四个角都圆滑20px,其值如果为100px那么圆角度则为最高,如果是正 ...

  7. 文件操作篇 close creat dup dup2 fcntl flock fsync lseek mkstemp open read sync write

    文件操作篇 close creat dup dup2 fcntl flock fsync lseek mkstemp open read sync write close(关闭文件) 相关函数 ope ...

  8. Java网络编程技术1

    1. Java网络编程常用API 1.1 InetAddress类使用示例 1.1.1根据域名查找IP地址 获取用户通过命令行方式指定的域名,然后通过InetAddress对象来获取该域名对应的IP地 ...

  9. @JVM中的几种垃圾收集算法

    标记-清除(Mark-Sweep) 算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象(没有与GC Roo ...

  10. Median of Two Sorted Array leetcode java

    题目: There are two sorted arrays A and B of size m and n respectively. Find the median of the two sor ...