对比下蓝牙的数据流:
这里梳理下wifi的
注意,wifi子系统分为控制路径和数据路径,不要搞混了关于控制路径,参考这篇:
下行数据流
WiFi 子系统架构回顾
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间应用程序 (如 iw, wpa_supplicant) │ │ ↓ │ │ 内核: nl80211/cfg80211 (配置与管理层) │ │ ↓ │ │ 内核: mac80211 (软件MAC层) │ │ ↓ │ │ 内核: 设备驱动 (如 ath9k, iwlwifi) │ │ ↓ │ │ 硬件设备 │ └─────────────────────────────────────────────────────────────────────┘关键理解 :这个架构图主要展示的是控制路径 (配置、连接管理)。对于数据路径(实际收发网络数据),还需要叠加完整的网络协议栈。
而数据路径,就是常规的socket编程
📡 完整的数据下行路径
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 网络应用 (浏览器 / curl / ping) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ socket() │ ├─────────────────────────────────────────────────────────────────────┤ │ 内核网络协议栈 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 传输层 (TCP/UDP) - 分段、拥塞控制 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 网络层 (IP) - 路由、分片、Netfilter │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 邻居层 (ARP) - MAC 地址解析 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 网络设备层 - qdisc 排队、dev_queue_xmit() │ │ │ └─────────────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────────────┤ │ WiFi 子系统 (你的架构图) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ cfg80211 (配置层) - 本例中不直接参与数据路径 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ mac80211 (软件MAC层) - 核心处理 │ │ │ │ - 802.3 → 802.11 帧转换 │ │ │ │ - 速率控制、分片、加密、聚合 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 设备驱动 (ath9k/iwlwifi) - 硬件操作 │ │ │ │ - 填充发送描述符 │ │ │ │ - DMA 映射 │ │ │ │ - 写 Tx Ring,通知硬件 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ USB/PCIe/SDIO │ ├─────────────────────────────────────────────────────────────────────┤ │ 硬件层 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ WiFi 芯片 - 空中发送 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
📦 逐层详解
第一层:用户空间应用程序
// 应用程序发送数据 int sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, ...); send(sock, "Hello WiFi", 10, 0);这一层产生的是纯应用数据,完全不知道下面会经过 WiFi。
第二层:内核网络协议栈(TCP/IP)
这是 WiFi 与蓝牙的关键区别------WiFi 数据经过完整的 TCP/IP 栈:
// net/ipv4/tcp.c - TCP 层 int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) { // 1. 拷贝用户数据到内核 // 2. 根据 MSS 分段 // 3. 添加 TCP 头 // 4. 加入发送队列 tcp_push(sk, flags, mss_now, nonagle); } // net/ipv4/ip_output.c - IP 层 int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl) { // 1. 添加 IP 头 // 2. 路由查找 (决定从 wlan0 出去) // 3. 分片处理 // 4. 调用邻居层 ip_local_out(net, sk, skb); }
第三层:网络设备层
// net/core/dev.c int dev_queue_xmit(struct sk_buff *skb) { struct net_device *dev = skb->dev; // wlan0 // 1. 选择发送队列 txq = netdev_pick_tx(dev, skb, NULL); // 2. 应用 qdisc 排队规则 (如 fq_codel) qdisc = rcu_dereference_bh(txq->qdisc); // 3. 入队并调度 return qdisc->enqueue(skb, qdisc, &to_free); }
第四层:cfg80211(配置层)
重要 :在数据路径中,cfg80211 不直接参与 。它主要负责:
设备注册 (wiphy_register)
配置管理 (扫描、连接、密钥设置)
监管合规
数据路径绕过 cfg80211,直接从 net_device 进入 mac80211。
// net/mac80211/iface.c static const struct net_device_ops ieee80211_dataif_ops = { .ndo_start_xmit = ieee80211_subif_start_xmit, // 数据入口! .ndo_open = ieee80211_open, .ndo_stop = ieee80211_stop, };
第五层:mac80211(核心处理)
这是 WiFi 数据路径的最关键层:
5.1 入口函数
// net/mac80211/tx.c static int ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ethhdr *ehdr = (struct ethhdr *)skb->data; // 1. 检查接口状态 // 2. 处理 VLAN 和桥接 // 3. 调用核心发送函数 return ieee80211_xmit(sdata, skb, IEEE80211_TX_CTL_SEND_AFTER_DTIM); }5.2 802.3 → 802.11 帧转换
// 帧转换的关键逻辑 static void ieee80211_8023_to_80211(struct sk_buff *skb, struct ieee80211_sub_if_data *sdata) { struct ethhdr *ehdr = (struct ethhdr *)skb->data; u8 *da = ehdr->h_dest; // 目的 MAC u8 *sa = ehdr->h_source; // 源 MAC // 根据接口类型决定 802.11 地址字段填充方式 // STA 模式 (连接到 AP): // Addr1 = AP 的 MAC (接收端) // Addr2 = 自己的 MAC (发送端) // Addr3 = 目的 MAC (最终目标) // AP 模式: // Addr1 = 目的 MAC (接收端) // Addr2 = 自己的 MAC (发送端) // Addr3 = 源 MAC (原始发送者) // IBSS (Ad-hoc) 模式: // Addr1 = 目的 MAC // Addr2 = 自己的 MAC // Addr3 = BSSID }5.3 发送处理链
// net/mac80211/tx.c static void invoke_tx_handlers(struct ieee80211_tx_data *tx) { // 依次调用各处理器 ieee80211_tx_h_rate_ctrl(tx); // 1. 速率选择 (minstrel) ieee80211_tx_h_fragment(tx); // 2. 分片 (如果超过阈值) ieee80211_tx_h_encrypt(tx); // 3. 加密 (WEP/TKIP/CCMP) ieee80211_tx_h_amsdu(tx); // 4. A-MSDU 聚合 ieee80211_tx_h_sequence(tx); // 5. 分配序列号 ieee80211_tx_h_ampdu(tx); // 6. A-MPDU 聚合 ieee80211_tx_h_check_control(tx); // 7. 控制字段检查 ieee80211_tx_h_driver(tx); // 8. 调用驱动 }5.4 速率控制
// net/mac80211/rc80211_minstrel.c static void minstrel_tx_status(struct ieee80211_hw *hw, struct ieee80211_tx_status *st) { // 根据发送成功/失败,调整速率 // 动态选择最优速率 (MCS 索引) }
第六层:设备驱动
// drivers/net/wireless/ath/ath9k/xmit.c static void ath9k_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb) { struct ath_softc *sc = hw->priv; struct ath_common *common = ath9k_hw_common(sc->sc_ah); // 1. 填充硬件发送描述符 // 2. DMA 映射 (将 skb 数据映射到硬件可访问的内存) // 3. 将描述符加入发送环 (Tx Ring) // 4. 更新发送环写指针,通知硬件有数据待发 ath_tx_start(sc, skb); }
📊 数据包格式变化追踪
层级 数据包格式 示例数据 应用层 应用数据 "Hello WiFi"TCP 层 TCP 段 TCP Header + "Hello WiFi"IP 层 IP 包 IP Header + TCP Header + "Hello WiFi"以太网层 802.3 帧 ETH Header + IP Header + TCP Header + "Hello WiFi"mac80211 802.11 帧 802.11 Header + LLC/SNAP + IP Header + TCP Header + "Hello WiFi"硬件 无线电帧 经过调制的无线电信号
🔄 与蓝牙下行数据流的对比
WiFi 下行路径: 应用 → TCP/UDP → IP → 邻居 → qdisc → netdev → mac80211 → 驱动 → 硬件 ↑ ↑ 完整的网络协议栈 WiFi 特有 蓝牙下行路径: 应用 → RFCOMM/ATT → L2CAP → HCI → 驱动 → 硬件 ↑ 独立蓝牙协议栈
维度 WiFi 蓝牙 网络协议栈 ✅ 完整 TCP/IP ❌ 独立蓝牙协议 传输层 TCP/UDP (内核) 应用层处理 网络层 IP (内核) 无 数据接口 netdev (wlan0) HCI Socket / RFCOMM TTY 核心处理层 mac80211 L2CAP / HCI
💡 关键总结
控制路径 vs 数据路径
nl80211/cfg80211:只负责控制(扫描、连接、配置)
mac80211:负责数据路径的核心处理WiFi 是完整的网络设备
数据经过 TCP/IP 协议栈
通过
net_device接口与网络栈交互mac80211 的核心职责
802.3 → 802.11 帧转换
速率控制、分片、加密、聚合
为驱动提供统一接口 (
ieee80211_ops)驱动层的职责
填充硬件描述符
DMA 映射
通知硬件发送
netdev (wlan0)是啥
netdev是 network device 的缩写,wlan0是 Linux 系统中无线网络设备的名称 。它是 WiFi 子系统与 Linux 网络协议栈之间的关键接口。
🎯 核心概念
什么是 netdev?
netdev 是 Linux 内核中所有网络设备的抽象层。无论是有线网卡(eth0)、无线网卡(wlan0)、虚拟网桥(br0)、环回接口(lo),在内核中都被抽象为
struct net_device。
// include/linux/netdevice.h struct net_device { char name[IFNAMSIZ]; // 设备名称: "wlan0" unsigned long state; // 设备状态 struct net_device_ops *netdev_ops; // 设备操作函数集 struct net_device_stats stats; // 统计信息 // WiFi 特有 const struct ieee80211_ops *ieee80211_ops; // mac80211 操作 // ... 更多字段 };wlan0 是什么?
wlan0是无线网络设备的典型命名:
wlan= Wireless LAN
0= 第一个无线网卡(第二个是 wlan1,以此类推)
📡 netdev 在 WiFi 架构中的位置
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ ping 8.8.8.8 │ curl │ iperf │ tcpdump -i wlan0 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ socket API (AF_INET) │ ├─────────────────────────────────────────────────────────────────────┤ │ 内核网络协议栈 (TCP/IP) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ TCP / UDP → IP → 路由 → ARP │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────┐ │ │ │ net_device │ ← 抽象层 │ │ │ (struct net_device) │ │ └────────┬────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ wlan0 (无线网卡) │ │ │ │ - netdev_ops: ndo_start_xmit = ieee80211_subif_start_xmit │ │ │ │ - 私有数据: 指向 mac80211 的 sdata │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ mac80211 (软件MAC层) │ │ ↓ │ │ WiFi 驱动 (ath9k/iwlwifi) │ │ ↓ │ │ 硬件 (WiFi 芯片) │ └─────────────────────────────────────────────────────────────────────┘
🔌 netdev 的关键作用
1. 向上:与网络协议栈对接
wlan0向网络协议栈呈现一个标准的以太网接口:
// 用户空间看到的 wlan0 $ ip addr show wlan0 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP inet 192.168.1.100/24 brd 192.168.1.255 scope global wlan0 valid_lft forever preferred_lft forever // 应用程序使用标准 socket API int sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, ...); send(sock, data, len); // 数据通过 wlan0 发出2. 向下:连接 mac80211
wlan0的netdev_ops将网络栈的发送请求转发给mac80211:
// net/mac80211/iface.c static const struct net_device_ops ieee80211_dataif_ops = { .ndo_start_xmit = ieee80211_subif_start_xmit, // 发送入口 .ndo_open = ieee80211_open, // 打开设备 .ndo_stop = ieee80211_stop, // 关闭设备 .ndo_set_mac_address = ieee80211_change_mac, // 设置 MAC 地址 .ndo_get_stats64 = ieee80211_get_stats64, // 获取统计信息 }; // 当网络栈有数据要发送时,调用 ndo_start_xmit static int ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev) { // 这里 skb 包含完整的 802.3 以太网帧 // 需要转换为 802.11 帧并发送 struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); return ieee80211_xmit(sdata, skb, ...); }
📋 netdev 的关键字段
struct net_device { // 基本信息 char name[IFNAMSIZ]; // "wlan0" unsigned int flags; // IFF_UP, IFF_BROADCAST, IFF_RUNNING // 操作函数集(关键!) const struct net_device_ops *netdev_ops; // 网络协议栈相关 unsigned int mtu; // 最大传输单元 (通常 1500) unsigned short type; // ARPHRD_ETHER (以太网类型) // MAC 地址 unsigned char perm_addr[MAX_ADDR_LEN]; unsigned char dev_addr[MAX_ADDR_LEN]; // 队列管理 struct netdev_queue *_tx; unsigned int num_tx_queues; // 统计信息 struct net_device_stats stats; // WiFi 特有:指向 mac80211 的私有数据 void *ieee80211_ptr; // struct wireless_dev * // 驱动私有数据 void *ml_priv; // 驱动自定义数据 };
🏗️ wlan0 的创建过程
// mac80211 创建 net_device 的过程 int ieee80211_if_add(struct ieee80211_local *local, const char *name, unsigned char name_assign_type, struct wireless_dev **new_wdev, enum nl80211_iftype type, struct vif_params *params) { struct net_device *ndev; struct ieee80211_sub_if_data *sdata; // 1. 分配 net_device 结构体 ndev = alloc_netdev_mqs(sizeof(*sdata), name, name_assign_type, ieee80211_if_setup, txqs, rxqs); // 2. 设置 netdev_ops ndev->netdev_ops = &ieee80211_dataif_ops; // 3. 设置设备类型为以太网 ndev->type = ARPHRD_ETHER; // 4. 设置 MTU ndev->mtu = IEEE80211_MAX_DATA_LEN; // 5. 关联 mac80211 私有数据 sdata = netdev_priv(ndev); sdata->dev = ndev; ndev->ieee80211_ptr = &sdata->wdev; // 6. 注册到网络系统 register_netdevice(ndev); // 用户空间就能看到 wlan0 了 }
🔍 如何查看和使用 netdev
查看 wlan0 信息
# 查看所有网络设备 ip link show 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP # 查看 wlan0 详细信息 ip addr show wlan0 ethtool -i wlan0 # 查看驱动信息 cat /sys/class/net/wlan0/statistics/tx_bytes # 查看 qdisc(排队规则) tc qdisc show dev wlan0配置 wlan0
# 配置 IP 地址 ip addr add 192.168.1.100/24 dev wlan0 # 启用设备 ip link set wlan0 up # 添加路由 ip route add default via 192.168.1.1 dev wlan0监控 wlan0
# 抓包 tcpdump -i wlan0 -e -v # 查看统计 watch -n 1 'cat /sys/class/net/wlan0/statistics/tx_bytes' # 查看连接信息 iw dev wlan0 link
💡 netdev 与蓝牙的对比
维度 WiFi (netdev) 蓝牙 设备名称 wlan0,wlp2s0hci0(HCI设备)设备类型 网络设备 ( ARPHRD_ETHER)字符设备 / HCI 设备 数据接口 send()/write()通过 socketRFCOMM TTY ( /dev/rfcomm0) 或 L2CAP socketIP 协议栈 ✅ 完整支持 ❌ 需要 BNEP 桥接 应用程序 curl, ping, 浏览器 bluetoothctl, 串口程序
🎯 总结
问题 答案 netdev 是什么? Linux 内核中所有网络设备的抽象 wlan0 是什么? 无线网卡的具体实例,是 netdev 的一种 wlan0 的作用? 连接网络协议栈和 WiFi 子系统的桥梁 数据如何经过 wlan0? 网络栈 → ndo_start_xmit→ieee80211_subif_start_xmit→ mac80211用户如何与 wlan0 交互? ip,iw,ifconfig,tcpdump, 以及所有 socket 应用一句话总结 :
wlan0是一个 net_device,它让 WiFi 硬件看起来就像一个普通的以太网卡,使得所有网络应用(curl、ping、浏览器)无需修改就能通过 WiFi 上网。这是 Linux 网络设计"一切皆文件"和"统一抽象"哲学的体现。
区分socket和netlink
用户空间与内核的通信方式取决于具体场景:配置/控制用 Netlink,数据传输用 Socket。两者并不矛盾。
🎯 核心答案:两种不同的通信路径
WiFi 子系统有两条完全不同的用户-内核通信路径:
路径 用途 接口 数据内容 控制路径 配置、管理、扫描、连接 nl80211(Netlink)命令和事件 数据路径 实际网络数据传输 socket(AF_INET)TCP/IP 数据包
📡 一、控制路径:Netlink (nl80211)
用于配置和管理 WiFi 设备:
// 用户空间工具 iw 通过 Netlink 发送扫描命令 $ iw dev wlan0 scan // 内部实现 struct nl_sock *sk = nl_socket_alloc(); genl_connect(sk); // Netlink 连接 // 构造 nl80211 命令 nl80211_send_scan_request(sk, ...); // 内核 nl80211 接收 // net/wireless/nl80211.c static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) { // 执行扫描... }控制路径的数据流向:
iw (用户空间) → Netlink (nl80211) → cfg80211 (内核) → mac80211 → 驱动
📦 二、数据路径:Socket (AF_INET)
用于实际数据传输(上网):
// 应用程序发送数据(如 curl、浏览器) int sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, ...); send(sock, "GET /", 5); // 这里不是 Netlink,是普通的网络 socket!数据路径的数据流向:
curl (用户空间) → socket (AF_INET) → TCP/IP 协议栈 → netdev (wlan0) → mac80211 → 驱动
🏗️ 完整的 WiFi 用户-内核通信架构
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────────┐ │ │ │ 网络应用 │ │ 管理工具 │ │ │ │ (curl / ping / 浏览器) │ │ (iw / wpa_supplicant) │ │ │ └───────────┬─────────────┘ └──────────────┬──────────────┘ │ │ │ │ │ │ │ 数据路径 │ 控制路径 │ │ ↓ ↓ │ │ ┌─────────────────────────┐ ┌─────────────────────────────┐ │ │ │ socket(AF_INET) │ │ Netlink (nl80211) │ │ │ │ (TCP/UDP 协议) │ │ (通用 Netlink 协议) │ │ │ └───────────┬─────────────┘ └──────────────┬──────────────┘ │ ├──────────────┼────────────────────────────────┼─────────────────────┤ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 内核空间 │ │ │ │ │ │ │ │ ┌─────────────────────┐ ┌─────────────────────────┐ │ │ │ │ │ TCP/IP 协议栈 │ │ nl80211/cfg80211 │ │ │ │ │ │ - TCP │ │ - 扫描 │ │ │ │ │ │ - IP │ │ - 连接 │ │ │ │ │ │ - 路由 │ │ - 密钥管理 │ │ │ │ │ └──────────┬──────────┘ └──────────┬──────────────┘ │ │ │ │ ↓ ↓ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ net_device (wlan0) │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ │ │ mac80211 │ │ │ │ │ │ │ │ - 802.3 → 802.11 转换 │ │ │ │ │ │ │ │ - 速率控制、加密、聚合 │ │ │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ↓ │ │ │ │ WiFi 驱动 (ath9k/iwlwifi) │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
🔍 为什么需要两条路径?
1. 数据路径为什么用 Socket?
因为 WiFi 传输的是普通网络数据(TCP/IP),必须走标准的网络协议栈:
// 数据路径:应用程序的标准网络通信 curl http://example.com ↓ socket(AF_INET) ← 标准 socket,不是 Netlink ↓ TCP/IP 协议栈 ↓ wlan0 (net_device)2. 控制路径为什么用 Netlink?
因为控制命令需要灵活、异步、多播等特性:
需求 Netlink 优势 异步通知 内核可以主动推送事件(如扫描结果) 多播 多个程序可以同时监听事件 扩展性 可以灵活添加新命令 安全性 支持权限检查
// 控制路径:内核主动推送事件 内核发现新 AP → Netlink 多播 → wpa_supplicant 收到 → 自动连接
📊 两种通信方式对比
维度 数据路径 (socket) 控制路径 (Netlink) 协议族 AF_INET/AF_INET6AF_NETLINK用途 传输用户数据 (上网) 配置和管理 数据内容 TCP/IP 包 nl80211 命令/事件 方向 主要是用户→内核 双向 (内核可主动推送) 接口 socket(),send(),recv()libnl,genl应用 curl, ping, 浏览器 iw, wpa_supplicant
💡 类比理解
把 WiFi 子系统想象成一个出租车公司:
角色 类比 说明 数据路径 乘客坐车 应用程序通过 socket 发送数据 控制路径 调度中心 iw/wpa_supplicant 通过 Netlink 配置网络 wlan0 出租车 net_device 负责数据传输 nl80211 对讲机 控制命令通道 乘客上车后,司机通过对讲机 与调度中心通信(控制路径),同时开车送乘客(数据路径)。两者是独立的。
🛠️ 验证两种路径
查看控制路径(Netlink)
# 监控 Netlink 消息 $ strace -e sendto iw dev wlan0 scan 2>&1 | grep netlink sendto(3, {{len=40, type=GENL_ID_CTRL...}, ...}, ...) # 查看 Netlink 连接 $ ss -A netlink -p nl UNCONN 0 0 genl:0 * users:(("iw",pid=1234,fd=3))查看数据路径(Socket)
# 查看网络连接 $ ss -tunp tcp ESTAB 0 0 192.168.1.100:45678 8.8.8.8:443 users:(("curl",pid=5678,fd=3)) # ↑ 这是 AF_INET socket,不是 Netlink
🎯 总结
问题 答案 用户空间和内核用什么通信? 取决于场景:控制用 Netlink,数据用 Socket 为什么不用统一的方式? 两者设计目标不同:Netlink 适合控制,Socket 适合数据传输 wlan0 是什么? net_device,数据路径的入口 nl80211 是什么? Netlink 协议,控制路径的入口 两者会混淆吗? 不会,它们服务于完全不同的目的 一句话总结 :WiFi 子系统有两条独立的用户-内核通信通道 ------控制通道用 Netlink (nl80211) 来配置和管理设备,数据通道用标准 Socket (AF_INET) 来传输实际的网络数据。这是"控制与数据分离"设计思想的典型体现。
上行数据流
上行数据流指的是从 WiFi 硬件到应用程序的数据路径。与下行对称但方向相反,经过完整的网络协议栈处理。
🎯 上行数据流全景图
┌─────────────────────────────────────────────────────────────────────┐ │ 硬件层 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ WiFi 芯片接收到空中数据包,产生中断 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ DMA / 中断 │ ├─────────────────────────────────────────────────────────────────────┤ │ 驱动层 (ath9k/iwlwifi) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 中断处理 → 从 Rx Ring 取数据 → 填充 skb → ieee80211_rx() │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ mac80211 (软件MAC层) │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ - 802.11 → 802.3 帧转换 │ │ │ │ - 解密、去重、重组 │ │ │ │ - 监控接口拷贝 (monitor) │ │ │ │ - 提交给网络协议栈 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ net_device (wlan0) │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 网络协议栈 (TCP/IP) │ │ │ │ - 邻居层 (ARP) │ │ │ │ - IP 层 (重组、Netfilter) │ │ │ │ - TCP/UDP 层 (重组、流量控制) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ socket │ ├─────────────────────────────────────────────────────────────────────┤ │ 用户空间 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 应用程序 (浏览器 / curl / ping) 通过 recv() 读取数据 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
📥 第一步:硬件 → 驱动层
硬件中断触发
当 WiFi 芯片收到空中数据包时,通过 PCIe/USB/SDIO 触发中断:
// drivers/net/wireless/ath/ath9k/pci.c static irqreturn_t ath9k_pci_irq_handler(int irq, void *dev) { struct ath_softc *sc = dev; // 1. 读取硬件中断状态 status = ath9k_hw_get_isr(sc->sc_ah, &isr); // 2. 如果是接收中断 if (isr & ATH9K_INT_RX) { // 调度接收任务 tasklet_schedule(&sc->rx_tasklet); } return IRQ_HANDLED; }驱动接收处理
// drivers/net/wireless/ath/ath9k/recv.c static void ath9k_rx_tasklet(struct tasklet_struct *t) { struct ath_softc *sc = from_tasklet(sc, t, rx_tasklet); struct sk_buff *skb; // 1. 从硬件 Rx Ring 读取数据 while (ath_rx_buf_link(sc, &skb)) { struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); // 2. 填充接收状态(信号强度、速率、频率等) rx_status->signal = rs->rs_rssi; rx_status->rate_idx = rs->rs_rate; rx_status->freq = curchan->center_freq; // 3. 提交给 mac80211 ieee80211_rx(sc->hw, skb); } }
⚙️ 第二步:mac80211 接收处理
接收入口
// net/mac80211/rx.c void ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb) { struct ieee80211_local *local = hw_to_local(hw); // 1. 加入接收队列 skb_queue_tail(&local->rx_skb_queue, skb); // 2. 调度接收工作 queue_work(local->workqueue, &local->rx_work); } static void ieee80211_rx_work(struct work_struct *work) { struct ieee80211_local *local = container_of(work, ...); while ((skb = skb_dequeue(&local->rx_skb_queue))) { // 调用接收处理链 ieee80211_rx_handlers(local, skb); } }接收处理链
// net/mac80211/rx.c static void ieee80211_rx_handlers(struct ieee80211_local *local, struct sk_buff *skb) { // 依次调用各处理器 ieee80211_rx_h_monitor(skb); // 1. 拷贝给监控接口 (tcpdump) ieee80211_rx_h_decrypt(skb); // 2. 解密 (WEP/TKIP/CCMP) ieee80211_rx_h_defragment(skb); // 3. 重组分片 ieee80211_rx_h_check(skb); // 4. 检查重复帧 ieee80211_rx_h_amsdu(skb); // 5. A-MSDU 解聚合 ieee80211_rx_h_data(skb); // 6. 数据处理 → 802.3 转换 ieee80211_rx_h_mgmt(skb); // 7. 管理帧处理 }802.11 → 802.3 帧转换
// net/mac80211/rx.c static int ieee80211_rx_h_data(struct ieee80211_rx_data *rx) { struct ieee80211_hdr *hdr = (void *)rx->skb->data; struct ethhdr *ehdr; // 1. 从 802.11 帧头提取源 MAC 和目的 MAC // - From DS / To DS 位决定地址字段含义 // STA 模式收到 AP 发来的帧: // Addr1 = 自己的 MAC (接收端) // Addr2 = AP 的 MAC (发送端) // Addr3 = 源 MAC (原始发送者) // 2. 构造 802.3 以太网帧头 ehdr = (struct ethhdr *)skb_push(rx->skb, ETH_HLEN); memcpy(ehdr->h_dest, dest, ETH_ALEN); // 目的 MAC memcpy(ehdr->h_source, src, ETH_ALEN); // 源 MAC ehdr->h_proto = htons(ETH_P_IP); // 协议类型 // 3. 移除 802.11 帧头,保留 802.3 帧头 + 数据 // 4. 提交给网络协议栈 netif_receive_skb(rx->skb); }
🌐 第三步:网络协议栈接收
netif_receive_skb 入口
// net/core/dev.c int netif_receive_skb(struct sk_buff *skb) { // 1. 经过 ptype 链表,根据协议类型分发 return __netif_receive_skb(skb); } static int __netif_receive_skb(struct sk_buff *skb) { // 2. 处理网桥 (如果存在) skb = skb_vlan_untag(skb); // 3. 调用协议处理器 (如 IP) return __netif_receive_skb_core(skb, false); }IP 层接收
// net/ipv4/ip_input.c int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { // 1. 检查 IP 头 // 2. 重组分片 // 3. 处理 IP 选项 // 4. Netfilter 钩子 (NF_INET_PRE_ROUTING) // 5. 路由查找,决定本地接收还是转发 if (ip_local_deliver) { // 本地接收 return ip_local_deliver(skb); } } int ip_local_deliver(struct sk_buff *skb) { // 1. 重组分片 // 2. Netfilter 钩子 (NF_INET_LOCAL_IN) // 3. 根据协议分发到 TCP/UDP return ip_local_deliver_finish(skb); }TCP/UDP 层接收
// net/ipv4/tcp_ipv4.c int tcp_v4_rcv(struct sk_buff *skb) { // 1. 查找 socket sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); // 2. 加入 socket 接收队列 if (!tcp_queue_rcv(sk, skb)) { // 3. 唤醒等待的进程 sk->sk_data_ready(sk); } } // net/ipv4/udp.c int udp_rcv(struct sk_buff *skb) { // UDP 接收处理 udp_queue_rcv_skb(sk, skb); }
🖥️ 第四步:用户空间接收
应用程序读取数据
// 用户空间应用程序 int sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, ...); char buf[1024]; int n = recv(sock, buf, sizeof(buf), 0); // 阻塞等待数据 printf("Received: %s\n", buf);内核返回数据
// net/socket.c SYSCALL_DEFINE3(recvmsg, int, fd, struct msghdr __user *, msg, unsigned int, flags) { struct socket *sock = sockfd_lookup(fd, &err); // 调用协议层的 recvmsg err = sock->ops->recvmsg(sock, msg, size, flags); // 将数据从内核拷贝到用户空间 if (msg->msg_controllen) err = copy_msghdr_from_user(umsg, msg); return err; }
📊 数据包格式变化(上行)
层级 数据包格式 方向 硬件 无线电信号 空中 → 硬件 驱动 802.11 帧 硬件 → mac80211 mac80211 802.11 帧 → 802.3 帧 转换 网络设备层 802.3 以太网帧 mac80211 → 网络栈 IP 层 IP 包 网络栈 → TCP/UDP TCP/UDP 层 TCP 段 / UDP 包 协议栈 → socket 用户空间 应用数据 socket → 应用程序
🔄 完整上行数据流时序图
应用程序网络协议栈mac80211驱动层WiFi硬件应用程序网络协议栈mac80211驱动层WiFi硬件收到数据包,产生中断从 Rx Ring 读取数据ieee80211_rx(skb)802.11 帧解析解密、去重、重组802.11 → 802.3 转换netif_receive_skb(skb)IP 层处理Netfilter 钩子TCP/UDP 层处理唤醒等待的进程recv() 系统调用拷贝数据到用户空间
🎯 WiFi 上行 vs 下行对比
维度 下行 (发送) 上行 (接收) 入口 应用 socket 硬件中断 出口 硬件空中发送 应用 socket 数据转换 802.3 → 802.11 802.11 → 802.3 关键函数 dev_queue_xmit()netif_receive_skb()驱动接口 ieee80211_ops->txieee80211_rx()流控 qdisc 排队 TCP 接收窗口 中断 发送完成中断 接收数据中断
🔍 调试技巧
1. 查看接收统计
# 查看网卡接收统计 cat /sys/class/net/wlan0/statistics/rx_bytes cat /sys/class/net/wlan0/statistics/rx_packets # 查看 mac80211 接收统计 cat /sys/kernel/debug/ieee80211/phy0/rx_stats2. 抓取数据包
# 在 wlan0 抓取 802.3 帧 tcpdump -i wlan0 -e -v # 在 monitor 接口抓取 802.11 帧 iw dev wlan0 set monitor other tcpdump -i mon0 -e -v3. 跟踪内核接收路径
# 跟踪接收函数 echo 'p:netif_receive_skb' >> /sys/kernel/debug/tracing/kprobe_events echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable cat /sys/kernel/debug/tracing/trace_pipe
💡 总结
问题 答案 上行入口是什么? 硬件中断 核心转换在哪里? mac80211 进行 802.11 → 802.3 转换 如何进入网络栈? netif_receive_skb()如何到达应用? TCP/UDP → socket → recv() 与下行对称吗? 是的,方向相反,处理逻辑对称 一句话总结 :WiFi 上行数据流是下行的逆过程------硬件收到 802.11 帧后,驱动调用
ieee80211_rx()交给 mac80211,经过解密、重组、802.11→802.3 转换后,通过netif_receive_skb()进入网络协议栈,最终通过 socket 到达应用程序。
对比:蓝牙的控制路径
蓝牙同样有控制路径和数据路径的分离 ,只是实现方式和 WiFi 有所不同。蓝牙的控制路径通过 HCI Socket 的特定通道实现,而不是像 WiFi 那样使用独立的 Netlink 协议。
🎯 蓝牙的控制路径 vs 数据路径
路径 用途 接口 数据内容 控制路径 设备管理、扫描、配对、连接 HCI Socket (HCI_CHANNEL_CONTROL / USER) HCI 命令和事件 数据路径 实际数据传输 RFCOMM Socket / L2CAP Socket / ATT 应用数据
📡 一、蓝牙控制路径架构
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 管理工具 │ 应用 │ │ │ │ (bluetoothctl / bluetoothd) │ (蓝牙耳机 / 文件传输) │ │ │ └───────────────────────────────┴─────────────────────────────┘ │ │ │ │ │ │ │ 控制路径 │ 数据路径 │ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ D-Bus (bluetoothd 对外接口) RFCOMM / L2CAP Socket │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ HCI Socket (AF_BLUETOOTH) │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────────┐ │ │ │ │ │ HCI_CHANNEL_ │ │ HCI_CHANNEL_RAW / L2CAP │ │ │ │ │ │ CONTROL / USER │ │ (数据通道) │ │ │ │ │ │ (控制通道) │ │ │ │ │ │ │ └─────────────────┘ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────────────┤ │ 内核空间 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ HCI 核心层 (hci_core.c) │ │ │ │ - 命令队列管理 │ │ │ │ - 事件分发 │ │ │ │ - 连接管理 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 蓝牙驱动 (btusb / hci_uart) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────────┤ │ 硬件层 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 蓝牙控制器 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
🔧 二、蓝牙控制路径的具体实现
1. HCI Socket 的控制通道
HCI Socket 通过不同的 channel 区分控制和数据:
// include/net/bluetooth/hci.h #define HCI_CHANNEL_RAW 0 // 原始 HCI (数据+控制混合) #define HCI_CHANNEL_USER 1 // 用户通道 (控制路径) #define HCI_CHANNEL_MONITOR 2 // 监控通道 #define HCI_CHANNEL_CONTROL 3 // 控制通道 (bluetoothd 使用)2. bluetoothd 使用控制通道
// bluetoothd 源码简化 int sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); struct sockaddr_hci addr = { .hci_family = AF_BLUETOOTH, .hci_dev = 0, // hci0 .hci_channel = HCI_CHANNEL_CONTROL, // 控制通道 }; bind(sk, (struct sockaddr *)&addr, sizeof(addr)); // 发送管理命令 struct hci_command_hdr cmd = { .opcode = htobs(HCI_OP_LE_SET_SCAN_ENABLE), .plen = 2, }; send(sk, &cmd, sizeof(cmd), 0);3. 内核中的控制路径处理
// net/bluetooth/hci_sock.c static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct hci_dev *hdev = hci_sock_dev(sk); // 根据 channel 区分处理 switch (hci_pi(sk)->channel) { case HCI_CHANNEL_CONTROL: // 控制通道:管理命令 return hci_sock_sendmsg_control(sk, msg, len, hdev); case HCI_CHANNEL_USER: // 用户通道:完整控制权限 return hci_sock_sendmsg_user(sk, msg, len, hdev); case HCI_CHANNEL_RAW: // 原始通道:可发送任意 HCI 命令 return hci_sock_sendmsg_raw(sk, msg, len, hdev); } }
📊 三、蓝牙 vs WiFi 控制路径对比
维度 WiFi 控制路径 蓝牙控制路径 接口协议 Netlink (nl80211) HCI Socket (AF_BLUETOOTH) 通道区分 不同的 Netlink 协议族 HCI Socket 的不同 channel 用户空间工具 iw, wpa_supplicant bluetoothctl, bluetoothd 内核组件 nl80211, cfg80211 hci_sock.c, hci_core.c 命令格式 nl80211 命令 HCI 命令 (操作码 + 参数) 事件推送 Netlink 多播 HCI 事件通过 socket 返回 权限管理 Netlink 权限检查 HCI Socket 权限检查
🔄 四、蓝牙控制路径的典型流程
以 BLE 扫描为例,展示控制路径:
用户: bluetoothctl scan on ↓ bluetoothctl 通过 D-Bus 调用 bluetoothd ↓ bluetoothd 构造 HCI 命令 ↓ bluetoothd 通过 HCI Socket (HCI_CHANNEL_CONTROL) 发送 ↓ 内核 hci_sock.c 接收,根据 channel 识别为控制命令 ↓ hci_core.c 处理命令,加入命令队列 ↓ 驱动 (btusb) 发送命令给硬件 ↓ 硬件开始扫描 ↓ 硬件收到扫描结果,触发中断 ↓ 驱动接收数据,调用 hci_recv_frame() ↓ hci_core.c 识别为 HCI 事件 ↓ 通过 HCI Socket 返回给 bluetoothd ↓ bluetoothd 解析事件,通过 D-Bus 发送给 bluetoothctl ↓ bluetoothctl 显示扫描到的设备
📝 五、验证蓝牙控制路径
1. 查看 HCI Socket 连接
# 查看所有 HCI Socket $ ss -A bluetooth -p Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port hci UNCONN 0 0 hci0:3 *:* users:(("bluetoothd",pid=789,fd=10)) # ↑ channel 3 = HCI_CHANNEL_CONTROL $ ss -A bluetooth -p | grep "hci0:" hci UNCONN 0 0 hci0:1 *:* users:(("bluetoothctl",pid=1234,fd=3)) # ↑ channel 1 = HCI_CHANNEL_USER2. 跟踪控制命令
# 使用 strace 跟踪 bluetoothd 的 HCI Socket 操作 $ strace -e sendto -p $(pidof bluetoothd) sendto(10, "\x01\x0c\x20\x02\x01\x00", 6, 0, NULL, 0) = 6 # ^^ ^^^^^^ ^^ ^^^^^ # | | | └─ 参数: 开启扫描 # | | └──── 参数长度: 2 # | └─────────── 操作码: 0x200C (LE Set Scan Enable) # └────────────── 包类型: HCI_COMMAND_PKT3. 使用 btmon 监控控制路径
# btmon 使用 HCI_CHANNEL_MONITOR 监控所有 HCI 流量 $ btmon < HCI Command: LE Set Scan Enable (0x08|0x200C) plen 2 # 控制命令 Scanning: Enabled (0x01) > HCI Event: LE Meta Event (0x3e) plen 12 # 控制事件 LE Advertising Report (0x02)
💡 六、总结
问题 答案 蓝牙有控制路径吗? ✅ 有,通过 HCI Socket 的控制通道实现 控制路径用什么接口? HCI Socket (AF_BLUETOOTH) + HCI_CHANNEL_CONTROL/USER 数据路径用什么接口? RFCOMM Socket / L2CAP Socket / ATT 与 WiFi 控制路径有何不同? WiFi 用 Netlink,蓝牙用 HCI Socket 的 channel 区分 用户空间控制工具? bluetoothctl, bluetoothd, btmgmt 一句话总结 :蓝牙的控制路径和数据路径共用 HCI Socket ,但通过 channel 字段区分------控制通道 (HCI_CHANNEL_CONTROL/USER) 传输管理命令和事件,数据通道 (RFCOMM/L2CAP Socket) 传输实际应用数据。这种设计与 WiFi 用 Netlink 分离控制与数据的思路异曲同工。
