跳到主要内容

ch12_9

12.9 快速参考(Quick Reference)

我们在这一章里走了很远——从最基础的 CSMA/CA 握手,到 802.11n 的 A-MPDU 聚合,再到 802.11s 的多跳路由。这些协议在纸面上是抽象的原理图,但在内核代码里,它们是一个个具体的函数调用、结构体成员和标志位。

当你真正动手去写驱动或者调试 mac80211 时,你需要的不是大段的原理叙述,而是一份能快速告诉你“这个函数是干什么的”、“那个标志位代表什么”的地图。

这就是本节存在的意义。它不是用来读的,是用来的。

这里我列出了本章提到过的一些核心方法,以及那个极为复杂的 ieee80211_rx_status 标志位字典。把这一页当词典用——当你在 drivers/net/wireless 的代码丛林里迷路时,回来看看这里。


核心方法速查

这些函数大多定义在 net/mac80211/ 目录下。为了方便查阅,我按功能将它们分类,并补充了原文中未提及但在调用时至关重要的行为细节。

1. Block Ack 与聚合(A-MPDU / A-MSDU)

这部分是 802.11n 提升吞吐量的核心。驱动通常不直接操作这些细节,但如果你想调试为什么聚合没开启,这里就是终点。

  • void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)

    • 作用:发送一个 Block Ack Request (BAR) 控制帧。
    • 场景:当你发送了一堆聚合帧,但对端没给你回 Block Ack,或者你想显式触发对方确认时调用。ssn (Starting Sequence Number) 告诉对方:“从这个序号开始给我确认”。
  • int ieee80211_start_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid, u16 timeout)

    • 作用:启动一个发送端的 Block Ack 会话( aggregation session)。
    • 机制:它会调用驱动层的 ampdu_action() 回调,传入参数 IEEE80211_AMPDU_TX_START
    • 注意:这不是同步的。驱动准备就绪后,必须回调 ieee80211_start_tx_ba_cb()_irqsafe 版本,真正的聚合才会开始。
  • int ieee80211_stop_tx_ba_session(struct ieee80211_sta *pubsta, u16 tid)

    • 作用:停止发送端的 Block Ack 会话。
    • 机制:调用驱动的 ampdu_action(),传入 IEEE80211_AMPDU_TX_STOP。驱动随后必须回调 ieee80211_stop_tx_ba_cb() 来完成清理。
  • static void ieee80211_send_addba_request(...)

    • 作用:发送 ADDBA Request 管理帧(Action Frame)。
    • 参数buf_size 决定了接收端的聚合窗口大小(通常 64),timeout 决定了会话超时时间。
  • void ieee80211_process_addba_request(...)

    • 作用:接收端处理收到的 ADDBA 请求。这是 mac80211 的核心逻辑,通常不由驱动调用,而是内核在收到 Action Frame 后自动触发。
  • static void ieee80211_send_addba_resp(...)

    • 作用:发送 ADDBA Response。也就是告诉对方“行,我可以聚合”或者“不行,窗口太小”。
  • void ieee80211_process_delba(...)

    • 作用:处理 DELBA 帧。对方不跟你玩了,或者对方通知你停止发送聚合帧。
  • void ieee80211_send_delba(...)

    • 作用:主动发送 DELBA 帧,结束一段友谊。
  • static ieee80211_rx_result ieee80211_rx_h_amsdu(struct ieee80211_rx_data *rx)

    • 作用:在 RX 路径 上处理 A-MSDU(聚合 MSDU)。
    • 细节:这是 RX Handler 链中的一环。如果硬件把解聚合后的包交给内核,这个函数负责把一个大包拆回多个 SKB。
  • static void ieee80211_rx_reorder_ampdu(...)

    • 作用:处理 A-MPDU 的 重排序缓冲区
    • 痛点:因为无线乱序到达很正常,这个函数把乱序的 MPDU 插入窗口,把连续可提交的包交给上层,把缺序的包留着等 BAR。

2. 电源管理

