当前位置: 首页 > 新闻动态 > 技术教程

Linux 网络通信从应用到网卡的完整流程

作者:舞夢輝影 浏览: 发布日期:2026-01-28
[导读]:socket系统调用通过陷入内核态交由socket子系统处理,send/sendto/write触发协议栈下行;TCP走tcp_sendmsg()、UDP走udp_sendmsg(),经路由查找、ARP解析后生成sk_buff,经qdisc、驱动ndo_start_xmit提交至网卡DMA发送。
socket系统调用通过陷入内核态交由socket子系统处理,send/sendto/write触发协议栈下行;TCP走tcp_sendmsg()、UDP走udp_sendmsg(),经路由查找、ARP解析后生成sk_buff,经qdisc、驱动ndo_start_xmit提交至网卡DMA发送。

socket 系统调用如何触发内核协议栈处理

应用层调用 socket()bind()connect()sendto() 时,并不直接操作硬件,而是陷入内核态,由内核的 socket 子系统接管。关键点在于:这些调用本身不发包,只是准备数据结构和状态;真正触发网络栈下行的是 send() / sendto() / write() 这类写操作。

常见误区是认为 connect() 就发 SYN 包——其实它只是设置 socket 状态为 CONNECTING,真正发包发生在第一次 send()(阻塞模式)或内核在后续软中断中调度发送队列(非阻塞 + EPOLLOUT 场景)。

  • AF_INET + SOCK_STREAM 对应 TCP 协议栈入口,走 inet_stream_opstcp_sendmsg()
  • UDP 走 udp_sendmsg(),不建连接,路径更短,但依然要经过路由查找(ip_route_output_flow())和邻居子系统(ARP)
  • 若目标 IP 不在同一子网,且路由表未缓存下一跳 MAC,send() 可能被阻塞在 ARP 请求完成前(尤其在 SOCK_DGRAM 首次发包时)

IP 层怎么决定走哪个网卡和下一跳 MAC

内核通过 fib_lookup() 查路由表(FIB),结果不是“目标 IP”,而是 struct rtablestruct fib_result,其中包含:

  • oif:出接口索引,对应具体网卡(如 eth0 的 dev->ifindex
  • gateway:若非直连,

    此字段为下一跳路由器 IP
  • dst:最终封装用的目标 IP(可能与原始 send 目标不同,比如经策略路由或 NAT 后)

拿到出接口和下一跳 IP 后,进入邻居子系统(neighbour subsystem):如果目标是直连网段,查 arp_table 获取 MAC;如果是网关,则查该网关 IP 对应的 MAC。查不到就发 ARP 请求并临时挂起 sk_buff 在 skb->dst->neighbour->arp_queue 上,等响应回来再重发。

注意:ip route get 可验证实际选路结果,而 ip neigh show 能看到当前 ARP 缓存——很多“ping 通但应用连不上”的问题,根源是 ARP 表老化或被防火墙丢弃了请求。

sk_buff 如何从协议栈落到网卡驱动

数据包经 TCP/UDP/IP 封装后,变成一个 sk_buff 结构体,最终调用 dev_queue_xmit() 进入设备层。这里的关键跳转是:

dev_queue_xmit() → 检查 dev->flags & IFF_UP → 进入 qdisc(如 pfifo_fast)→ __qdisc_run()sch_direct_xmit()dev_hard_start_xmit() → 网卡驱动的 ndo_start_xmit 回调

  • 若启用了 GSO(Generic Segmentation Offload),TCP 分段可能延迟到驱动层(skb_is_gso() 为真),由网卡硬件完成分片,此时 sk_buff 携带的是大包 + gso_size
  • 若网卡不支持 TSO/GSO,内核在 tcp_tso_segment() 提前分片,生成多个小 sk_buff
  • dev->xmit_lock 是 per-CPU 锁,高并发下锁竞争可能成为瓶颈,可通过 ethtool -L eth0 combined N 调整队列数缓解

网卡驱动如何把数据交给物理介质

驱动的 ndo_start_xmit 实现因芯片而异,但通用流程是:将 sk_buff 数据地址和长度写入网卡 DMA 描述符环(descriptor ring),触发 tx_doorbell 告知硬件取包。此时 CPU 不等待发送完成,而是继续处理其他任务。

真正发出信号靠网卡硬件:它读取描述符,用 DMA 把数据搬进自己的 FIFO,按以太网帧格式(含 preamble、SFD、DA/SA、type、FCS)串行输出到 PHY 层,PHY 再转成电信号(RJ45)或光信号(SFP)。

容易忽略的点:

  • /proc/net/dev 中的 tx_dropped 不代表线缆没信号,可能是驱动 ring 满(tx_fifo_errors)、DMA 映射失败(tx_aborted_errors)或校验错误
  • ethtool -S eth0 可查看芯片级计数器,比如 tx_packets(驱动提交数) vs tx_unicast(PHY 实际发出单播帧数),差值过大说明链路层丢包
  • 启用 CONFIG_NET_RX_BUSY_POLL 或 XDP 程序时,部分路径会绕过传统 softirq,需确认是否影响你观察的统计点

整个流程里,最易被当成“黑盒”而掩盖真实瓶颈的,其实是邻居子系统和 qdisc 队列——它们不报错,但会让包在内存里滞留几十毫秒,且不会出现在 tcpdump 中。

免责声明:转载请注明出处:http://m.jing-feng.com.cn/news/578162.html

扫一扫高效沟通

多一份参考总有益处

免费领取网站策划SEO优化策划方案

请填写下方表单,我们会尽快与您联系
感谢您的咨询,我们会尽快给您回复!