乐于分享
好东西不私藏

Android WiFi 源码分析(五·中):wpa_supplicant 内部扫描 — 不是 Framework 的扫描路径

Android WiFi 源码分析(五·中):wpa_supplicant 内部扫描 — 不是 Framework 的扫描路径

⚠️ 关键认知:Android Framework 的常规扫描(Settings 10s 扫描、App 调用 startScan、WifiConnectivityManager 周期性扫描)不经过 wpa_supplicant——它们走的是 wificond → nl80211 → 驱动的直通路径(详见 05 上)。那 wpa_supplicant 的扫描引擎是在什么时候用的?答案是:supplicant 自己内部触发的扫描——已连接状态下的 bgscan(后台扫描更优 AP 供漫游)、P2P Device Discovery、WPS 配网时的扫描。这些扫描不和 Framework 的扫描共享任何代码路径,它们是 supplicant 的内部事务。

上篇回顾:在 05(上)中,我们追踪了 Android Framework 的扫描请求从 App 到内核的完整链路——wificond 直通 nl80211,不经 supplicant。本篇聚焦另一条独立路径:supplicant 自己触发的扫描。

本章导读

本章比喻:如果 Framework 的 wificond 扫描是雷达指挥中心发起的例行探测任务(定时扫、用户要求扫),那么 wpa_supplicant 的扫描引擎就是雷达的自动维护程序——它在后台悄悄地扫描周围环境,自己决定何时扫、扫什么,目的是保持连接质量和发现 P2P 设备。最精妙的设计是 radio work 队列——就像雷达的任务调度器:同一台雷达不能同时做两件事,所有射频操作(扫描、连接、P2P)必须排队。

你将学到

supplicant 内部扫描的触发场景:连接前扫描(无缓存 BSS 时先扫再连)、bgscan(漫游候选)、P2P Discovery、WPS 配网wpa_supplicant_scan() 的参数构建过程——SSID 列表、频率列表、IE 构建、MAC 随机化radio work 的队列机制——为什么扫描要排队?单信道 vs 多信道互斥规则扫描结果的完整分发链——EVENT_SCAN_RESULTS → BSS 更新BSS 缓存的生命周期管理——添加、更新、scan_miss_count 过期淘汰、使用中保护

数据来源

external/wpa_supplicant_8[1](eloop / scan / bss / events)

代码说明:本文代码来自 AOSP external/wpa_supplicant_8 真实源码,有精简(去掉 wpa_dbg log 语句),关键路径完整保留。

系列导航:本篇是扫描三部曲的中篇,聚焦 wpa_supplicant 内部的扫描引擎(bgscan/P2P/漫游场景)。上篇讲 Framework → wificond → nl80211 的直通路径,下篇讲驱动执行 + PNO + WiFi 6E/7。


一、supplicant 什么时候会自己扫描?

一句话总结:Android Framework 的常规扫描走 wificond,不经过 supplicant。supplicant 的扫描引擎在四种场景下触发——bgscan(漫游)、P2P Discovery、WPS 配网、以及连接前扫描(没有缓存 BSS 时先扫再连)。

1.1 Framework 扫描 vs supplicant 扫描 — 两条独立路径

路径 A:Framework 扫描(05 上)        路径 B:supplicant 内部扫描(本篇)  App.startScan()                        supplicant 内部事件触发    → WifiScanningServiceImpl                → wpa_supplicant_req_scan()      → WifiNl80211Manager                       → wpa_supplicant_scan()        → wificond ScannerImpl::scanRequest()        → wpa_supplicant_trigger_scan()          → ScanUtils::Scan()                          → radio_add_work("scan", wpas_trigger_scan_cb)            → NL80211_CMD_TRIGGER_SCAN                   → wpa_drv_scan()              → 内核 → 驱动 → 固件                         → driver_nl80211_scan()                                                               → NL80211_CMD_TRIGGER_SCAN                                                                 → 内核 → 驱动 → 固件

两条路径互不感知——Framework 不知道 supplicant 在扫什么,supplicant 不知道 Framework 什么时候扫。

1.2 supplicant 内部扫描的四种场景

触发场景
触发条件
目的
典型配置
连接前扫描wpa_supplicant_select_network()
 发现无缓存 BSS 或缓存过期
先扫描找到目标 AP,再发起连接
scan_res_valid_for_connect
(缓存有效期)
bgscan
已连接状态下,按 bgscan 模块的间隔定时触发
寻找同 ESS 的更优 AP(信号更好/负载更低)供漫游
bgscan="simple:30:-45:300"
(30s short interval, -45dB threshold, 300s long interval)
P2P Device Discovery
P2P 操作中(Find 阶段)
发现附近的 P2P 设备
p2p_find
 命令,Listen + Search 状态交替
WPS 配网扫描
WPS 按钮按下的 2 分钟内
发现正在进行 WPS 配网的 AP
只在 WPS 频段(2.4GHz ch1/6/11)扫描

连接前扫描的完整流程——这是最容易被忽略的场景:

wpa_supplicant_select_network()              // wpa_supplicant.c:5152  ├── wpa_supplicant_fast_associate()        // 检查缓存是否可用  │     ├── last_scan_res_used == 0?         → 无缓存,return -1  │     ├── scan_res_valid_for_connect 超时?  → 缓存过期,return -1  │     ├── crossed_6ghz_dom?               → 跨越 6GHz 域,return -1(需重新扫描)  │     └── wpas_select_network_from_last_scan()  // 有缓存 → 直接选网连接  │  └── fast_associate 失败(无缓存/过期/跨 6GHz 域):        (注:connect_without_scan 仅在 MESH/AP 模式下设置,普通 STA 连接不会跳过扫描)        → wpa_supplicant_req_scan()          // 调度扫描          → wpa_supplicant_scan()            // 本篇的核心函数            → 驱动层扫描              → EVENT_SCAN_RESULTS                → wpas_select_network_from_last_scan()                  → wpa_supplicant_connect()                    → wpa_supplicant_associate()

