本篇定位 :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)
-
取完或配额到 → 重开中断
- 网卡收包 → 中断
- 驱动 ISR:关中断,napi_schedule
- softirq(NET_RX_SOFTIRQ)调度
- napi_poll 批量取包(一次取 budget 个)
- 取完 → 重开中断
- 没取完 → 留 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 |
💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的 点赞 👍 和 收藏 ⭐。
如果你在调试过程中遇到了其他问题,欢迎在 评论区 💬 留言,我们一起探讨。也欢迎 关注 👀 我,一起交流底层开发的那些事儿。