Linux内核网络栈分为链路层、网络层、传输层、套接字层四层;每层通过sk_buff指针操作实现零拷贝,依赖struct sock标识连接,驱动经NAPI或中断将包送入netif_receive_skb()启动协议处理。

Linux 网络栈分哪几层?不是 OSI,是内核实际跑的四层
Linux 内核网络栈按功能划分为四层:链路层、网络层、传输层、套接字层(应用层逻辑由用户进程实现,不属内核栈)。这不是教科书式的 OSI 七层映射,而是内核数据流穿行的真实路径。每层对应一组核心数据结构和处理函数,sk_buff 是贯穿全程的“数据包载体”,它不存数据本身,只通过指针管理缓冲区头尾,各层仅移动指针、增删头部,避免拷贝。
- 链路层:处理
ethhdr、ARP 请求/响应,驱动收包后调用netif_receive_skb()入栈;发包前调用dev_queue_xmit() - 网络层:核心是
ip_rcv()和ip_forward(),查 FIB 路由表(ip route show查看)、做分片/重组、校验 IP 头 - 传输层:TCP 用
tcp_v4_rcv(),UDP 用udp_rcv();端口查找靠inet_hashinfo结构,连接状态存在struct sock中 - 套接字层:所有系统调用入口,如
bind()、connect()、sendmsg()最终都落到sock->ops->xxx函数指针上
sk_buff 是什么?为什么改错指针位置就丢包
sk_buff 是 Linux 网络栈的“命脉”——它不是数据容器,而是一个带多组指针(head/data/tail/end)的元结构。数据真正存在 DMA 分配的内存页里,sk_buff 只负责告诉各层:“当前有效数据从哪开始、到哪结束”。一旦某层(比如 TCP)在封装时没调用 skb_push() 扩展 data 指针,或错误调用 skb_pull() 导致 data > tail,后续层读取时就会越界或读空,表现为静默丢包、tcpdump 能抓到入包但应用层收不到。
- 典型坑:
skb_reserve(skb, ETH_HLEN)必须在驱动收包后立即调用,否则以太网头会覆盖 IP 头 - 调试技巧:用
skb_dump()或在netdev_rx_handler_register()回调里打印skb->len和skb_headlen(skb)判断数据是否被意外截断 - 注意:NAPI 轮询中,一个
sk_buff可能被多个 CPU 并发访问,不能裸改指针而不加锁或使用 per-CPU 变量
网卡驱动怎么把包交给协议栈?别只盯着 ifconfig
物理网卡收包不是靠 ifconfig 或 ip link 启动的,而是靠驱动注册中断处理函数(如 e1000_intr())或 NAPI poll 函数(如 e1000_clean())。当网卡 DMA 完成,触发硬中断 → 内核调度软中断 NET_RX_SOFTIRQ → 执行 net_rx_action() → 调用驱动的 poll 方法批量收包 → 对每个包调用 netif_receive_skb() 进入协议栈。这个链条一旦断裂(比如驱动未启用 NAPI、net.core.netdev_budget 设太小),就会看到 /proc/net/dev 中 RX 计数增长但 netstat -s | grep -i "packet receive" 不变——包卡在驱动队列没进栈。
RPCMS是一款基于PHP+MYSQL的轻量型内容管理/博客系统,支持PHP5.6版本以上,支持win/Linux系统。它自主研发的RP框架(OPP方式),采用MVC架构搭建的高效、稳定的内容管理系统。灵活小巧,但有着强大的扩展性、丰富的插件接口和大量的模板。统一采用模板标签,轻松上手,让开发更方便!智能缓存机制让网站运行方面大幅度提高。系统特点:源码简洁、体积轻巧、功能丰富、安全、灵活等特点,完
- 验证是否进栈:运行
cat /proc/interrupts | grep eth0看中断是否上升;再执行watch -n1 'cat /proc/net/snmp | grep -A1 Tcp'观察TcpInSegs是否同步增加 - 常见误操作:用
ethtool -K eth0 gso off关闭 GSO 后,大包在传输层被分片,若 MTU 配置不当,可能触发链路层反复重传 - 虚拟网卡(如
veth)无中断,走的是直接函数调用路径:dev_hard_start_xmit()→dev->netdev_ops->ndo_start_xmit()→ 直接送入对端sk_buff队列
为什么 socket() 创建的 fd 能跨层通信?关键在 struct sock
用户调用 socket(AF_INET, SOCK_STREAM, 0) 时,内核分配一个 struct socket(含文件描述符接口)并关联一个 struct sock(含协议控制块)。后者才是协议栈真正的“身份凭证”:TCP 层的 tcp_v4_rcv() 收到包后,用五元组(源IP/端口、目的IP/端口、协议)查哈希表,找到匹配的 struct sock,再把包塞进它的接收队列 sk->sk_receive_queue。应用层 recv() 实际就是从这个队列里取包。
- 所以
bind()的端口、listen()的 backlog、setsockopt()的TCP_NODELAY,全作用于struct sock字段,而非文件描述符本身 - 陷阱:多个线程同时
recv()同一个 TCP socket,不会竞争sk_receive_queue—— 因为内核已用sk_lock保护,但若用SO_REUSEPORT创建多个 socket 绑定同一端口,每个 socket 有独立队列,负载不均时需用reuseport+ BPF 做分流 - 查看真实状态:
ss -i显示每个 socket 的rcv_ssthresh、cwnd等字段,比netstat更贴近内核视角
sk_buff 是传送带,struct sock 是工位编号,驱动和协议函数是机械臂。任何一层指针偏移、队列溢出、哈希冲突或锁争用,都会让包在某个环节“掉下传送带”——而这种故障往往没有日志,只有 tcpdump 和 /proc/net/snmp 的数字差值在默默提醒你。