也就是说,当用户在 Settings 中点击连接一个 WiFi 时,如果 supplicant 的 BSS 缓存中没有这个 AP 的信息(或缓存已过期),它会先扫描再连接——这也是 supplicant 扫描引擎的典型使用场景。connect_without_scan 标志仅在 MESH/AP 模式下被设置(wpa_supplicant.c);P2P GO 的 connect_without_scan 在 p2p_supplicant.c 中单独设置——普通 STA 连接不会跳过扫描。

Tips:bgscan 是否还在用?Android 11+ 默认配置下,connected mode 的后台扫描由 WifiConnectivityManager 通过 wificond 触发(Framework 路径)。bgscan 模块主要用于遗留配置和某些 OEM 定制。本篇的引擎机制(eloop 调度、radio work、BSS 缓存)无论触发源是什么,底层逻辑完全相同。

1.3 各种扫描最终都汇聚到 eloop 定时器

下图展示了 supplicant 扫描引擎的完整流程——从 eloop 定时器触发到 BSS 缓存更新:

// external_wpa_supplicant_8/wpa_supplicant/scan.cvoid wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec){    int res;    if (wpa_s->p2p_mgmt) {           // P2P management 接口不执行 STA 扫描        return;    }    res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,                                NULL);    if (res == 1) {        wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",                sec, usec);    } else if (res == 0) {        wpa_dbg(wpa_s, MSG_DEBUG, "Ignore new scan request for %d.%06d sec "                "since an earlier request is scheduled to trigger sooner",                sec, usec);    } else {        wpa_dbg(wpa_s, MSG_DEBUG, "Setting scan request: %d.%06d sec",                sec, usec);        eloop_register_timeout(sec, usec, wpa_supplicant_scan, wpa_s, NULL);    }}

主要功能:

