【Linux内核专栏 14】网络协议栈

本篇定位 :Linux 网络栈是其最复杂的子系统之一。裸机网络靠 lwIP/自己写,Linux 有完整 TCP/IP 栈 + socket 抽象 + NAPI 收包 + netfilter 钩子。本篇讲清数据通路(收发路径)、sk_buff、net_device、NAPI、netfilter。读完能理解 ping/curl 的内核路径、能看懂网卡驱动收发框架。
网络栈是 Linux 最大数据通路,你裸机用 lwIP 是简化版

FreeRTOS 用 lwIP:socket-like API,单核,轮询/中断收包。Linux 网络栈:socket 抽象 + 完整 TCP/IP + NAPI(中断+轮询混合)+ netfilter 钩子 + 多核并行。增量是 NAPI(防中断风暴)、netfilter(防火墙钩子)、sk_buff(统一缓冲)。你 lwIP 经验能迁移,新增的是规模和优化。


目录

  • 一、网络栈分层
  • 二、socket:用户接口
    • [2.1 socket 是什么](#2.1 socket 是什么)
    • [2.2 socket 在内核](#2.2 socket 在内核)
      • [2.3 socket 一切皆文件](#2.3 socket 一切皆文件)
  • 三、sk_buff:数据包⭐
    • [3.1 sk_buff 是什么](#3.1 sk_buff 是什么)
    • [3.2 sk_buff 的缓冲区布局](#3.2 sk_buff 的缓冲区布局)
    • [3.3 关键 API](#3.3 关键 API)
    • [3.4 收发包时 skb 的变化](#3.4 收发包时 skb 的变化)
  • 四、net_device:网卡抽象
    • [4.1 net_device 是什么](#4.1 net_device 是什么)
    • [4.2 注册网卡](#4.2 注册网卡)
    • [4.3 网卡驱动核心:ndo_start_xmit](#4.3 网卡驱动核心:ndo_start_xmit)
  • 五、发包路径(TX)
    • [5.1 完整发包](#5.1 完整发包)
    • [5.2 拥塞控制(TCP)](#5.2 拥塞控制(TCP))
  • [六、收包路径(RX)与 NAPI⭐](#六、收包路径(RX)与 NAPI⭐)
    • [6.1 老方式(纯中断)](#6.1 老方式(纯中断))
    • [6.2 NAPI(New API)](#6.2 NAPI(New API))
    • [6.3 NAPI 的好处](#6.3 NAPI 的好处)
    • [6.4 网卡驱动 NAPI 实现](#6.4 网卡驱动 NAPI 实现)
  • 七、netfilter:防火墙钩子⭐
    • [7.1 netfilter 是什么](#7.1 netfilter 是什么)
    • [7.2 五个钩子点](#7.2 五个钩子点)
    • [7.3 注册钩子](#7.3 注册钩子)
    • [7.4 iptables/nftables](#7.4 iptables/nftables)
  • 八、路由
    • [8.1 路由表](#8.1 路由表)
    • [8.2 路由查找](#8.2 路由查找)
    • [8.3 路由策略(policy routing)](#8.3 路由策略(policy routing))
  • 九、网络性能优化
    • [9.1 关键优化](#9.1 关键优化)
    • [9.2 多队列(RSS)](#9.2 多队列(RSS))
    • [9.3 XDP(高性能路径)](#9.3 XDP(高性能路径))
  • 十、网络监控
    • [10.1 基础](#10.1 基础)
    • [10.2 性能](#10.2 性能)
    • [10.3 调路由/防火墙](#10.3 调路由/防火墙)
  • 十一、对照总表
  • 十二、本篇小结
  • 速查表

一、网络栈分层

复制代码
用户态:    socket() / send() / recv()
              │
              ▼ syscall
VFS 层:    socketfs(socket 是文件)
              │
              ▼
协议栈:    TCP / UDP / ICMP / IP
              │
              ▼
netfilter: 钩子(防火墙/NAT/过滤)
              │
              ▼
链路层:    net_device(网卡驱动)
              │
              ▼
硬件:      网卡(MAC/PHY)

核心抽象

抽象 作用
socket 用户态接口(fd)
sk_buff 数据包(贯穿全栈)
net_device 网卡抽象
NAPI 收包机制(中断+轮询)
netfilter 钩子点(改包/丢包/NAT)

二、socket:用户接口

2.1 socket 是什么

  • 网络通信的端点,fd 表示
  • 类型:stream(TCP)/dgram(UDP)/raw
c 复制代码
int fd = socket(AF_INET, SOCK_STREAM, 0);  // TCP
bind(fd, ...);
listen(fd, ...);
accept(fd, ...);
connect(fd, ...);
send(fd, buf, n, 0);
recv(fd, buf, n, 0);
close(fd);

2.2 socket 在内核

c 复制代码
struct socket {
    short type;                  // SOCK_STREAM/DGRAM
    const struct proto_ops *ops; // 操作(inet_stream_ops 等)
    struct file *file;           // 关联 file(socket 也是文件)
    struct sock *sk;             // 协议层 sock
    ...
};

struct sock {
    struct sock_common __sk_common;
    int sk_protocol;             // TCP/UDP
    struct sk_buff_head sk_receive_queue;  // 接收队列
    struct sk_buff_head sk_write_queue;    // 发送队列
    ...
};
  • struct socet:VFS 层(用户接口)
  • struct sock:协议层(状态/队列)
  • socket fd 经 VFS(12 篇),file->f_op 是 socket_file_ops

2.3 socket 一切皆文件

  • socket 也是文件,有 fd
  • read/write/recv/send 都经 VFS
  • file->private_data 指向 socket

socket 体现"一切皆文件"

裸机网络自己管 socket 结构。Linux socket 是 fd,read/write 统一。VFS 抽象让网络和文件用同一套 syscall(send 是 write 的变种)。这是 Linux 设计哲学的体现。


三、sk_buff:数据包⭐

3.1 sk_buff 是什么

  • 网络数据包的载体,贯穿全栈
  • 含包数据 + 元数据(协议头指针/长度/设备)
  • 每层协议不拷贝数据,只改 sk_buff 的指针(零拷贝思想)
c 复制代码
struct sk_buff {
    struct sk_buff *next, *prev;      // 链表
    struct net_device *dev;            // 收/发设备
    char cb[48];                       // 控制缓冲(私有)
    unsigned int len;                  // 总长度
    unsigned char *head, *data;        // 缓冲区起/数据起
    unsigned char *tail, *end;         // 数据尾/缓冲尾
    __u16 protocol;                    // L2 协议(ETH_P_IP)
    __u32 priority;
    ...
    /* 协议头指针(各层) */
    unsigned char *transport_header;   // L4(TCP/UDP)
    unsigned char *network_header;     // L3(IP)
    unsigned char *mac_header;         // L2(以太网)
};

3.2 sk_buff 的缓冲区布局

复制代码
head                      data                    tail              end
 │                         │                       │                 │
 ▼                         ▼                       ▼                 ▼
 ┌─────预留(可加L2头)────┬────实际数据────┬──预留(可加尾)──┐
 │                         │   IP/TCP/负载  │                 │
 └─────────────────────────┴────────────────┴─────────────────┘
  • head/data/tail/end 四指针
  • 加协议头:skb_push(数据指针前移,腾出头部空间)
  • 去协议头:skb_pull(数据指针后移)

3.3 关键 API

API 作用
alloc_skb(size, GFP) 分配 skb
kfree_skb(skb) 释放
skb_put(skb, len) 尾部追加数据(tail 后移)
skb_push(skb, len) 头部加协议头(data 前移)
skb_pull(skb, len) 去掉头部(data 后移)
skb_reserve(skb, len) 预留头部空间

3.4 收发包时 skb 的变化

复制代码
发包(下行):
  应用数据 → skb(只含 payload)
  → TCP 加头:skb_push(TCP 头)
  → IP 加头:skb_push(IP 头)
  → 以太网加头:skb_push(以太网头)
  → 网卡发

收包(上行):
  网卡收 → skb(含以太网头)
  → 去以太网头:skb_pull
  → 去 IP 头:skb_pull
  → 去 TCP 头:skb_pull
  → 应用数据

sk_buff 是网络栈的灵魂

裸机网络可能用固定缓冲区 + memcpy 加头。Linux sk_buff 用指针操作,加头只是前移 data 指针,不拷贝数据。这是零拷贝思想,网络栈高性能的关键。你写网卡驱动要熟练操作 skb。


四、net_device:网卡抽象

4.1 net_device 是什么

  • 每个网卡一个 net_device
  • 注册后出现 eth0/wlan0 接口
c 复制代码
struct net_device {
    char name[IFNAMSIZ];              // eth0
    unsigned long mem_end, mem_start;
    unsigned long base_addr;          // I/O 基址
    int irq;                          // 中断
    const struct net_device_ops *netdev_ops;  // 操作
    int mtu;                          // 最大传输单元
    unsigned char dev_addr[6];        // MAC 地址
    struct napi_struct napi;          // NAPI
    ...
};

struct net_device_ops {
    int (*ndo_open)(struct net_device *dev);      // ifconfig up
    int (*ndo_stop)(struct net_device *dev);      // ifconfig down
    int (*ndo_start_xmit)(struct sk_buff *skb,    // 发包
                          struct net_device *dev);
    ...
};

4.2 注册网卡

c 复制代码
struct net_device *dev = alloc_netdev(sizeof(struct my_priv), "eth%d",
                                      NET_NAME_UNKNOWN, ether_setup);
dev->netdev_ops = &my_ops;
register_netdev(dev);   // 注册,出现 eth0

4.3 网卡驱动核心:ndo_start_xmit

c 复制代码
static int my_start_xmit(struct sk_buff *skb, struct net_device *dev) {
    // 把 skb 数据发到硬件(DMA 或 PIO)
    // 发完 free skb
    dev_kfree_skb(skb);
    return NETDEV_TX_OK;
}
  • 协议栈发包最终调驱动的 ndo_start_xmit
  • 驱动把 skb 数据 DMA 到网卡,网卡发
  • 发完释放 skb

五、发包路径(TX)

5.1 完整发包

复制代码
用户: send(fd, buf, n)
  → syscall → sock_sendmsg
  → TCP/UDP 处理(加 L4 头,分段,拥塞控制)
  → IP 处理(加 L3 头,路由)
  → netfilter NF_INET_LOCAL_OUT 钩子
  → 链路层(加 L2 头,ARP 解析 MAC)
  → netfilter NF_INET_POST_ROUTING 钩子(NAT)
  → dev_queue_xmit → 驱动 ndo_start_xmit
  → 网卡硬件发送

5.2 拥塞控制(TCP)

  • TCP 发包受拥塞窗口控制
  • 慢启动/拥塞避免/快重传/快恢复
  • 拥塞丢包则窗口减半

六、收包路径(RX)与 NAPI⭐

6.1 老方式(纯中断)

  • 每个包来一个中断
  • 高流量下中断风暴,CPU 全在中断

6.2 NAPI(New API)

  • 中断 + 轮询混合

  • 第一个包来中断 → 关中断,切换到轮询

  • 轮询批量取包(从网卡 ring buffer)

  • 取完或配额到 → 重开中断

    1. 网卡收包 → 中断
    2. 驱动 ISR:关中断,napi_schedule
    3. softirq(NET_RX_SOFTIRQ)调度
    4. napi_poll 批量取包(一次取 budget 个)
    5. 取完 → 重开中断
    6. 没取完 → 留 softirq 下次继续

6.3 NAPI 的好处

  • 高流量时轮询,无中断风暴
  • 低流量时中断,及时响应
  • 批量处理,缓存友好

6.4 网卡驱动 NAPI 实现

c 复制代码
static int my_napi_poll(struct napi_struct *napi, int budget) {
    int rx_count = 0;
    struct my_dev *dev = container_of(napi, struct my_dev, napi);

    // 批量从网卡 ring buffer 取包
    while (rx_count < budget) {
        struct sk_buff *skb = my_rx(dev);
        if (!skb) break;
        netif_receive_skb(skb);   // 上交协议栈
        rx_count++;
    }

    if (rx_count < budget) {
        // 取完了,重开中断
        napi_complete_done(napi, rx_count);
        my_enable_irq(dev);
    }
    return rx_count;
}

static irqreturn_t my_rx_irq(int irq, void *dev_id) {
    struct my_dev *dev = dev_id;
    my_disable_irq(dev);            // 关中断
    napi_schedule(&dev->napi);      // 调度轮询
    return IRQ_HANDLED;
}

NAPI 是现代网卡标配

裸机网络可能纯中断(每包中断)。Linux NAPI 中断+轮询混合,高流量不中断风暴。你写网卡驱动(以太网 MAC)必须用 NAPI。这是 05 篇 softirq 在网络的应用(NET_RX_SOFTIRQ)。


七、netfilter:防火墙钩子⭐

7.1 netfilter 是什么

  • 内核网络栈的钩子框架
  • 在包路径关键点插入回调,可改包/丢包/NAT
  • iptables/nftables 基于 netfilter

7.2 五个钩子点

复制代码
包路径:
  收包 → PREROUTING → 路由判断 ─┬─→ 本机 → INPUT → 协议栈 → 应用
                              │
                              └─→ 转发 → FORWARD → POSTROUTING → 发出
  应用 → OUTPUT → POSTROUTING → 发出

钩子点:
  NF_INET_PRE_ROUTING     收包早期(路由前)
  NF_INET_LOCAL_IN        进本机
  NF_INET_FORWARD         转发
  NF_INET_LOCAL_OUT       本机发出
  NF_INET_POST_ROUTING    发出最后

7.3 注册钩子

c 复制代码
static struct nf_hook_ops my_hook = {
    .hook     = my_hook_fn,
    .pf       = NFPROTO_IPV4,
    .hooknum  = NF_INET_LOCAL_IN,
    .priority = NF_IP_PRI_FIRST,
};

nf_register_net_hook(&init_net, &my_hook);

unsigned int my_hook_fn(void *priv, struct sk_buff *skb,
                       const struct nf_hook_state *state) {
    // 检查包,返回:
    // NF_ACCEPT: 接受,继续
    // NF_DROP:   丢弃
    // NF_STOLEN: 拿走(自己处理)
    return NF_ACCEPT;
}

7.4 iptables/nftables

  • 用户态工具,规则转成 netfilter 钩子
  • 表(filter/nat/mangle/raw)+ 链(INPUT/OUTPUT/FORWARD...)
bash 复制代码
iptables -A INPUT -p tcp --dport 22 -j ACCEPT   # 允许 SSH
iptables -A INPUT -j DROP                        # 其余丢弃

嵌入式视角:netfilter 在嵌入式做过滤/NAT

嵌入式 Linux 做网关/路由器,用 iptables 配 NAT(masquerade)/端口过滤。netfilter 是底层钩子,iptables 是前端。你 BSP 配网络策略,懂 netfilter 钩子点能精确定位包在哪被处理。


八、路由

8.1 路由表

bash 复制代码
ip route       # 看路由表
# default via 192.168.1.1 dev eth0
# 192.168.1.0/24 dev eth0 proto kernel

8.2 路由查找

  • 每个发包查路由表,决定下一跳 + 出接口
  • FIB(Forwarding Information Base)表
  • 路由缓存(已废弃,现在用 LC-Trie)

8.3 路由策略(policy routing)

  • 多表路由(按源地址/ToS 选表)
  • ip rule 管理规则

九、网络性能优化

9.1 关键优化

优化 作用
NAPI 收包中断+轮询,防风暴
sk_buff 零拷贝 加头不 memcpy
TSO/GSO TCP 分段卸载到网卡(发大包,网卡分)
LRO/GRO 收包合并(网卡合并小包成大包)
checksum offload 校验和硬件算
RSS 多队列,每核收包(配 blk-mq 思想)
XDP eXpress Data Path,ebpf 在网卡层处理(超快)

9.2 多队列(RSS)

  • 现代网卡多 RX/TX 队列,每队列一中断,绑不同核
  • 并行收发,性能线性扩展
  • /proc/interrupts 看每队列中断分布

9.3 XDP(高性能路径)

  • eBPF 程序在网卡驱动收包最早点运行
  • 包不过协议栈,直接处理(丢/改/转发)
  • DDoS 防护/负载均衡利器

十、网络监控

10.1 基础

bash 复制代码
ifconfig / ip -s link      # 接口统计
cat /proc/net/dev           # 每接口收发字节数/包数/错误
ss -tlnp                    # 看 socket(替代 netstat)
netstat -i                  # 接口统计

10.2 性能

bash 复制代码
ethtool -S eth0             # 网卡详细统计
ethtool -g eth0             # ring buffer 大小
ethtool -l eth0             # 多队列
tcpdump -i eth0             # 抓包(16 篇)
iperf3                      # 带宽测

10.3 调路由/防火墙

bash 复制代码
ip route                    # 路由
ip rule                     # 策略路由
iptables -L -n -v           # 防火墙规则+计数
conntrack -L                # 连接跟踪

十一、对照总表

概念 lwIP/裸机 Linux 网络栈
socket socket-like socket fd(一切皆文件)
数据包 pbuf sk_buff(指针加头)
网卡抽象 自己结构 net_device
收包 中断 NAPI(中断+轮询)
防火墙 无 netfilter 钩子
路由 自己表 FIB + 多表
拥塞控制 简化 完整 TCP 拥塞
多核 单核 RSS 多队列并行
卸载 无 TSO/GSO/checksum
抓包 自己写 tcpdump

十二、本篇小结

  • 网络栈分层:socket(VFS)→ TCP/IP → netfilter → net_device → 硬件
  • socket:网络端点,fd 表示,也是文件(VFS 抽象);struct socket(VFS层)+ struct sock(协议层)
  • sk_buff:数据包,指针操作加头(push)/去头(pull),零拷贝思想,网络栈灵魂
  • net_device:网卡抽象,ndo_start_xmit 发包;alloc_netdev + register_netdev
  • NAPI:中断+轮询混合收包,第一个包中断→关中断轮询批量取→取完开中断,防中断风暴,网卡驱动必备
  • netfilter:五钩子点(PRE_ROUTING/LOCAL_IN/FORWARD/LOCAL_OUT/POST_ROUTING),iptables/nftables 前端
  • 发包:send → TCP/IP → netfilter → dev_queue_xmit → ndo_start_xmit → 网卡
  • 收包:网卡中断 → NAPI 轮询 → netif_receive_skb → 协议栈 → socket 队列
  • 优化:NAPI/sk_buff 零拷贝/TSO/GSO/checksum offload/RSS 多队列/XDP
  • 监控:ifconfig/ip/ss/ethtool/tcpdump/iperf3

速查表

想干啥 用什么
看接口 ip link / ifconfig
看接口统计 ip -s link / cat /proc/net/dev
看 socket ss -tlnp
看路由 ip route
看防火墙 iptables -L -n -v
看网卡统计 ethtool -S eth0
看 ring buffer ethtool -g eth0
抓包 tcpdump -i eth0
带宽测 iperf3
配 IP ip addr add 192.168.1.10/24 dev eth0
配默认网关 ip route add default via 192.168.1.1
改 MAC ip link set dev eth0 address xx:xx
注册网卡驱动 alloc_netdev + register_netdev
收包 NAPI napi_schedule + napi_poll
发包 ndo_start_xmit(skb, dev)
netfilter 钩子 nf_register_net_hook

💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的 点赞 👍 和 收藏 ⭐。

如果你在调试过程中遇到了其他问题,欢迎在 评论区 💬 留言,我们一起探讨。也欢迎 关注 👀 我,一起交流底层开发的那些事儿。


相关推荐
沫璃染墨3 小时前
《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》
linux·c++·安全·设计模式·策略模式
工作10年+,存储芯片行业3 小时前
Linux NVMe 中断排查与性能优化:CPU 亲和性
linux·运维·服务器·windows·性能优化·ssd·pcie
wuminyu3 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++
快乐の番薯4 小时前
卷王问卷考试系统自动化功能测试
运维·功能测试·自动化
她说彩礼65万4 小时前
C语言 堆区和栈区
java·linux·c语言
一号弯4 小时前
装完LINUX,请先新建日常用户
linux·运维·服务器
驭渊的小故事4 小时前
linux 基础命令 + git 仓库创建和配置命令
linux·git
ShineWinsu5 小时前
对于Redis:主从复制的解析
linux·数据库·c++·redis·缓存·面试·主从复制
Lsetea5 小时前
OpenSSL verify报unable to get issuer certificate:error 2与partial_chain排查
linux·https·ssl证书·openssl·证书链