news 2026/5/9 8:22:24

linux之网络子系统(13)- 内核发送数据包流程以及相关实际问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
linux之网络子系统(13)- 内核发送数据包流程以及相关实际问题

一、相关实际问题

  1. 查看内核发送数据消耗的CPU时应该看sy还是si
  2. 在服务器上查看/proc/softirqs,为什么NET_RX要比NET_TX大得多
  3. 发送网络数据的时候都涉及那些内存拷贝操作
  4. 零拷贝到底是怎么回事
  5. 为什么Kafka的网络性能很突出

二、网络包发送过程总览

  1. 调用系统调用send发送
  2. 内存拷贝
  3. 协议处理
  4. 进入驱动RingBuffer
  5. 实际发送
  6. 中断通知发送完成
  7. 清理RingBuffer

三、网卡启动准备

现在的服务器上的网卡一般都是支持多队列的。每一个队列都是由一个RingBuffer表示的,开启了多队列以后的网卡就会有多个RingBuffer。

在多核时代,NIC 也相应的提供了 Multi-Queue 功能,可以将多个 Queue 通过硬中断绑定到不同的 CPU Cores 上处理。

以 Intel 82575 为例。

在硬件层面:它拥有 4 组硬件队列,它们的硬中断分别绑定到 4 个 Core 上,并通过 RSS(Receive Side Scaling)技术实现负载均衡。RSS 技术通过 HASH Packet Header IP 4-tuple(srcIP、srcPort、dstIP、dstPort),将同一条 Flow 总是送到相同的队列,从而避免了报文乱序问题。

在软件层面:Linux Kernel v2.6.21 开始支持网卡多队列特性。在 Net driver 初始化流程中,Kernel 获悉 Net device 所支持的硬件队列数量。然后结合 CPU Cores 的数量,通过 Sum=Min(NIC queue, CPU core) 公式计算得出应该被激活 Sum 个硬件队列,并申请 Sum 个中断号,分配给激活的每个队列。

网卡启动时最重要的任务就是分配和初始化RingBuffer,在网卡启动的时候会调用到__igb_open函数,RingBuffer就是在这里分配的。

// kernel/drivers/net/ethernet/intel/igb/igb_main.c static int __igb_open(struct net_device *netdev, bool resuming) { // 分配传输描述符数组 err = igb_setup_all_tx_resources(adpater); // 分配接收描述符数组 err = igb_setup_all_rx_resources(adpater); // 注册中断处理函数 err = igb_request_irq(adapter); if(err) goto err_req_irq; // 启用NAPI for(i = 0; i < adapter->num_q_vectors; i++) napi_enable(&(adapter->q_vector[i]->napi)); ...... } static int igb_setup_all_tx_resources(struct igb_adapter *adapter) { // 有几个队列就构造几个RingBuffer for(int i = 0; i < adapter->num_tx_queues; i++) { igb_setup_tx_resources(adapter->tx_ring[i]); } }

igb_setup_tx_resources:

/** * igb_setup_tx_resources - allocate Tx resources (Descriptors) * @tx_ring: tx descriptor ring (for a specific queue) to setup * * Return 0 on success, negative on failure **/ int igb_setup_tx_resources(struct igb_ring *tx_ring) { struct device *dev = tx_ring->dev; int size; size = sizeof(struct igb_tx_buffer) * tx_ring->count; tx_ring->tx_buffer_info = vmalloc(size); //内核使用的数组 if (!tx_ring->tx_buffer_info) goto err; /* round up to nearest 4K */ tx_ring->size = tx_ring->count * sizeof(union e1000_adv_tx_desc); //网卡硬件使用的数组 tx_ring->size = ALIGN(tx_ring->size, 4096); tx_ring->desc = dma_alloc_coherent(dev, tx_ring->size, &tx_ring->dma, GFP_KERNEL);//硬件数组的DMA映射 if (!tx_ring->desc) goto err; tx_ring->next_to_use = 0; tx_ring->next_to_clean = 0; return 0; err: vfree(tx_ring->tx_buffer_info); tx_ring->tx_buffer_info = NULL; dev_err(dev, "Unable to allocate memory for the Tx descriptor ring\n"); return -ENOMEM; }

igb_setup_tx_resources内部也是申请了两个数组,igb_tx_buffer数组和e1000_adv_tx_desc数组,一个供内核使用,一个供网卡硬件使用

在这个时候它们之间还没什么关系,将来在发送数据的时候这两个数组的指针都指向同一个skb,这样内核和硬件就能共同访问同样的数据了。内核往skb写数据,网卡硬件负责发送

硬中断的处理函数igb_msix_ring也是在__igb_open函数中注册的

四、数据从用户进程到网卡的详细过程

1)系统调用实现