eloop_deplete_timeout() 检查是否已有 wpa_supplicant_scan 的定时器在排队——如果有且触发时间更早,则忽略新请求(res == 0);如果新请求更早,则替换(res == 1这是一种去抖动机制——短时间内多次调用 wpa_supplicant_req_scan() 不会导致多次扫描,只有最早触发的那个会生效eloop_register_timeout() 注册的到期回调就是 wpa_supplicant_scan()——eloop 事件循环在 select() 超时到期后自动调用它

1.4 eloop 定时器机制——Supplicant 的「心跳」

eloop_register_timeout() 是 Supplicant 中最核心的调度原语。它把一个超时事件插入 eloop 的有序双向链表(按到期时间升序排列):

// external_wpa_supplicant_8/src/utils/eloop.cint eloop_register_timeout(unsigned int secs, unsigned int usecs,                           eloop_timeout_handler handler,                           void *eloop_data, void *user_data){    struct eloop_timeout *timeout, *tmp;    timeout = os_zalloc(sizeof(*timeout));    if (timeout == NULL)        return -1;    // ...os_get_reltime() 获取当前时间 + 计算绝对到期时间...    // 注:源码有两个 goto overflow 检查点(sec 加法溢出和 usec 进位后溢出),    // overflow 标签会 os_free(timeout) 并返回 0(注册失败但不崩溃),此处省略    timeout->time.sec += secs;    timeout->time.usec += usecs;    while (timeout->time.usec >= 1000000) {        timeout->time.sec++;        timeout->time.usec -= 1000000;    }    timeout->handler = handler;    timeout->eloop_data = eloop_data;    timeout->user_data = user_data;     // eloop_deplete_timeout 按 handler+eloop_data+user_data 三元组匹配    /* 按到期时间升序插入双向链表 */    dl_list_for_each(tmp, &eloop.timeout, struct eloop_timeout, list) {        if (os_reltime_before(&timeout->time, &tmp->time)) {            dl_list_add(tmp->list.prev, &timeout->list);            return 0;        }    }    dl_list_add_tail(&eloop.timeout, &timeout->list);    return 0;}

主要功能:

链表按到期时间升序排列——eloop_run() 中的 select() 只取链表头的时间作为超时值这意味着 eloop 的 select() 调用总是自动选择最近到期的超时——不需要手动管理多个定时器扫描的回调 wpa_supplicant_scan 就是通过这个机制被调度的


二、wpa_supplicant_scan() — 参数构建的三个列表

一句话总结:这是 Supplicant 扫描的主入口,它不直接执行扫描,而是构建好所有参数后通过 radio work 排队。核心是三个列表的构建:SSID 列表(找谁)、频率列表(在哪找)、IE 列表(带什么附加信息)。

2.1 前置检查——这些情况下不扫描

wpa_supplicant_scan() 在构建参数之前先走一遍提前退出检查。以下是源码中实际存在的退出路径(按逻辑分组,非严格代码顺序):

条件
行为
设计意图
wpa_s->wpa_state == WPA_INTERFACE_DISABLED
直接返回
接口未启用
wpa_s->disconnected && scan_req == NORMAL_SCAN_REQ
设置 WPA_DISCONNECTED 后直接返回
断连态的普通扫描不需要——手动/初始扫描仍放行
wpa_s->scanningwpa_supplicant_req_scan(wpa_s, 1, 0)
 重新调度
已有扫描进行中——推迟 1 秒
connect_without_scan
 已设置(非手动请求时)
跳过扫描参数构建,直接跳转到 wpa_supplicant_associate()
已有缓存 BSS 信息,不扫直接连
无 enabled 网络 且 scan_req == NORMAL_SCAN_REQ
调用 wpa_supplicant_set_state(wpa_s, WPA_INACTIVE) 后返回
非手动扫描 + 无网络 = 不扫
wired 驱动强制 ap_scan = 0
覆写 ap_scan 为 0,后续通过 ap_scan == 0 检查退出(跳过参数构建和扫描下发)
有线网卡不执行 STA 扫描
ap_scan == 0
(无线网卡)
直接返回
驱动负责扫描
wpas_p2p_in_progress(wpa_s)
 且非 P2P GO 且当前 SSID mode 非 AP/P2P_GO
wpa_supplicant_req_scan(wpa_s, 5, 0)
 推迟 5 秒
P2P 操作中 STA 扫描让路
PNO 进行中 (wpa_s->pno || wpa_s->pno_sched_pending)
wpa_supplicant_req_scan(wpa_s, 0, 100000)
 推迟 100ms
PNO 优先

关键设计:拒绝不丢弃——多数情况通过 wpa_supplicant_req_scan() 重新调度。另外 wpa_supplicant_trigger_scan() 中还有一个独立检查——如果已有 scan_work 在排队则拒绝(radio work 串行化保证同时间只有一个扫描 work)。

2.2 SSID 列表构建——找谁?

扫描参数的 SSID 列表决定了 Probe Request 中携带的 SSID。构建优先级如下:

手动扫描: 用 scan_req 中的 SSID 列表    ↓ (如果没有手动请求)P2P 场景: 特定 SSID + BSSID(如 WPS provisioning)    ↓ (如果不是 P2P)通用场景: 遍历 wpa_s->conf->ssid 链表    └── 加入 scan_ssid=1 且未禁用的网络 SSID    └── max_ssids==1 时交替 wildcard 和具体 SSID

源码中 SSID 链表遍历的实际代码(wpa_supplicant_scan() 内,第 1213-1290 行区域):

// scan.c: wpa_supplicant_scan() — SSID 列表构建的核心逻辑/* 找到上次扫描的断点,从这里继续往下遍历 */ssid = wpa_s->conf->ssid;if (wpa_s->prev_scan_ssid != WILDCARD_SSID_SCAN) {    while (ssid) {        if (ssid == wpa_s->prev_scan_ssid) {            ssid = ssid->next;  // 从上次断点的下一个开始            break;        }        ssid = ssid->next;    }}while (ssid && params.num_ssids < max_ssids) {    if (!wpas_network_disabled(wpa_s, ssid) && ssid->scan_ssid) {        // 只有 scan_ssid=1 且未禁用的网络才加入扫描列表        os_memcpy(params.ssids[params.num_ssids].ssid,                  ssid->ssid, ssid->ssid_len);        params.ssids[params.num_ssids].ssid_len = ssid->ssid_len;        params.num_ssids++;    }    ssid = ssid->next;}

主要功能:

断点续扫prev_scan_ssid 记录上次扫描到哪个 SSID,本次从下一个开始——这样不会每次都从链表头扫,节省时间只扫需要主动探测的网络scan_ssid=1 的才加入(隐藏网络),普通广播 SSID 的网络用 wildcard 一次扫出max_ssids 限制:受 wpa_s->max_scan_ssids(驱动能力上限)和 WPAS_MAX_SCAN_SSIDS(supplicant 硬上限 16)双重约束

为什么 max_ssids==1 时要交替? 如果驱动只支持一次扫描携带一个 SSID(如某些老驱动),Supplicant 会交错发送 wildcard SSID(探测所有 AP)和具体 SSID(探测隐藏网络)——这样既不会错过一般 AP,也能发现隐藏网络。

2.3 频率列表构建——在哪扫?

频率(信道)的构建是 wpa_supplicant_scan() 中最精细的部分。以下 7 级是通用场景的优先级;P2P/WPS 场景下 wpa_supplicant_optimize_freqs() 会在所有级别之前预设频率(详见 2.2 节 P2P 场景)。优先级从高到低:

优先级
来源
代码中的判断顺序(scan.c:1377-1409
1(最高)
manual_scan_freqswpa_s->last_scan_req == MANUAL_SCAN_REQ && wpa_s->manual_scan_freqs
2
select_network_scan_freqswpa_s->select_network_scan_freqs
(当前 SSID 配置的扫描频率)
3
next_scan_freqs
BTM 漫游候选频率列表
4
setband_scan_freqswpa_setband_scan_freqs()
——用户设置的频段(2.4G/5G)
5
initial_freq_list
 / freq_list
INITIAL_SCAN_REQ
 时用 initial_freq_list,否则用 freq_list
6
scan_cur_freqwpa_s->conf->scan_cur_freq
 启用时,将当前连接频率加入列表
7(最低)
默认全频段
以上全部为空时,不设置 params.freqs,驱动扫全部支持的信道
// scan.c: wpa_supplicant_scan() — 频率来源的实际代码if (wpa_s->last_scan_req == MANUAL_SCAN_REQ && params.freqs == NULL &&    wpa_s->manual_scan_freqs) {    params.freqs = wpa_s->manual_scan_freqs;     // ① 手动指定的频率    wpa_s->manual_scan_freqs = NULL;              // 用完即清,防止下次复用}if (params.freqs == NULL && wpa_s->select_network_scan_freqs) {    params.freqs = wpa_s->select_network_scan_freqs; // ② 当前网络配置的频率    wpa_s->select_network_scan_freqs = NULL;}if (params.freqs == NULL && wpa_s->next_scan_freqs) {    params.freqs = wpa_s->next_scan_freqs;           // ③ BTM 漫游候选频率}wpa_s->next_scan_freqs = NULL;wpa_setband_scan_freqs(wpa_s, ¶ms);              // ④ 频段限制// ⑤ 启动扫描时用 initial_freq_list,后续用 freq_listif (wpa_s->last_scan_req == INITIAL_SCAN_REQ &&    wpa_s->conf->initial_freq_list && !params.freqs) {    int_array_concat(¶ms.freqs, wpa_s->conf->initial_freq_list);} else if (wpa_s->conf->freq_list && !params.freqs) {    int_array_concat(¶ms.freqs, wpa_s->conf->freq_list);}// 如果以上全部为空 → params.freqs = NULL → 驱动扫描全部支持的信道

主要功能:

链式 fallthrough:每个 if 都检查 params.freqs == NULL——一旦前面设置了频率就不再覆盖用完即清manual_scan_freqsselect_network_scan_freqs 在赋值后置 NULL——防止下次扫描误复用默认不设 = 全扫:如果所有来源都为空,params.freqs 保持 NULL,驱动默认扫全部支持的信道

2.4 IE 构建——带什么附加信息?

wpa_supplicant_extra_ies() 函数构建 Probe Request 中携带的额外信息元素(Information Element):

IE 类别
内容
何时携带
MLD probe
多链路探测信息(WiFi 7)
支持 MLO 时
Extended Capabilities
扩展能力声明
始终
Interworking
运营商互通、ANQP
启用 Interworking 时
WPS
Wi-Fi 保护设置
WPS 配网中
P2P
P2P 设备信息
P2P 操作中
MBO/OCE
运营商优化
运营商特性启用时
Vendor Specific
厂商自定义 IE
上层传入时

上表列出主要 IE 类别,完整列表还包括 Hotspot 2.0(wpas_hs20_add_indication)、FST(wpa_s->fst_ies)、Mesh(wpa_supplicant_mesh_add_scan_ie)等按编译配置和运行状态条件添加的 IE。

这些 IE 决定了 AP 回复的 Probe Response 中会包含哪些能力信息——可以理解为 STA 在 Probe Request 中说的「我会这些,请告诉我你对应支持什么」。

2.5 MAC 地址随机化

扫描时的 MAC 随机化策略在 wpas_trigger_scan_cb() 中生效:

if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) &&    wpa_s->wpa_state <= WPA_SCANNING)    wpa_setup_mac_addr_rand_params(params, wpa_s->mac_addr_scan);