省电逻辑无线里全是坑。STA 想睡,AP 得帮着缓存;STA 醒了,得赶紧拿数据。

  • void ieee80211_send_nullfunc(struct ieee80211_local *local, ... int powersave)

    • 作用:发送 Null Data 帧。
    • 关键:这是告诉 AP“我要睡了”或者“我醒了”的信令。通过帧头里的 Power Management 位实现。
  • void ieee80211_send_pspoll(...)

    • 作用:发送 PS-Poll 控制帧。
    • 场景:STA 从睡眠中醒来,主动问 AP:“有没有缓存给我的单播数据?有就赶紧发。”
  • static inline bool ieee80211_check_tim(..., u16 aid)

    • 作用:检查 TIM (Traffic Indication Map) 位图。
    • 场景:STA 收到 Beacon 帧后,调用此函数查看自己的 AID 对应的位是否被置 1。如果是,说明 AP 有货,该发 PS-Poll 了。

3. 扫描、认证与关联

这是 MLME 的核心流程。

  • int ieee80211_request_scan(...)

    • 作用:发起 主动扫描
  • void ieee80211_send_probe_req(...)

    • 作用:发送 Probe Request 管理帧。
    • 参数ssidie (Information Elements) 决定了你在问什么。
  • void ieee80211_send_auth(...)

    • 作用:发送 Authentication 帧。这是连接的第一步,可以是 Open System 也可以是 Shared Key。
  • static void ieee80211_send_assoc(...)

    • 作用:发送 Association Request 帧。
    • 时机:认证通过后调用。它会携带所有支持的速率和能力信息。

4. Mesh 路由(802.11s / HWMP)

如果你在写 Mesh 相关的代码,这几个函数是 HWMP 协议在内核中的具体实现入口。

  • struct mesh_path *mesh_path_lookup(..., const u8 *dst)

    • 作用:在 Mesh 路由表中查找去往 dst 的路径。
    • 返回:找不到返回 NULL,找到返回 mesh_path 结构体。这是转发数据的第一步。
  • void mesh_path_tx_pending(struct mesh_path *mpath)

    • 作用:发送 mpath->frame_queue 里积压的数据包。
    • 场景:当路由发现完成(比如收到了 PREP)后,内核会调用此函数把刚才堵在路口的包放行。
  • static int mesh_path_sel_frame_tx(enum mpath_frame_type action, ...)

    • 作用:通用的 HWMP 帧发送函数。
    • 支持:既发 PREQ (Path Request),也发 PREP (Path Reply)。参数里的 metric 就是那条著名的“airtime”开销。
  • static void hwmp_preq_frame_process(...)

    • 作用:处理收到的 PREQ 帧。这是 Mesh 节点互相协作找路的核心逻辑。

5. 设备注册与底层操作

这是驱动开发者最先接触的 API。

  • struct ieee80211_hw *ieee80211_alloc_hw(...)

    • 作用:分配一个 ieee80211_hw 设备结构以及驱动的私有数据 priv。一切的开始。
  • int ieee80211_register_hw(struct ieee80211_hw *hw)

    • 作用:向内核注册这个硬件设备。从这一刻起,你的网卡才在系统中“活着”。
  • int ieee80211_hw_config(..., u32 changed)

    • 作用:配置硬件参数。
    • 参数changed 指定了改什么(比如改信道用 IEEE80211_CONF_CHANGE_CHANNEL)。这个函数通常会下沉到驱动的 config() 回调。
  • void ieee80211_rx_irqsafe(struct ieee80211_hw *hw, struct sk_buff *skb)

    • 作用接收数据包
    • 特性:正如其名,它可以在 硬件中断上下文 中安全调用。它会利用软中断(tasklet)把收包工作延后处理。

6. RX 处理与状态

收包不仅仅是把 SKB 往上层递,还要检查 FCS、解密状态和重排序。

  • struct ieee80211_rx_status *IEEE80211_SKB_RXCB(struct sk_buff *skb)

    • 作用:从 SKB 的控制块 (cb) 中提取 rx_status
    • 重点:硬件驱动收包后,必须把信号强度、速率、MAC 时间戳等信息填在这个结构体里,放进去 skb->cb,再交给 mac80211
  • static ieee80211_rx_result ieee80211_rx_h_check(struct ieee80211_rx_data *rx)

    • 作用:RX 处理的第一道关卡。
    • 逻辑:检查是否是重复帧(Retransmission)。如果是,增加 dot11FrameDuplicateCount 计数器,并丢弃。
  • static bool ieee80211_sta_manage_reorder_buf(...)

    • 作用:管理重排序缓冲区的具体实现逻辑。它决定了这个包该进缓冲区等待,还是可以直接交给网络栈。