send系统调用内部真正使用的是sendto系统调用,主要做了两件事:

  1. 在内核中把真正的socket找出来
  2. 构造struct msghdr对象, 把用户传入的数据,比如buffer地址(用户待发送数据的指针)、数据长度、发送标志都装进去
SYS_CALL_DEFINE6(sendto, ......) { sock = sockfd_lookup_light(fd, &err, &fput_needed); struct msghdr msg; struct iovec iov; iov.iov_base = buff; iov.iov_len = len; msg.msg_iovlen = &iov; msg.msg_iov = &iov; msg.msg_flags = flags; ...... sock_sendmsg(sock, &msg, len); }

sock_sendmsg经过一系列调用,最终来到__sock_sendmsg_nosec中调用sock->ops->sendmsg

对于AF_INET协议族的socket,sendmsg的实现统一为inet_sendmsg

2)传输层处理
1. 传输层拷贝

在进入协议栈inet_sendmsg以后,内核接着会找到sock中具体的协议处理函数,对于TCP协议而言,sk_prot操作函数集实例为tcp_prot,其中.sendmsg的实现为tcp_sendmsg(对于UDP而言中的为udp_sendmsg)。

int inet_sendmsg(......) { ...... return sk->sk_prot->sendmsg(iocb, sk, msg, size); } int tcp_sendmsg(......) { ...... // 获取用户传递过来的数据和标志 iov = msg->msg_iov; // 用户数据地址 iovlen = msg->msg_iovlen; // 数据块数为1 flags =
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 8:22:00

PIC18单片机与24XXX EEPROM的I2C通信实战

1. PIC18单片机与24XXX系列EEPROM的I2C通信实战指南在嵌入式系统开发中&#xff0c;非易失性存储是保存配置参数、运行日志等关键数据的必备功能。Microchip的24XXX系列EEPROM凭借其稳定的性能和简单的接口&#xff0c;成为工程师们的首选。本文将基于PIC18F452单片机&#xff…

作者头像 李华
网站建设 2026/5/9 8:20:34

Stratix III FPGA的DPA电路与rx_dpa_locked信号解析

1. Stratix III DPA电路架构解析动态相位调整(DPA)电路是Stratix III FPGA实现高速LVDS接口的核心模块。其核心工作原理是通过接收端PLL提供的8个相位间隔45的时钟信号&#xff0c;动态选择最佳采样相位点。与常规CDR电路不同&#xff0c;Stratix III的DPA设计具有以下特性&…

作者头像 李华
网站建设 2026/5/9 8:20:32

Yeti实体关系图构建指南:如何可视化威胁活动与攻击者关联

Yeti实体关系图构建指南&#xff1a;如何可视化威胁活动与攻击者关联 【免费下载链接】yeti Your Everyday Threat Intelligence 项目地址: https://gitcode.com/gh_mirrors/ye/yeti &#x1f50d; Yeti威胁情报平台为网络安全分析师提供了一个强大的实体关系图构建工具…

作者头像 李华
网站建设 2026/5/9 8:15:31

打卡信奥刷题(3232)用C++实现信奥题 P8436 【模板】边双连通分量

P8436 【模板】边双连通分量 题目描述 对于一个 nnn 个节点 mmm 条无向边的图&#xff0c;请输出其边双连通分量的个数&#xff0c;并且输出每个边双连通分量。 输入格式 第一行&#xff0c;两个整数 nnn 和 mmm。 接下来 mmm 行&#xff0c;每行两个整数 u,vu, vu,v&#xff0…

作者头像 李华
网站建设 2026/5/9 8:14:30

Vale编译器构建系统详解:跨平台编译与依赖管理终极指南

Vale编译器构建系统详解&#xff1a;跨平台编译与依赖管理终极指南 【免费下载链接】Vale Compiler for the Vale programming language - http://vale.dev/ 项目地址: https://gitcode.com/gh_mirrors/val/Vale Vale编译器是一款高性能、内存安全的编程语言编译器&…

作者头像 李华
网站建设 2026/5/9 8:14:30

如何用AKShare快速搞定金融数据获取?终极实战指南

如何用AKShare快速搞定金融数据获取&#xff1f;终极实战指南 【免费下载链接】akshare AKShare is an elegant and simple financial data interface library for Python, built for human beings! 开源财经数据接口库 项目地址: https://gitcode.com/gh_mirrors/aks/akshar…

作者头像 李华