2、WiFi协议栈架构:OSI模型与WiFi协议栈映射、Linux无线子系统架构、mac80211/cfg80211框架解析

做WiFi适配,第一件事不是看芯片手册,而是搞清楚协议栈长什么样。我见过太多人一上来就追着寄存器啃,结果连数据包在系统里怎么流转都没弄明白。今天咱们就把这个地基打牢。

2.1 OSI模型与WiFi协议栈的映射关系

OSI七层模型,教科书上都讲过。但实际WiFi协议栈只用了下面几层。说白了,WiFi就是物理层+数据链路层的无线化实现。

OSI层 WiFi对应实体 核心功能
应用层 用户程序 (浏览器/SSH等) 业务数据生成
传输层 TCP/UDP (内核协议栈) 端到端可靠传输
网络层 IP (内核协议栈) 路由与寻址
数据链路层 mac80211 + 驱动 帧封装、介质访问、重传
物理层 射频芯片 + 天线 调制解调、信号收发

注意看,数据链路层在WiFi里被拆成了两部分:MAC层管理(mac80211处理)和MAC层硬件相关(驱动处理)。这个拆分是Linux无线子系统的精髓。

关键认知:WiFi的MAC层远比以太网复杂。以太网只管发帧,WiFi还要管信道扫描、关联认证、省电管理、速率选择。这些逻辑如果全塞进驱动,代码会炸掉。所以Linux把公共逻辑抽出来,形成了mac80211框架。

2.2 Linux无线子系统整体架构

我个人习惯把Linux无线子系统看成三层:

  • 上层:用户空间工具(iw, wpa_supplicant, hostapd)
  • 中层:内核无线框架(cfg80211 + mac80211)
  • 底层:硬件驱动(直接操作寄存器)

这三层通过标准的netlink接口和内核API通信。你想想看,如果每一家芯片厂商都自己搞一套用户空间接口,那wpa_supplicant得适配多少种驱动?

实战经验:我在调试某款Realtek芯片时,发现iw命令能扫描但连不上AP。排查到最后,是cfg80211和驱动之间的scan结果回调没实现完整。这种问题,不懂框架结构根本无从下手。

下面这张图,是我每次培训必画的。它把整个数据路径和管理路径都串起来了。

Linux无线子系统架构图 用户空间 iw / wpa_supplicant / hostapd / NetworkManager 内核空间 - cfg80211 策略管理 / 扫描 / 连接 / 监管 / netlink接口 内核空间 - mac80211 帧封装 / 软件A-MPDU / 速率控制 / 重传 / 密钥管理 内核空间 - 硬件驱动 寄存器操作 / 中断处理 / DMA / 固件交互 WiFi芯片硬件 (PHY + MAC) 数据路径 控制路径

2.3 cfg80211框架详解

cfg80211是Linux无线子系统的策略层。它不关心具体怎么发帧,只关心能不能发、怎么发、发给谁。

它的核心职责包括:

  • 监管合规:不同国家信道不同,cfg80211负责查表。我记得有一次客户把设备卖到日本,结果5GHz信道不能用,就是因为regulatory domain没设对。
  • 扫描管理:用户发起的扫描请求,由cfg80211拆分成多次硬件扫描,再合并结果返回。
  • 连接状态机:关联、认证、4次握手,这些状态迁移都在cfg80211里维护。
  • netlink接口:用户空间的iw/wpa_supplicant通过nl80211与cfg80211通信。

避坑指南:我曾经在移植驱动时,发现cfg80211注册的wiphy设备总是创建失败。查了两天才发现是regulatory hint回调没实现,导致内核认为设备不支持任何信道。记住:cfg80211 ops结构体里的每个回调,能实现就实现,别偷懒。

2.4 mac80211框架详解

mac80211是真正的协议实现层。它处理802.11帧的封装、解封装、重传、聚合等逻辑。

我把它分成两个部分:

  1. 软件MAC (SoftMAC):大部分MAC逻辑由CPU处理,驱动只负责收发原始帧。这是目前主流方案。
  2. 硬件MAC (HardMAC):MAC逻辑全在固件里,驱动只做配置。比如一些USB网卡。

mac80211给驱动提供了两个关键结构体:

结构体 作用 驱动需要实现
ieee80211_ops 驱动回调函数表 tx、start/stop、config、sta_add/remove等
ieee80211_hw 硬件能力描述 填充支持的频段、带宽、队列数等

举个实际例子,驱动注册时大概长这样:

static const struct ieee80211_ops my_wifi_ops = {
    .tx              = my_wifi_tx,
    .start           = my_wifi_start,
    .stop            = my_wifi_stop,
    .add_interface   = my_wifi_add_interface,
    .remove_interface= my_wifi_remove_interface,
    .config          = my_wifi_config,
    .bss_info_changed= my_wifi_bss_info_changed,
    .sta_state       = my_wifi_sta_state,
};

static int my_wifi_probe(struct platform_device *pdev)
{
    struct ieee80211_hw *hw;
    
    hw = ieee80211_alloc_hw(sizeof(struct my_wifi_priv),
                            &my_wifi_ops);
    if (!hw)
        return -ENOMEM;
    
    // 设置硬件能力
    hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION)
                               | BIT(NL80211_IFTYPE_AP);
    hw->queues = 4;
    hw->max_rates = 1;
    
    // 注册到mac80211
    return ieee80211_register_hw(hw);
}

个人经验:写驱动时,ieee80211_ops里的回调不要全部实现。比如set_key,如果你的硬件不支持硬件加密,可以返回-ENOTSUPP,mac80211会自动切到软件加密。但tx和start是必须的,否则内核会直接报错。

2.5 数据包在协议栈中的流转

理解了框架,咱们看看一个数据包从网卡到应用的完整路径。嗯,这里要注意,WiFi的数据路径和以太网不太一样。

  1. 接收方向:硬件收到802.11帧 → 驱动通过ieee80211_rx()送入mac80211 → mac80211解帧头、做聚合重组 → 转成802.3帧 → 送入内核网络栈 → 最终到socket。
  2. 发送方向:应用数据 → 内核网络栈 → mac80211封装802.11头 → 驱动通过ieee80211_tx_info获取控制信息 → 硬件发送。

这里有个坑:WiFi帧头和以太网帧头不一样。mac80211负责做桥接转换。如果你在驱动里直接操作skb,一定要搞清楚当前是802.11格式还是802.3格式。

核心要点:驱动开发者只需要关注ieee80211_rx()和ieee80211_tx()这两个接口。mac80211帮你处理了所有协议细节。但前提是,你得正确填充ieee80211_tx_info结构体,告诉mac80211你的硬件支持什么。

2.6 框架选择:SoftMAC vs HardMAC

最后聊一个实际选型问题。做产品时,到底选SoftMAC还是HardMAC?

对比项 SoftMAC HardMAC
CPU负载 高(协议处理在CPU) 低(固件处理)
灵活性 高(可修改协议行为) 低(依赖固件)
调试难度 中等(内核代码可跟踪) 高(固件黑盒)
典型芯片 ath9k, iwlwifi brcmfmac, mt76 USB
适用场景 开发调试、定制协议 量产产品、低功耗设备

我个人偏好SoftMAC。虽然CPU负载高一点,但出了问题你能在mac80211里加printk,能单步调试。HardMAC一旦固件崩溃,你只能找芯片原厂要日志,那感觉,就像在黑夜里找一只黑猫。