标志位字典:ieee80211_rx_status

这是 mac80211 驱动开发中最容易出错的地方之一。rx_status 结构体里的 flag 是一个 32 位位域。硬件驱动负责把这些位填对,mac80211 负责根据这些位做正确的事。

下面这个表格是我在调试收包问题时经常翻的“天书”。我把含义稍微展开了点,免得你看手册时一脸懵。

表 12-1:Rx Flags (ieee80211_rx_status 的 flag 字段)

Linux 符号常量位 (Bit)真实含义与实战提示
RX_FLAG_MMIC_ERROR0Michael MIC 错误
这个帧通过了 CRC 校验,但内容被篡改了。通常发生在 TKIP 加密时。如果是这个错,直接丢包。
RX_FLAG_DECRYPTED1硬件已解密
极其重要的标志。如果硬件帮你解密了(WEP/WPA/WPA2),必须置此位。内核看到它就知道不用再跑软件解密算法了。
RX_FLAG_MMIC_STRIPPED3MIC 已被剥离
硬件不仅解了密,还把尾部的 MIC 校验码给删了。内核会据此计算 MIC。
RX_FLAG_IV_STRIPPED4IV/ICV 已被剥离
硬件解密后把头部的 IV (Initialization Vector) 和尾部的 ICV (Integrity Check Value) 都去掉了。置此位说明你给内核的数据是“干净”的明文 payload。
RX_FLAG_FAILED_FCS_CRC5FCS 校验失败
这包是坏的。虽然你收到了,但 FCS (Frame Check Sequence) 对不上。内核通常会直接静默丢弃。
RX_FLAG_FAILED_PLCP_CRC6PLCP 校验失败
物理层的前导码和信号头就错了。这通常是信号太差导致的。
RX_FLAG_MACTIME_START7时间戳有效(起始时刻)
rx_status->mactime 字段里存的是 帧第一个符号到达的时间。对于需要精确测距或同步的场景(如某些定位协议)至关重要。
RX_FLAG_SHORTPRE8短前导码
使用了 802.11b 的 Short Preamble 模式。
RX_FLAG_HT9High Throughput (11n)
这是 802.11n 的帧。注意,此时 rate_idx 字段存的是 MCS 索引,不是传统的 legacy rate。
RX_FLAG_40MHZ10HT40 模式
使用了 40MHz 信道带宽。
RX_FLAG_SHORT_GI11短保护间隔
用了 400ns 的 Short Guard Interval,速度比 800ns 的 GI 快一点。
RX_FLAG_NO_SIGNAL_VAL12无信号强度
硬件没测到信号强度 (RSSI),或者根本不支持。rx_status->signal 字段此时无效。
RX_FLAG_HT_GF13HT Greenfield
这是 802.11n 的 Greenfield 模式,没有为了兼容 802.11a/g 而加的 Training Field。速度最快,但老设备听不懂。
RX_FLAG_AMPDU_DETAILS14A-MPDU 详情已知
这个包是聚合帧的一部分。此时 rx_status 里的 ampdu_ref 必须有效,用于区分不同的 A-MPDU 突发。
RX_FLAG_AMPDU_REPORT_ZEROLEN15驱动报告零长度子帧
这是一个比较特殊的硬件行为,有些硬件会把聚合帧里的空包也报告上来。
RX_FLAG_AMPDU_IS_ZEROLEN16这是一个零长度子帧
对应上面的报告,标记这个包本身就是空的,通常用于监控。
RX_FLAG_AMPDU_LAST_KNOWN17最后一帧已知
驱动知道这是这个 A-MPDU 突发的最后一包。
RX_FLAG_AMPDU_IS_LAST18这是最后一帧
这个包就是该 A-MPDU 的终结者。
RX_FLAG_AMPDU_DELIM_CRC_ERROR19分隔符 CRC 错误
在 A-MPDU 中,子帧之间有 Delimiter。如果这个 Delimiter 的 CRC 挂了,说明硬件解析同步出了问题。
RX_FLAG_AMPDU_DELIM_CRC_KNOWN20分隔符 CRC 值有效
如果置位,说明 rx_status->ampdu_delimiter_crc 字段存着那个算出来的 CRC 值。
RX_FLAG_MACTIME_END21时间戳有效(结束时刻)
mactime 记录的是 帧最后一个符号(含 FCS)收完的时间。和 START 标志二选一。
RX_FLAG_VHT22Very High Throughput (11ac)
这是 802.11ac 的帧。rate_idx 此时存的是 VHT MCS 索引。
RX_FLAG_80MHZ23VHT80 模式
802.11ac,使用了 80MHz 带宽。
RX_FLAG_80P80MHZ24VHT80+80 模式
使用了两个不连续的 80MHz 通道。这是 802.11ac 的高级特性。
RX_FLAG_160MHZ25VHT160 模式
连续的 160MHz 带宽。不仅需要硬件支持,还得看当地法规让不让你用这么宽的频段。

