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层数据包之前,必须先处理中断系统.设立中断系统,才有可能每秒处理成千的 ...
随机推荐
- PHP获取文件大小详解
通过PHP filesize函数可直接获取文件大小(单位字节),如:filesize('test.png') echo filesize('test.png'); 查看test.png图片属性: 文件 ...
- struts2对拦截器使用带实例
拦截器是struts2的核心.拦截器可以拦截请求,控制视图的走向.那么怎么来实现自定义的拦截器呢? 这里我们做一个例子. 首先假现在做了两个jsp页面一个是登陆的信息的(用session来模拟),一个 ...
- MVC文件上传07-使用客户端jQuery-File-Upload插件和服务端Backload组件裁剪上传图片
本篇通过在配置文件中设置,对上传图片修剪后保存到指定文件夹. 相关兄弟篇: MVC文件上传01-使用jquery异步上传并客户端验证类型和大小 MVC文件上传02-使用HttpPostedFileB ...
- 去除ArcMap连接空间数据库中多余的属性表
这个操作目前可能不具有可行性,但是为了完整性还是在下面讲一下吧.如有兴趣的小伙伴,可以按照下面的操作方式去尝试. 一.需求 去除ArcMap连接空间数据库中多余的属性表. PL/SQL中查询得到的内容 ...
- Redis持久化RDB和AOF优缺点是什么,怎么实现的?我应该用哪一个?
原文http://www.ymq.io/2018/03/24/redis/ Redis是一种高级key-value数据库.数据可以持久化,而且支持的数据类型很丰富.有字符串,链表,集 合和有序集合.支 ...
- wireshark提取gzip格式的html
原文地址:http://blog.csdn.net/vah101/article/details/44102501 首先使用wireshark启动抓包,然后以百度为例,访问百度的首页,之后停止抓包,w ...
- NDK开发环境搭建_r8
本文主内容: 1. Android NDK 安装 2. 安装Cygwin与使用NDK编译 3. 在Eclipse中集成C/C++开发环境CDT 4. 安装Sequoyah插件 5. JNI编 ...
- C结构体
什么是结构体? 简单的来说,结构体就是个能够包含不同数据类型的一个结构,他是一种能够自己定义的数据类型,他的特点和数组主要有两点不同,首先结构体能够在一个结构中声明不同的数据类型,第二相同结构的结构体 ...
- 深度学习数据集Deep Learning Datasets
Datasets These datasets can be used for benchmarking deep learning algorithms: Symbolic Music Datase ...
- 微信小程序Nginx环境配置
环境配置概述 主要内容: SSL免费证书申请步骤 Nginx HTTPS 配置 TLS 1.2 升级过程 微信小程序要求使用 https 发送请求,那么Web服务器就要配置成支持 https,需要先申 ...