Linux 网卡数据收发过程分析

用户7686797 2021-04-08 11:18:34
linux 数据 收发 发过 网卡


一般来说,网卡主要有两个重要的功能:接收数据发送数据

所以,当网卡接收到数据包后,要通知 Linux 内核有数据需要处理。另外,网卡驱动应该提供让 Linux 内核把数据把发送出去的接口。

net_device 结构是 Linux 为了适配不同类型的网卡设备而抽象出来的对象,不同的网卡驱动只需要按 Linux 的规范来填充 net_device 结构的各个成员变量,Linux 内核就能够识别出网卡,并工作起来。

下面我们将分析网卡设备接收和发送数据包的实现原理。

net_device 结构

net_device 结构是 Linux 内核对网卡设备的抽象,但由于历史原因,net_device 结构的定义十分复杂。

不过本文主要分析网卡设备收发数据的实现,所以不会分析 net_device 结构的所有成员。下面主要列出收发数据相关的成员,如下:

struct net_device
{
char name[IFNAMSIZ]; // 设备名字
...
unsigned int irq; // 中断号
...
int (*init)(struct net_device *dev); // 设备初始化设备的接口
...
int (*open)(struct net_device *dev); // 打开设备时调用的接口
int (*stop)(struct net_device *dev); // 关闭设备时调用的接口
// 发送数据接口
int (*hard_start_xmit)(struct sk_buff *skb,struct net_device *dev);
...
};
  • name:设备的名字。用于在终端显示设备的名字或者通过设备名字来搜索设备。
  • irq:中断号。当网卡从网络接收到数据包后,需要产生一个中断来通知 Linux 内核有数据包需要处理,而 irq 就是网卡驱动注册到内核中断服务的中断号。
  • initopenstop:分别为设备的初始化接口,打开接口和关闭接口。
  • hard_start_xmit:当需要通过网卡设备发送数据时,可以调用这个接口来发送数据。

所以,一个网卡驱动必须完成以下两个工作:

  • 通过实现 net_device 结构的 hard_start_xmit 方法来提供发送数据的功能。
  • 通过向内核注册硬件中断服务,来通知内核处理网卡设备接收到的数据包。

也就是说,发送数据的功能是由 net_device 结构的 hard_start_xmit 方法提供,而通知内核处理接收到的数据包的功能是由网卡的硬件中断提供的。

图1 展示了网卡接收和发送数据的过程:

图1 网卡接收和发送数据过程

上图展示的是 NS8390网卡 接收和发送数据的过程(红色括号为接收过程,蓝色括号为发送过程),从上图可以发现,NS8390网卡驱动 完成了两件事情:

  • net_device 结构的 hard_start_xmit 方法设置为 ei_start_xmit
  • 向 Linux 内核注册了 ei_interrupt 硬件中断服务。

所以,当网卡接收到数据包时,会触发 ei_interrupt 中断服务来通知内核有数据包需要处理。而当需要通过网卡发送数据时,将会调用 ei_start_xmit 方法把数据发送出去。

接收数据过程

当网卡从网络中接收到数据包后,会触发 ei_interrupt 中断服务,我们来看看 ei_interrupt 中断服务的实现:

void ei_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct net_device *dev = dev_id;
long e8390_base;
int interrupts, nr_serviced = 0;
struct ei_device *ei_local;
e8390_base = dev->base_addr;
ei_local = (struct ei_device *)dev->priv;
spin_lock(&ei_local->page_lock);
...
// (1) 通过读取网卡的中断类型来进行相应的操作
while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0
&& ++nr_serviced < MAX_SERVICE)
{
...
// (2) 如果中断类型为接收到数据包
if (interrupts & (ENISR_RX + ENISR_RX_ERR)) {
ei_receive(dev); // (3) 则从网卡读取数据
}
...
}
...
spin_unlock(&ei_local->page_lock);
return;
}