关键判断wpa_state <= WPA_SCANNING——已连接状态下扫描不使用随机 MAC。这是因为已连接时的扫描通常是漫游场景(寻找同 ESS 的其他 AP),使用随机 MAC 会让 AP 不认识你,漫游就无法加速(无法复用 PMKSA 缓存)。


三、radio work — 为什么扫描要排队?

一句话总结:同一台射频同一时间只能干一件事。radio work 队列是 Supplicant 内部所有射频操作(扫描、连接、P2P 发现、offchannel 操作)的串行化机制——它确保不会有两个操作同时抢占射频。

回到雷达比喻——同一台雷达天线上,你不能同时执行两套扫描任务(两套的信道列表、dwell time、增益参数都不同)。radio work 队列就是来排队使用射频资源的。

3.1 数据结构

// external_wpa_supplicant_8/wpa_supplicant/wpa_supplicant_i.hstruct wpa_radio {    char name[16];              // 射频名称(驱动提供)    unsigned int num_active_works;  // 当前激活的 work 数量    struct dl_list ifaces;      // 共享此射频的接口链表    struct dl_list work;        // work 队列(双向链表)    struct wpa_supplicant *external_scan_req_interface;  // 外部扫描请求接口(协调 Framework 扫描)};struct wpa_radio_work {    struct dl_list list;        // 链表节点    unsigned int freq;          // 操作频率(0 = 多信道,非 0 = 单信道)    const char *type;           // 类型标签:"scan"/"p2p-scan"/"connect"等    struct wpa_supplicant *wpa_s;    void (*cb)(struct wpa_radio_work *work, int deinit);  // 回调函数    void *ctx;                  // 回调的上下文(如 wpa_driver_scan_params)    unsigned int started:1;     // 是否已开始执行    struct os_reltime time;     // 入队时间    unsigned int bands;         // 涉及的频段};

关键字段

freq = 0 表示多信道操作(如扫描),不锁定特定信道——只要驱动不支持 OFFCHANNEL_SIMULTANEOUS,它就和所有其他 work 互斥freq != 0 表示单信道操作(如 P2P listen 在某信道上),只阻塞同信道的其他 workstarted 标志区分「排队中」和「执行中」——radio_work_done() 只在 started = 1 时触发下一个 work

3.2 radio_add_work() — 加入队列

// external_wpa_supplicant_8/wpa_supplicant/wpa_supplicant.cint radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq,                   const char *type, int next,                   void (*cb)(struct wpa_radio_work *work, int deinit),                   void *ctx){    struct wpa_radio *radio = wpa_s->radio;    struct wpa_radio_work *work;    int was_empty;    work = os_zalloc(sizeof(*work));    work->freq = freq;    work->type = type;    work->wpa_s = wpa_s;    work->cb = cb;    work->ctx = ctx;    if (freq)        work->bands = wpas_freq_to_band(freq);    else if (os_strcmp(type, "scan") == 0 || os_strcmp(type, "p2p-scan") == 0)        work->bands = wpas_get_bands(wpa_s,                         ((struct wpa_driver_scan_params *)ctx)->freqs);    else        work->bands = wpas_get_bands(wpa_s, NULL);    was_empty = dl_list_empty(&wpa_s->radio->work);    if (next)        dl_list_add(&wpa_s->radio->work, &work->list);     // 插入队头    else        dl_list_add_tail(&wpa_s->radio->work, &work->list); // 插入队尾    if (was_empty) {        /* 队列之前是空的——立即调度 */        radio_work_check_next(wpa_s);    } else if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS)               && radio->num_active_works < MAX_ACTIVE_WORKS) {        /* 驱动支持并发 offchannel——尝试立即调度 */        radio_work_check_next(wpa_s);    }    return 0;}

主要功能:

next 参数控制优先级next=1 插入队头(更高优先级),next=0 插入队尾频段自动计算:扫描类型从 wpa_driver_scan_params 的 freqs 中推断频段,非扫描类型从全局配置推断并发策略:队列为空时立即调度;如果驱动支持 OFFCHANNEL_SIMULTANEOUS 且激活数未满,即使有正在执行的 work 也尝试并发

3.3 单信道 vs 多信道的互斥规则

radio work 的互斥逻辑在 radio_work_check_next() 中实现:

当前激活的 work
队列中等待的 work
能否并发?
任意
✅ 立即激活
freq=0(多信道,如扫描)
freq=0
❌ 互斥,排队
freq=0(多信道)
freq!=0(单信道)
❌ 互斥(除非 OFFCHANNEL_SIMULTANEOUS
freq=2412
freq=2412
❌ 同信道互斥
freq=2412
freq=2437
✅ 可并发(不同信道)

设计要点:扫描是 freq=0 的 work——它需要独占射频。这就是为什么扫描过程中不能同时做 P2P 发现或发起连接——它们的 work 都在队列里等着扫描完成。

3.4 radio_work_done() — 完成并触发下一个

// external_wpa_supplicant_8/wpa_supplicant/wpa_supplicant.cvoid radio_work_done(struct wpa_radio_work *work){    struct wpa_supplicant *wpa_s = work->wpa_s;    unsigned int started = work->started;    radio_work_free(work);       // 从队列中移除并释放内存    if (started)        radio_work_check_next(wpa_s);  // 只有已启动的 work 完成时才触发下一个}

关键行为radio_work_done() 只在 started=1 时触发 radio_work_check_next()——如果一个 work 还在排队就被取消(如 radio_remove_works() 调用 cb(work, 1) → deinit),它不会触发下一个 work。这防止了「取消的 work 意外唤醒下一个」的问题。

3.5 radio_remove_works() — 取消扫描

// external_wpa_supplicant_8/wpa_supplicant/wpa_supplicant.cvoid radio_remove_works(struct wpa_supplicant *wpa_s,                        const char *type, int remove_all){    struct wpa_radio_work *work, *tmp;    struct wpa_radio *radio = wpa_s->radio;    dl_list_for_each_safe(work, tmp, &radio->work, struct wpa_radio_work, list) {        if (type && os_strcmp(type, work->type) != 0)            continue;        if (!remove_all && work->wpa_s != wpa_s)            continue;        work->cb(work, 1);   // deinit=1 → 通知回调:这个 work 被取消了        radio_work_free(work);    }    radio_work_check_next(wpa_s);  // 取消后可能释放了射频,检查是否有下一个可用}

主要功能:

按 type 过滤(如 "scan" 只取消扫描类型的 work)remove_all=1 时跨接口取消(如禁用 radio 时清空所有排队操作)回调传 deinit=1 让 work 的 callback 做清理(如释放 wpa_driver_scan_params


四、wpas_trigger_scan_cb() — 实际下发扫描到驱动

回到雷达比喻:参数构建完成,就像雷达确定了扫描范围和模式。radio work 队列是任务调度器——现在轮到扫描任务执行了。wpas_trigger_scan_cb() 就是雷达操作员按下发射按钮的那一刻。

一句话总结:当 radio work 被调度到执行时,wpas_trigger_scan_cb() 被调用——它设置 MAC 随机化、调用 wpa_drv_scan() 下发扫描、处理失败重试。

// external_wpa_supplicant_8/wpa_supplicant/scan.c. 教学简化版——省略 log/wpa_msg 语句static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit){    struct wpa_supplicant *wpa_s = work->wpa_s;    struct wpa_driver_scan_params *params = work->ctx;    int ret;    if (deinit) {        // 清理路径:radio work 被取消时走这里        if (!work->started) {            wpa_scan_free_params(params);            return;        }        wpa_supplicant_notify_scanning(wpa_s, 0);        wpas_notify_scan_done(wpa_s, 0);        wpa_s->scan_work = NULL;        return;    }    // MAC 随机化:仅在 wpa_state <= WPA_SCANNING 时启用    if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) &&        wpa_s->wpa_state <= WPA_SCANNING)        wpa_setup_mac_addr_rand_params(params, wpa_s->mac_addr_scan);    // 随机 MAC 分配失败 → 中止扫描(关键异常路径)    if (wpas_update_random_addr_disassoc(wpa_s) < 0) {        wpa_scan_free_params(params);        radio_work_done(work);        return;    }    if (wpa_s->clear_driver_scan_cache) {        params->only_new_results = 1;    }    wpa_supplicant_notify_scanning(wpa_s, 1);    ret = wpa_drv_scan(wpa_s, params);    if (ret == 0)        wpa_s->curr_scan_cookie = params->scan_cookie;  // vendor scan cookie(QCA 平台用)    wpa_scan_free_params(params);    work->ctx = NULL;    if (ret) {        // 失败路径:检查是否需要重试        int retry = wpa_s->last_scan_req != MANUAL_SCAN_REQ &&            !wpa_s->beacon_rep_data.token;        if (wpa_s->disconnected)            retry = 0;                  // 已断连不重试        if (ret == -EOPNOTSUPP)            retry = 0;                  // 驱动不支持不重试        // ...省略 wpa_msg SCAN_FAILED、状态恢复...        wpa_supplicant_notify_scanning(wpa_s, 0);        wpas_notify_scan_done(wpa_s, 0);        radio_work_done(work);        if (retry) {            wpa_s->scan_req = wpa_s->last_scan_req;            wpa_supplicant_req_scan(wpa_s, 1, 0);  // 1 秒后重试        } else if (wpa_s->scan_res_handler) {            wpa_s->scan_res_handler = NULL;  // 清理回调处理器        }        return;    }    // 成功路径:记录扫描状态(注意 scan_work 在函数末尾,不在 if(ret==0) 内)    os_get_reltime(&wpa_s->scan_trigger_time);    wpa_s->scan_runs++;    wpa_s->normal_scans++;    wpa_s->own_scan_requested = 1;    wpa_s->clear_driver_scan_cache = 0;    wpa_s->scan_work = work;          // 追踪当前激活的扫描——再有请求来会被拒绝}

主要功能:

两条路径deinit=1 是清理路径(work 被取消),deinit=0 是正常执行路径MAC 随机化在这里生效:在调用 wpa_drv_scan() 之前设置随机 MAC 参数失败重试策略:非手动扫描 + 非 EOPNOTSUPP + 非断连状态时,1 秒后自动重试;不重试时清理 scan_res_handler成功后记录 scan_workscan_work = work 在函数最末尾(不在 if (ret == 0) 内)——只有成功下发扫描时才执行到这里(失败路径已提前 return),确保 scan_work 一定被正确设置

wpa_drv_scan() 是驱动接口的虚函数调用,对于 Linux 平台最终落到 driver_nl80211_scan()——它构建 NL80211_CMD_TRIGGER_SCAN 并发送到内核。这是上篇第五章中 wificond 层也调用的同一个内核命令——wificond 和 Supplicant 都可以发扫描命令,取决于 Android 版本和 HAL 层配置。


五、扫描结果怎么回来?—— EVENT_SCAN_RESULTS 的分发链

一句话总结:驱动完成扫描后通过 nl80211 multicast 通知 Supplicant,事件经过 wpa_supplicant_event() 分发到 _wpa_supplicant_event_scan_results(),然后走一条结果处理链:获取扫描结果 → 更新 BSS 缓存 → 判断是否做网络选择。

5.1 事件分发

驱动完成扫描后内核发送 NL80211_CMD_NEW_SCAN_RESULTS,Supplicant 的事件回调被触发:

内核 nl80211 multicast → do_process_drv_event()  → send_scan_event()    → wpa_supplicant_event(EVENT_SCAN_RESULTS, data)      → _wpa_supplicant_event_scan_results(wpa_s, data, own_request, update_only)

_wpa_supplicant_event_scan_results() 的处理优先级链(以下列出主要步骤,完整链路有 20+ 个判断节点):

1.自定义 scan_res_handler(如果有注册)→ 最高优先级,直接消费结果并返回2.AP 模式检查 —— AP/Mesh 模式下提前返回,不做 STA 网络选择3.external_scan 检查 —— 外部请求的扫描结果(如通过 ctrl_iface 触发)提前返回4.OBSS 扫描处理(802.11ax 重叠 BSS 扫描)5.Beacon Report 处理(802.11k 无线测量)6.ML link probe 处理(WiFi 7 多链路探测)7.ap_scan==2 + WPS 搜索中检查8.autoscan(自动扫描模块)9.断连检查(已断连 → 跳过网络选择)10.bgscan(后台扫描模块)11.WPS AP 信息更新12.WNM 处理(无线网络管理)13.认证中状态检查14.6GHz Short SSID match 检测15.WPS PBC Partner Link 检查(PBC 激活且配网扫描未完成 → 直接返回)16.6GHz 触发扫描(Short SSID 匹配后补扫 6GHz)17.radio_work_done() —— 释放射频工作队列槽位,触发下一个排队的 work18.最终落到wpas_select_network_from_last_scan()

5.2 获取扫描结果

在更新 BSS 缓存之前,先要从驱动/内核拉取原始扫描结果:

wpa_supplicant_get_scan_results()  → wpa_drv_get_scan_results()    → driver_nl80211_get_scan_results()      → NL80211_CMD_GET_SCAN(NLM_F_DUMP)        → 内核返回 BSS 列表

内核返回的每个 BSS 都是一个 wpa_scan_res 结构——包含 BSSID、频率、信号强度、TSF 时间戳、IE 数据。Supplicant 逐一提取后进入 BSS 缓存更新流程。

5.3 扫描超时保护

扫描超时不是 Supplicant 设置的——是驱动层在 wpa_driver_nl80211_scan() 中注册的:

// driver_nl80211_scan.c — wpa_driver_nl80211_scan() 中超时注册逻辑timeout = drv->uses_6ghz ? 20 : 10;   // ① 先判 6GHz:是则 20sif (drv->uses_s1g)    timeout += 5;                        // ② S1G 额外加 5sif (drv->scan_complete_events) {    timeout = 30;                        // ③ completion events 最后无条件覆盖为 30s}eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, bss->ctx);eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout,                       drv, bss->ctx);

超时处理函数会尝试 NL80211_CMD_ABORT_SCAN 中止扫描,然后仍去获取已完成的那部分结果——已经扫到的 AP 结果不会因超时而丢失。


六、BSS 缓存管理——Supplicant 的「雷达回波记忆」

一句话总结:Supplicant 维护了一个 BSS 缓存链表——每次扫描结果到达时,新 AP 被加入缓存,已知 AP 被更新信号和 IE,未再出现的 AP 按 scan_miss_count 计数淘汰。

6.1 为什么需要 BSS 缓存?

设想你连着一个 AP,信号在 -55 dBm 到 -72 dBm 之间波动。如果没有 BSS 缓存,每次扫描结果到来时你只看当次的数据——你无法判断「这个 AP 的信号在变弱,可能需要漫游了」。BSS 缓存维护了跨多次扫描的 BSS 状态——信号变化趋势、IE 变更(比如 AP 升级了固件)、消失的 AP。

6.2 wpa_bss_update_scan_res() — 逐条更新

每一条扫描结果都会经过 wpa_bss_update_scan_res() 处理:

// external_wpa_supplicant_8/wpa_supplicant/bss.cvoid wpa_bss_update_scan_res(struct wpa_supplicant *wpa_s,                             struct wpa_scan_res *res,                             struct os_reltime *fetch_time){    const u8 *ssid;    struct wpa_bss *bss;    // 陈旧结果过滤    if (wpa_s->conf->ignore_old_scan_res) {        // 如果 BSS 的时间戳早于 scan_trigger_time,跳过        // ...    }    ssid = wpa_scan_get_ie(res, WLAN_EID_SSID);    if (ssid == NULL || ssid[1] > SSID_MAX_LEN)        return;  // 无有效 SSID,跳过    // P2P 接口的特殊过滤(跳过无 P2P IE 的结果)    // ...    // 在缓存中查找已有 entry(按 BSSID + SSID)    bss = wpa_bss_get(wpa_s, res->bssid, ssid + 2, ssid[1]);    if (bss == NULL)        bss = wpa_bss_add(wpa_s, ssid + 2, ssid[1], res, fetch_time);    else        bss = wpa_bss_update(wpa_s, bss, res, fetch_time);    // 加入 last_scan_res 数组(动态扩容)    if (wpa_s->last_scan_res_used >= wpa_s->last_scan_res_size) {        // ...扩容(初始 32,翻倍增长)...    }    wpa_s->last_scan_res[wpa_s->last_scan_res_used++] = bss;}

主要功能:

BSSID + SSID 联合查重:同一个 AP 的多个 SSID(Multi-BSSID 场景)会创建独立的缓存条目wpa_bss_add() 做的事:分配 wpa_bss 结构 → 复制 IE 数据 → 解析 RSN/HT/VHT/HE/EHT/MLO IE → 加入链表末尾wpa_bss_update() 做的事:比较 IE 变化 → 更新信号强度和频率 → 重置 scan_miss_count = 0 → 移到链表末尾链表按最后更新时间排序——最新扫描到的 BSS 在链表尾部

6.3 BSS 过期淘汰机制

每次扫描结束后,wpa_bss_update_end() 遍历整个 BSS 链表,对未在本轮扫描中出现的 BSS 递增 scan_miss_count

// external_wpa_supplicant_8/wpa_supplicant/bss.cvoid wpa_bss_update_end(struct wpa_supplicant *wpa_s, struct scan_info *info,                        int new_scan){    struct wpa_bss *bss, *n;    if ((info && info->aborted) || !new_scan)        return;  // 扫描被中止或不完整,不淘汰    dl_list_for_each_safe(bss, n, &wpa_s->bss, struct wpa_bss, list) {        if (wpa_bss_in_use(wpa_s, bss))            continue;  // 正在使用的 BSS 不淘汰        if (!wpa_bss_included_in_scan(bss, info))            continue;  // 🔑 BSS 的频率或 SSID 不在本轮扫描范围内——不淘汰        if (bss->last_update_idx < wpa_s->bss_update_idx)            bss->scan_miss_count++;        if (bss->scan_miss_count >=            wpa_s->conf->bss_expiration_scan_count) {            wpa_bss_remove(wpa_s, bss, "no match in scan");        }    }}

淘汰规则表

条件
行为
原因
BSS 的频率或 SSID 不在本轮扫描范围内
跳过,不递增计数
wpa_bss_included_in_scan()
 同时检查频率和 SSID——两者之一不在就跳过淘汰(扫 2.4GHz 时不能因为 5GHz AP 没出现就淘汰它;扫特定 SSID 时也不能因为 wildcard 未匹配的 AP 没出现就淘汰它)
scan_miss_count >= bss_expiration_scan_count
删除
多次扫描未出现,AP 已消失或已离开覆盖范围
wpa_bss_in_use()
 返回 true
不淘汰
保护顺序:current_bss(已连接)→ ml_connect_probe_bss(MLO 链路探测)→ wnm_target_bss(WNM 漫游目标,在 #ifdef CONFIG_WNM 下)→ SSID guard(不同 SSID 则跳过后续检查)→ bssid/pending_bssid 比较 → MLO link BSS。注意:SSID guard 是 early-return——若 BSS 的 SSID 与 current_bss 不同,直接返回 0(不在使用中),后续的 BSSID/MLO 检查不会执行。这防止不同网络中恰好 BSSID 相同的 BSS 被误保护
扫描被中止(aborted
不淘汰
不完整扫描不能作为「AP 消失」的可靠依据

6.4 BSS 生命周期总结

新 AP 首次出现:  → wpa_bss_add() → scan_miss_count = 0 → 加入链表末尾已知 AP 再次出现:  → wpa_bss_update() → 更新信号/IE → scan_miss_count = 0 → 移到链表末尾已知 AP 本轮未出现:  → scan_miss_count++scan_miss_count 超过阈值:  → wpa_bss_remove() → 从链表移除 → 释放内存特殊保护:  current_bss / wnm_target_bss → 不淘汰(即使 scan_miss_count 很高)

七、扫描结果怎么变成连接决策?——网络选择收口

一句话总结:扫描的目的是连接。wpas_select_network_from_last_scan() 遍历所有已启用网络配置,用 BSS 缓存中的最佳匹配来决定是否发起连接。

这一节只做收口——网络选择的完整逻辑(WifiNetworkSelector 评分算法、自动连接策略)是 06 章连接的核心内容。这里只展示扫描结果怎么流向连接决策:

// 概念示意图——以下是逻辑简化版,非真实源码行,真实实现在 wpa_supplicant_pick_network() 中// 实际调用链: _wpa_supplicant_event_scan_results() → wpa_supplicant_pick_network()//             → wpa_supplicant_select_bss() → wpa_supplicant_connect()//// 逻辑要点(本节仅做收口,06 章展开)://   1. 遍历所有已启用网络配置(按 priority 分层,不是简单链表遍历)//   2. wpa_supplicant_select_bss() 在 BSS 缓存中匹配多要素(SSID/key_mgmt/加密方式/频率,详见 06 章)//   3. wpa_scan_result_compar() 排序:WPA/WPA2 支持 > Privacy > SAE 优先(有条件)> est_throughput > 频段偏好 > SNR//   4. 找到最佳 BSS → wpa_supplicant_connect() → 进入连接流程

关键点

wpa_supplicant_select_bss() 匹配多要素:SSID、key_mgmt(WPA2/WPA3)、加密方式(pairwise_cipher 如 CCMP/GCMP)、频率在当前信道列表中——具体匹配逻辑委托给 wpa_scan_res_match()(详见 06 章连接篇)wpa_scan_result_compar() 的排序优先级(从高到低): WPA/WPA2 支持有 RSN IE 的 AP 优先于无 RSN IE 的; Privacy(加密能力)优先于 Open; SAE 认证的 AP 在 SNR 足够好时(≥ GREAT_SNR 或不低于对方)优先于 PSK——低 SNR 时不保证 WPA3 优先; SNR 差异 < 7dB 时比 est_throughput(6GHz 网络无条件触发 est_throughput 比较,因为 LPI/VLP 功率规则导致 SNR 不可直接跨频段比较); 频段偏好(6GHz > 5GHz > 2.4GHz,SNR 差异 < 5dB 时生效); 最终 fallback 到 SNR 全值比较找到最佳 BSS 后立即发起连接——supplicant 的 bgscan 会主动漫游,不需要 Framework 的指令


八、总结

8.1 Supplicant 内部扫描的完整时间线

1. 外部请求到达(Framework / eloop 定时器)   → wpa_supplicant_req_scan(sec, usec)     → eloop_register_timeout → 排队2. eloop 超时到期   → wpa_supplicant_scan()     ├── 前置检查(10+ 种不扫描的情况)     ├── 构建 SSID 列表     ├── 构建频率列表(7 级优先级)     ├── 构建额外 IE     └── wpa_supplicant_trigger_scan()       → radio_add_work("scan", wpas_trigger_scan_cb)3. radio work 被调度执行   → wpas_trigger_scan_cb()     ├── MAC 随机化设置     ├── wpa_drv_scan() → driver_nl80211_scan()     │   → NL80211_CMD_TRIGGER_SCAN → 内核     └── eloop_register_timeout(timeout, scan_timeout)  // 10s/20s/30s,见 §5.34. 内核报告 NL80211_CMD_NEW_SCAN_RESULTS   → wpa_supplicant_event(EVENT_SCAN_RESULTS)     ├── 取消 10 秒超时     ├── wpa_drv_get_scan_results()     ├── wpa_bss_update_scan_res() × N次     │     └── wpa_bss_add() / wpa_bss_update()     ├── wpa_bss_update_end()     │     └── 清理过期 BSS     ├── radio_work_done()     └── wpas_select_network_from_last_scan()           └── 如果找到匹配 → wpa_supplicant_associate()

8.2 关键模块职责

模块
核心函数
职责
eloop 调度wpa_supplicant_req_scan()
去抖动 + 异步调度扫描
参数构建wpa_supplicant_scan()
SSID/频率/IE 三列表构建
射频排队radio_add_work()
 / radio_work_done()
保证单信道操作互斥
扫描下发wpas_trigger_scan_cb()
MAC 随机化 + wpa_drv_scan() + 失败重试
事件分发_wpa_supplicant_event_scan_results()
20+ 步处理优先级链
缓存管理wpa_bss_update_scan_res()
 / wpa_bss_update_end()
添加→更新→过期淘汰
网络选择wpas_select_network_from_last_scan()
最佳 BSS 匹配(收口到连接)

8.3 关键设计决策

异步调度而非直接调用:扫描通过 eloop 定时器触发,允许去抖动和优先级管理radio work 串行化:不是 Supplicant 故意慢,而是射频物理上的单工性质决定的——同一时间只能在一个信道上操作BSS 缓存跨扫描持久化scan_miss_count 机制让缓存自动淘汰消失的 AP,同时保护正在使用的 BSS扫描失败有重试:非手动扫描失败后自动 1 秒重试——用户不需要感知底层的偶发失败

回到雷达比喻:整个 supplicant 扫描引擎就像雷达的自动维护程序——自己决定何时扫、扫什么、结果怎么处理,而 Framework 只负责告诉它"我需要看什么"。radio work 是任务调度器,BSS 缓存是雷达回波记忆,eloop 是心跳时钟。三者协作,让这台雷达在无人值守时也能保持对环境的感知。

References

[1] external/wpa_supplicant_8: https://android.googlesource.com/platform/external/wpa_supplicant_8/

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-05 10:49:41 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/834207.html
  2. 运行时间 : 0.202423s [ 吞吐率:4.94req/s ] 内存消耗:5,054.10kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=2643011ebe9f4c9b4fbcc6a7200e57da
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001125s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001836s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000815s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000763s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001656s ]
  6. SELECT * FROM `set` [ RunTime:0.000593s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001994s ]
  8. SELECT * FROM `article` WHERE `id` = 834207 LIMIT 1 [ RunTime:0.005137s ]
  9. UPDATE `article` SET `lasttime` = 1783219781 WHERE `id` = 834207 [ RunTime:0.018307s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000261s ]
  11. SELECT * FROM `article` WHERE `id` < 834207 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000647s ]
  12. SELECT * FROM `article` WHERE `id` > 834207 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000462s ]
  13. SELECT * FROM `article` WHERE `id` < 834207 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000876s ]
  14. SELECT * FROM `article` WHERE `id` < 834207 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000726s ]
  15. SELECT * FROM `article` WHERE `id` < 834207 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.000734s ]
0.204117s