本章回响

好了,这就是这本书里关于无线网络的最后一块拼图。

回首这一章,我们实际上是在Linux内核里重建了一座巴别塔。我们在物理层(PHY)通过 ieee80211_hw 与硬件对话,在 MAC 层通过 mac80211 实现了 CSMA/CA 的秩序,又通过 FullMAC 与软 MAC 的博弈,看到了驱动开发者的两难选择。

我们看到了 802.11 协议族是如何一步步从单纯的避让(CSMA/CA),进化到贪婪(802.11n 的聚合),再到智能(802.11s 的 Mesh 路由)。

还记得本章开头那个问题吗——为什么 Wi-Fi 在Linux里这么复杂? 现在答案很清楚了:因为它试图在一个充满噪声、不可靠的共享介质上,模拟出一条可靠、高速的点对点专线。

  • FullMAC 把复杂性塞进了黑盒,硬件厂商为你挡了一枪,但也锁住了你的手脚。
  • SoftMAC (mac80211) 把复杂性交给了内核,你得自己处理 TIM、Buffer、重排序,但你拥有了空气中的绝对控制权。

当你下次看着 iw 输出的连击信息,或者在内核日志里翻到 ieee80211_rx: mmic_error 时,希望你能意识到:这不仅仅是一条日志,这是这颗芯片为了把那一比特数据从天线送进内存,与混乱的无线电波搏斗后的战损报告。

这章结束了。收起你的天线,我们下一章见。那里我们将从“看不见的波”切换到“看得见却摸不着的光纤”——RDMA。在那里,内核甚至不再插手数据传输,只是为了把那条路铺平。


练习题

练习 1:understanding

题目:在 Infrastructure BSS(基础架构模式)下,当一个无线客户端(STA)向接入点(AP)发送数据帧,且该数据最终需转发至外部有线网络时,802.11 MAC 头部帧控制字段中的 ToDS 和 FromDS 位应分别被设置为什么值?

答案与解析

答案:ToDS = 1, FromDS = 0

解析:根据 802.11 规范,ToDS(Distribution System)位表示数据帧发往分发系统(即 AP),FromDS 位表示数据帧来自分发系统。当客户端向 AP 发送数据(由 AP 转发出去)时,数据是进入分发系统的,因此 ToDS 置 1,FromDS 置 0。反之,当 AP 向客户端发送数据时,FromDS 为 1,ToDS 为 0。

练习 2:understanding

题目:当 mac80211 子系统中的接入点(AP)接收到一个客户端发送的 Null Data Packet(空数据包),且发现帧控制中的 Power Management 位为 1 时,AP 接下来会执行什么操作?

答案与解析

答案:AP 会将该客户端标记为进入休眠状态,并开始将发往该客户端的单播数据包缓存到 ps_tx_buf 缓冲区中。

解析:Null Data Packet 不包含有效载荷,主要用于电源管理通知。根据 Power Save Mode 机制,当 PM 位为 1 时,表示站点即将进入省电模式。AP 收到此通知后,会将后续发往该站点的单播包存入该站点对应的 ps_tx_buf 队列(最多 128 个),直到站点通过 PS-Poll 或 Null Data 包唤醒并取回数据。

练习 3:application

题目:假设你正在开发一个 FullMAC 类型的无线网卡驱动,与 SoftMAC(使用 mac80211)驱动相比,在内核空间处理 802.11 管理帧(如 Association Request、Authentication)的逻辑责任有何主要不同?

答案与解析

答案:在 FullMAC 驱动架构中,MLME(MAC 层管理实体)逻辑主要由固件或硬件处理,内核驱动通常只负责数据面的传输和基本控制;而在 SoftMAC(mac80211)架构中,MLME 逻辑(如关联、认证、扫描状态的机)由内核中的 mac80211 子系统和用户空间守护进程(如 wpa_supplicant/hostapd)共同处理。