上面的代码删除了很多硬件相关的操作,因为本文并不是分析网卡驱动的实现。

ei_interrupt 中断服务首先读取中断的类型,保存到 interrupts 变量中。然后判断中断类型是否为接收到数据包,如果是就调用 ei_receive 函数从网卡处读取数据。

我们继续分析 ei_receive 函数的实现:

static void ei_receive(struct net_device *dev)
{
...
while (++rx_pkt_count < 10)
{
int pkt_len; // 数据包的长度
int pkt_stat; // 数据包的状态
...
if ((pkt_stat & 0x0F) == ENRSR_RXOK) { // 如果数据包状态是合法的
struct sk_buff *skb;
skb = dev_alloc_skb(pkt_len + 2); // 申请一个数据包对象
if (skb) {
skb_reserve(skb, 2);
skb->dev = dev; // 设置接收数据包的设备
skb_put(skb, pkt_len); // 增加数据的长度
// 从网卡中读取数据(由网卡驱动实现), 并将数据保存到skb中
ei_block_input(dev, pkt_len, skb, current_offset+sizeof(rx_frame));
skb->protocol = eth_type_trans(skb, dev); // 从以太网头部中获取网络层协议类型
netif_rx(skb); // 将数据包上送给内核网络协议栈
...
}
}
...
}
...
return;
}

ei_receive 函数主要完成以下几个工作:

  • 申请一个 sk_buff 数据包对象,并且设置其 dev 字段为接收数据包的设备。
  • 通过调用 ei_block_input 函数从网卡中读取接收到的数据,并保存到刚申请的 sk_buff 数据包对象中。ei_block_input 函数是由网卡驱动实现的,所以这里不作详细分析。
  • 通过调用 eth_type_trans 函数从数据包的以太网头部中获取网络层协议类型。
  • 调用 netif_rx 函数将数据包上送给内核网络协议栈。

当把数据包上送给内核网络协议栈后,数据包的处理就由内核接管。一般来说,内核网络协议栈会通过网络层的 IP协议 和传输层的 TCP协议 或者 UDP协议 来对数据包进行处理,处理完后就会把数据提交给应用层的进程进行处理。

发送数据过程

当网络协议栈需要通过网卡设备发送数据时,会调用 net_device 结构的 hard_start_xmit 方法,而对于 NS8390网卡 来说,hard_start_xmit 方法会被设置为 ei_start_xmit 函数。

也就是说,使用 NS8390网卡 发送数据时,最终会调用 ei_start_xmit 函数将数据发送出去。我们来看看 ei_start_xmit 函数的实现:

static int ei_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
length = skb->len; // 数据包的长度
...
disable_irq_nosync(dev->irq); // 关闭硬件中断
spin_lock(&ei_local->page_lock); // 对设备进行上锁, 避免多核CPU对设备的使用
...
// 使用网卡驱动的发送接口把数据发送出去,skb->data 为数据包的数据部分
ei_block_output(dev, length, skb->data, ei_local->tx_start_page);
...
spin_unlock(&ei_local->page_lock); // 对设备进行解锁
enable_irq(dev->irq); // 打开硬件中断
...
return 0;
}

删减了硬件相关的操作后,ei_start_xmit 函数的实现就非常简单:

  • 首先关闭网卡的硬件中断,防止发送过程中受到硬件中断的干扰。
  • 调用 ei_block_output 函数把数据包的数据发送出去,此函数由网卡驱动实现,这里不作详细分析。
  • 打开网卡的硬件中断,让网卡能够继续通知内核。

总结

本文主要简单的介绍了网卡设备接收和发送数据包的过程,而网卡设备的初始化过程并没有涉及。当然网卡设备的初始化过程也非常重要,可能会在后面的文章继续分析。

本文分享自微信公众号 - Linux内核那些事(like_linux)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间: 2021-03-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

