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_ops→tcp_sendmsg() - UDP 走
udp_sendmsg(),不建连接,路径更短,但依然要经过路由查找(ip_route_output_flow())和邻居子系统(ARP) - 若目标 IP 不在同一子网,且路由表未缓存下一跳 MAC,
send()可能被阻塞在 ARP 请求完成前(尤其在SOCK_DGRAM首次发包时)
IP 层怎么决定走哪个网卡和下一跳 MAC
内核通过 fib_lookup() 查路由表(FIB),结果不是“目标 IP”,而是 struct rtable 或 struct 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(驱动提交数) vstx_unicast(PHY 实际发出单播帧数),差值过大说明链路层丢包 - 启用
CONFIG_NET_RX_BUSY_POLL或 XDP 程序时,部分路径会绕过传统 softirq,需确认是否影响你观察的统计点
整个流程里,最易被当成“黑盒”而掩盖真实瓶颈的,其实是邻居子系统和 qdisc 队列——它们不报错,但会让包在内存里滞留几十毫秒,且不会出现在 tcpdump 中。