解析:FullMAC 设备自身处理大部分 MAC 层管理功能(如状态的维护、帧的交互),内核驱动相对轻量。mac80211 是一个 SoftMAC 框架,它将复杂的协议状态机放在内核软件中实现,从而统一不同硬件的行为,并允许更灵活的软件定义功能(如 Mesh 网络的复杂路由)。

练习 4:thinking

题目:IEEE 802.11n 引入了 A-MPDU(Aggregated MAC Protocol Data Unit)和 A-MSDU(Aggregated MAC Service Data Unit)两种聚合技术来提高吞吐量。请从“发生重传时的开销”角度分析,为什么 A-MPDU 通常比 A-MSDU 更适合高误码率的无线环境?

答案与解析

答案:因为 A-MPDU 的重传粒度是 MPDU(MAC 层协议数据单元),而 A-MSDU 的重传粒度是整个聚合后的 MSDU。在 A-MPDU 中,如果聚合块中只有一个子帧出错或丢失,接收端可以通过 Block Ack 机制仅请求重传出错的那一个子帧,而无需重传整个聚合块。相反,A-MSDU 将多个 MSDU 打包成一个大帧,如果传输过程中发生误码导致 CRC 校验失败,整个大帧都需要重传,这在信道质量差(高误码率)时会显著降低有效吞吐量。

解析:这道题考察对底层链路效率的深度思考。虽然 A-MSDU 减少了头部的开销,但其重传机制是“全有或全无”的。A-MPDU 配合 Block Ack 机制提供了更细粒度的错误恢复能力(Selective Retransmission),在不可靠的无线信道中,这种特性带来的效率收益通常超过了其稍高的头部开销。


要点提炼

Linux 内核将无线网络视为独立子系统 mac80211,而非以太网的简单变体,主要归因于无线物理介质的高不可靠性(频繁丢包)和独特的介质访问方式(从冲突检测 CD 转向冲突避免 CA)。为了应对 802.11 协议的快速迭代,Linux 主流采用 SoftMAC 架构:复杂的 MAC 层逻辑(如组帧、加密、重传)由内核统一接管,驱动仅负责硬件交互,从而获得了极高的灵活性和控制力。

802.11 的 MAC 头部比以太网复杂得多,最多包含 4 个地址字段(MAC Header 4 Addresses)和丰富的帧控制位,这些结构定义了帧的具体身份(管理/控制/数据)、流向(ToDS/FromDS)及行为属性(如省电模式、重传标志)。这种复杂性源于无线网络中的流量转发(AP 转发数据)和特殊机制需求,驱动开发必须深入理解这些字段才能正确解析和构造数据帧。

Infrastructure 基础架构模式下,客户端必须通过认证和关联两个步骤才能接入网络,并获取唯一的 AID(关联 ID)。为了解决移动设备(如手机、笔记本)的续航问题,协议设计了精细的 Power Save Mode(节电模式):客户端通过发送 Null Data 帺金标志位通知 AP 休眠,AP 维护每个站点的 ps_tx_buf 缓存单播包,并利用 Beacon 帧中的 TIM(流量指示图)位图来通知客户端“有货待取”,客户端再通过 PS-Poll 帧主动“提货”。

MLME(MAC 层管理实体) 负责处理网络发现与维护的底层逻辑。为了寻找 AP,设备会进行 Scanning(扫描):在 5GHz 雷达频段必须使用被动扫描(静默监听 Beacon),而在普通频段通常使用主动扫描(发送 Probe Request 并等待 Probe Response)。当客户端在移动中发现信号更好的 AP 时,会触发 Roaming(漫游) 机制,通过发送带有原 AP 地址的 Reassociation Request 帧与新 AP 建立连接,实现网络的无缝切换。

在内核实现层面,mac80211 框架将通用逻辑与硬件细节解耦。核心结构体 ieee80211_hw 作为硬件的抽象身份证,而 ieee80211_ops 回调函数集则定义了驱动的操作规范(如 tx 发包、config 配置信道、start 启动设备)。每个连接的对端站点在内核中对应一个 sta_info 结构体,它不仅存储 MAC 地址和统计信息,还管理着诸如节电缓冲队列(ps_tx_buf)等关键状态,是驱动维护连接关系的核心数据结构。