版权声明
本文为[用户7686797]所创,转载请带上原文链接,感谢
https://cloud.tencent.com/developer/article/1810398

  1. Java 学生成绩管理系统课程设计,附源码!
  2. Java arbitrary audio to MP3
  3. DNS of docker
  4. Docker - build log monitoring system
  5. SSM + MySQL + Maven + Shiro WMS
  6. Top 10 reasons to fall in love with java!
  7. 一本关于HTTP的恋爱日记
  8. 【RocketMQ源码分析】深入消息存储(3)
  9. SpringCloud+Nacos实现服务配置中心(Hoxton版本)
  10. SCIP:构造数据抽象--数据结构中队列与树的解释
  11. SCIP:构造过程抽象--面向对象的解释
  12. 使用 docker 进行 ElasticSearch + Kibana 集群搭建
  13. Spring IOC 特性有哪些,不会读不懂源码!
  14. [DB Bao 41] use of PMM -- monitoring mysql, PG, mongodb, proxysql, etc
  15. Spring Cloud 升级之路 - 2020.0.x - 3. Undertow 的 accesslog 配置
  16. [DB Bao 42] MySQL high availability architecture MHA + proxysql realizes read-write separation and load balancing
  17. [DataGuard] recovery of physical DG in case of losing archive files in main database (7)
  18. MyBatis(3)Map和模糊查询拓展
  19. 【TTS】AIX-&gt;Linux--基于RMAN(真实环境)
  20. 【TTS】传输表空间AIX-&gt;linux基于rman
  21. 【TTS】传输表空间AIX asm -&gt; linux asm
  22. 【TTS】传输表空间Linux asm -&gt; AIX asm
  23. 【DB宝40】MySQL高可用管理工具Orchestrator简介及测试
  24. 【TTS】传输表空间Linux -&gt;AIX 基于rman
  25. 一本关于HTTP的恋爱日记
  26. 【RocketMQ源码分析】深入消息存储(3)
  27. SpringCloud+Nacos实现服务配置中心(Hoxton版本)
  28. SICP:构造过程抽象--面向对象的解释
  29. 3w 字长文爆肝 Java 基础面试题!太顶了!!!
  30. Spring Cloud 升级之路 - 2020.0.x - 3. Undertow 的 accesslog 配置
  31. win10卸载mysql5.7
  32. MySQL 批量插入,如何不插入重复数据?
  33. k8s cronjob应用示例
  34. 非常规方法,轻松应对Oracle数据库危急异常
  35. Oracle hang 之sqlplus -prelim使用方法
  36. 如何全文搜索oracle官方文档
  37. Java student achievement management system course design, with source code!
  38. win10安装mysql8.0
  39. 手把手教你写一个spring IOC容器
  40. JAVA 中的异常(1)- 基本概念
  41. A love diary about http
  42. navicat连接win10 mysql8.0 报错2059
  43. [rocketmq source code analysis] in depth message storage (3)
  44. Implementation of service configuration center with spring cloud + Nacos (Hoxton version)
  45. SCIP: constructing data abstraction -- Explanation of queue and tree in data structure
  46. SCIP: abstraction of construction process -- object oriented explanation
  47. Using docker to build elasticsearch + kibana cluster
  48. What are the spring IOC features? I can't understand the source code!
  49. Spring cloud upgrade road - 2020.0. X - 3. Accesslog configuration of undertow
  50. 导致Oracle性能抖动的参数提醒
  51. 风险提醒之Oracle RAC高可用失效
  52. 小机上运行Oracle需要注意的进程调度bug
  53. Oracle内存过度消耗风险提醒
  54. Oracle SQL monitor
  55. 使用Bifrost实现Mysql的数据同步
  56. 揭秘Oracle数据库truncate原理
  57. 看了此文,Oracle SQL优化文章不必再看!
  58. Mybatis (3) map and fuzzy query expansion
  59. Kafka性能篇:为何这么“快”?
  60. 两个高频设计类面试题:如何设计HashMap和线程池