当前位置:首页>Linux>Linux 调度 - 从 schedule 看调度类顺序

Linux 调度 - 从 schedule 看调度类顺序

  • 2026-08-18 23:11:51
Linux 调度 - 从 schedule 看调度类顺序

本篇范围

本篇只钉一件事:

CPU 决定「下一个跑谁」时,内核会按固定顺序询问若干调度模块(sched_class)。
这个顺序从哪来?运行时怎么用?sched_ext(BPF 调度)怎样插进来?

行号基于 linux-source-7.0.0Makefile 为 7.0.12)。

官方源码:

  • • https://cdn.kernel.org/pub/linux/kernel/v7.x/linux-7.0.12.tar.xz
  • • 目录:https://cdn.kernel.org/pub/linux/kernel/v7.x/
  • • 在线浏览:https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/?h=v7.0.12
wget https://cdn.kernel.org/pub/linux/kernel/v7.x/linux-7.0.12.tar.xz
tar -xf linux-7.0.12.tar.xz

第 1 步:从一次调度说起

先钉死的要点与结论

后面会跟源码,但读之前先把「本篇认重要」的几条摆出来(这一节主要围着它们转):

  1. 1. 调度原则next 是任务(task_struct *),不是调度器。决定它时不是把本 CPU 所有可跑任务摊平打分,而是 按固定顺序问各个调度类(stop → dl → rt → fair → ext → idle)——前面的类有可跑任务就返回,后面的类不再问。
  2. 2. 这个顺序写在链接脚本里SCHED_DATA),不是运行时注册表;地址更小的类更先被问到。
  3. 3. fair 排在 ext 前面 → fair 先于 ext 被询问。BPF / sched_ext 不是靠把 ext 链到 fair 上面来「抢优先级」。
  4. 4. BPF「接管」靠两件事:把任务挂到 ext 类上,以及扫描时 跳过空的 fair / 未启用的 ext;链接顺序本身不变。
  5. 5. 全开还是 PARTIAL,由当前加载的那份 BPF 的 flags 决定;PARTIAL 下通常要显式 SCHED_EXT 才进 BPF。

调用入口记住一条链即可:

schedule → __schedule → __pick_next_task(按类询问,交出 next)

下面从入口跟起,把结论逐条对上源码。

这一步要搞清什么

本 CPU 上正在跑的进程,过一会儿可能要换:睡了、时间片到了、被更高优先级任务打断……
内核最终会进 schedule():找出 下一个要跑的任务,再做上下文切换。

这里的 next 是任务(类型是 struct task_struct *),不是调度器。
当前任务常叫 prev,即将上场的叫 next;各调度类(fair / rt / ext …)只是被依次询问「你这边有没有可跑任务可交出来当 next」。

本文不关心「怎么切寄存器」,只关心调用一路跟到哪里、这个 next 任务是在哪一层被决定的

先把调用链说清楚

一次普通调度,大致经过四层包装,一层比一层更靠近「决定 next」:

  1. 1. schedule()core.c:6975
    对外入口。进程主动让出 CPU、或内核路径里调用 schedule() 时从这里进。
    它自己几乎不做事,转给下面的循环。
  2. 2. __schedule_loop()core.c:6966
    关抢占,调用一次 __schedule(里面会挑 next、可能切换);
    出来后再看 need_resched()——若仍标着「该调度」,再挑一轮
    详见下方源码注释(为何已经选过还要再选)。
  3. 3. __schedule()core.c:6748
    真正干活的大函数:拿到本 CPU 的运行队列 rq、当前任务叫 prev
    若 prev 要睡,先把它从可跑队列里弄下去;
    然后调用下一层去得到 next,最后做上下文切换。
    「下一个跑谁」不是在这一层算出来的,而是交给下面的 pick。
  4. 4. pick_next_task() → __pick_next_task()(调用点 core.c:6836,实现 core.c:5910
    名字里的 pick = 从本 CPU 可跑任务里 挑出下一个
    多数配置下前者只是薄包装,后者才是按调度类挨个询问的地方(第 2 步展开)。

串起来就是:

schedule()                      // 入口:我要调度了
  → __schedule_loop()           // 关抢占,必要时多调几次
  → __schedule()                // 处理 prev,准备切换
       └─ pick_next_task()      // 请给出 next
            └─ __pick_next_task()  // 真正决定 next(按类询问)

对照源码

下面按调用顺序看。主线用完整语句标出;大约三成旁支用注释说明「它是什么 / 为什么在这」,不必深追。

1)schedule:入口

/* core.c:6975 */
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk =
 current;
    /*
     * current:本 CPU 当前正在跑的那个任务(宏,不是普通局部变量)。
     * task_struct:描述一个任务/线程的内核对象(pid、状态、sched_class…)。
     */


    /* ... 调试断言等,可忽略 ... */


    /*
     * task_is_running:状态是否还是 TASK_RUNNING(可跑)。
     * 若已经不是(例如准备睡眠),先做 workqueue / block 层收尾,
     * 再进真正的调度。细节与「谁当下一个任务」无关。
     */

    if
 (!task_is_running(tsk))
        sched_submit_work(tsk);

    /*
     * SM_NONE:sched_mode 之一,表示「普通主动 schedule」。
     * 另有 SM_PREEMPT(抢占进来)、SM_IDLE(idle 路径)等,
     * 主要影响 __schedule 里对「是否算抢占 / 是否阻塞 prev」的判断。
     */

    __schedule_loop(SM_NONE);

    sched_update_worker(tsk);    /* worker 线程记账,可忽略 */
}

2)__schedule_loop:可能多轮「挑 next」

/* core.c:6966 */
static
 __always_inline void __schedule_loop(int sched_mode)
{
    do
 {
        /*
         * preempt_disable:禁止内核抢占。
         * 调度过程中不能再被别的路径半路打断另开一次 schedule。
         */

        preempt_disable();
        __schedule(sched_mode);    /* 内部:挑 next,必要时 context_switch */
        /*
         * 开回抢占计数,但「先不要因为 need_resched 立刻再抢进 schedule」:
         * 交给下面 while 统一决定要不要再来一轮。
         */

        sched_preempt_enable_no_resched();
    } while (need_resched());
    /*
     * need_resched():当前任务(current)是否还挂着 TIF_NEED_RESCHED。
     * __schedule 开头会清掉 prev 的该标记;若循环条件仍为真,
     * 说明「这一轮结束之后、或结束过程中」又被标了一次「该调度」,
     * 必须再进 __schedule 重新挑 next——不是 while 闲的。
     */

}

为何已经挑过 next,还可能再进一轮?

__schedule 整段里抢占是关着的;中断仍可能进,但不能在中途再开一次完整 schedule
常见做法是:中断 / 唤醒路径只打上 TIF_NEED_RESCHED,等 __schedule 返回、本循环看到 need_resched() 再选。典型情况包括:

  1. 1. 唤醒了更高优先级任务
    别的 CPU 或本 CPU 中断路径里 try_to_wake_up → 若应抢占当前任务,就 resched_curr 置位。
    本轮 __schedule 可能已挑完甚至切走过;回到 loop 时标记还在 → 再挑。
  2. 2. 时钟节拍认为该让出
    scheduler_tick 发现时间片/延迟等需要换人,置 need_resched
    若恰好落在「正在 __schedule / 刚返回」的窗口,本轮 while 会再进一次。
  3. 3. IPI 远程求调度
    其它 CPU 对你发 reschedule IPI(例如把任务迁过来、或需要你立刻重算),同样落成 need_resched
  4. 4. 本轮几乎没切走,但标记已在
    例如 prev == next(没人可换),或刚清完标记又在关抢占窗口里被再次置位;
    sched_preempt_enable_no_resched 不会自动再进 schedule,靠 while 补上,避免漏调度。

直观记:

__schedule 挑好 next(甚至切走再切回)
    → 回到 loop
    → need_resched 仍真? → 局面可能已变 → 再 __schedule 重挑
    → 否则才真正离开 schedule()

主线仍是「一次 __schedule 里如何按类交出 next」;loop 只是保证 标记不会白打

3)__schedule:处理 prev,再请出 next

函数从 core.c:6748 起很长。按阶段读:

/* core.c:6748 起,结构示意 + 关键原句 */
static
 void __schedule(int sched_mode)
{
struct task_struct *prev, *next;

struct rq_flags rf;

struct rq *rq;

    int
 cpu;
    /* ... 局部变量:preempt、prev_state 等 ... */


    cpu = smp_processor_id();    /* 当前 CPU 编号 */
    rq = cpu_rq(cpu);        /* 该 CPU 的运行队列 runqueue */
    prev = rq->curr;        /* 当前正在跑的任务 → 后面叫 prev */

    /* ... schedule_debug / hrtick / livepatch 等旁路,可跳过 ... */


    local_irq_disable();        /* 关本 CPU 中断,避免选 next 时被打断 */
    /* ... RCU / migrate_disable 记账 ... */


    rq_lock(rq, &rf);        /* 拿住本 rq 的锁;调度核心数据结构要串行改 */
    /* ... update_rq_clock:更新 rq 时钟,供 fair 等算时间 ... */


    prev_state = READ_ONCE(prev->__state);
    /*
     * __state:任务状态。TASK_RUNNING=可跑;非 0 常表示睡眠/停止等。
     * 主动 schedule 且 prev 已非 RUNNING → 通常要把 prev 移出可跑队列。
     */

    if
 (sched_mode == SM_IDLE) {
        /* idle 专用捷径:队列空则继续跑自己,不必 pick */

        /* ... */

    } else if (!preempt && prev_state) {
        try_to_block_task(rq, prev, &prev_state, /* ... */);
        /* 把要睡的 prev 从 rq 可跑集合里弄下去(出队) */

    }

pick_again:
    /*
     * 本文主线:请本 CPU 的调度逻辑给出下一个任务。
     * 返回值 next:struct task_struct *,即将上场的任务。
     * rq->donor:与代理执行相关,初读可当成「从谁的视角挑」;
     * 多数路径下等价于从当前 rq 挑。
     */

    next = pick_next_task(rq, rq->donor, &rf);
    rq_set_donor(rq, next);
    rq->next_class = next->sched_class;    /* 记下 next 属于哪个调度类 */

    /* ... 若 next 被 mutex 挡住,可能 find_proxy_task 再挑;初读可跳过 ... */


picked:
    clear_tsk_need_resched(prev);    /* 清「该调度」标记 */
    /* ... */


    if
 (likely(prev != next)) {
        /*
         * 真发生切换:rq->curr = next,再 context_switch(prev, next)
         * 保存 prev 寄存器 / 恢复 next 寄存器。细节不在本文范围。
         */

        /* ... context_switch ... */

    }
    /* ... 放锁、开中断、可能返回到 next 的执行流 ... */

}

要点:__schedule 负责「场面」(锁、阻塞 prev、切换);谁当 next 交给 pick_next_task

4)pick_next_task:多数配置下只是转一手

/* core.c:6465(无 CONFIG_SCHED_CORE 时) */
static
 struct task_struct *
pick_next_task
(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
    /*
     * 有 SCHED_CORE(SMT 同核协同)时,这里会更复杂,见第 2 步旁支。
     * 未开时就是直接进 __pick_next_task。
     */

    return
 __pick_next_task(rq, prev, rf);
}

__pick_next_taskcore.c:5910)才是「按调度类挨个问」的地方,第 2 步展开。

到这里记住:

schedule → __schedule → __pick_next_task:next(下一个任务)在最后一层被决定。

决定 next 时,内核怎么组织这些任务?

容易以为:本 CPU 上所有可跑任务排成一张大表,__pick_next_task 从头扫到尾,挑一个「最该跑」的。

实际不是这样。 任务按策略分在不同调度类里(rt 管实时、fair 管普通进程、ext 管 BPF……),各自有自己的队列和算法。
__pick_next_task 做的是:按固定顺序问各个调度类——「你这类有没有可跑任务?」——而不是把全机任务摊平扫一遍。

  1. 1. 先问排在最前的那一类。
  2. 2.  → 用它返回的那个任务当 next,后面的类不再问
  3. 3. 没有 → 再问下一类,直到有类交出任务。
问 stop → 有可跑的?有就返回;没有再问
问 dl   → 有可跑的?有就返回;没有再问
问 rt   → …
问 fair → …
问 ext  → …
问 idle → 总能交出 idle(兜底)

例如有 SCHED_FIFO 可跑时,根本不会去 fair 里挑普通进程。

大约管什么
定义
stop
停机/迁移等特殊任务
stop_task.c:99
dl
SCHED_DEADLINEdeadline.c:3427
rt
SCHED_FIFO
 / SCHED_RR
rt.c:2590
fair
普通进程(EEVDF)
fair.c:13953
ext
sched_ext / BPF(可选)
ext.c:3521
idle
真没人可跑
idle.c:569

全文主线因此变成:

先问哪一个调度类?这个顺序从哪来?

下一步打开 __pick_next_task,看它怎么按类循环。


第 2 步:在 __pick_next_task 里看见「按类循环」

上一层只负责请出 next;真正决定「哪个任务」在这里(core.c:5910)。
读法同第 1 步:主线看语句,旁支看注释。

/* core.c:5909–5965 */
/*
 * Pick up the highest-prio task:
 * 在本 CPU 的 rq 上,挑出优先级最高的那一类里的下一个任务。
 * 「类间谁更高」= 后面 for_each_active_class 的遍历顺序(第 3–4 步)。
 */

static
 inlinestruct task_struct *
__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
    __must_hold(__rq_lockp(rq))    /* 调用方必须已持有 rq 锁 */
{

    const
struct sched_class *class;    /* 当前问到的调度类 */
struct task_struct *p;
            /* 某一类交出来的候选任务 */

    rq->dl_server = NULL;    /* deadline server 相关,初读可忽略 */

    /*
     * scx_enabled():是否已加载并启用了 BPF 调度器(sched_ext)。
     * 为真时关掉下面的 fair 捷径,一律走 restart 按类扫描。
     * 原因:BPF 可能接管了大量原 fair 任务,捷径假设不再成立。
     */

    if
 (scx_enabled())
        goto
 restart;

    /*
     * ========== fair 捷径(优化路径,不是主线语义)==========
     * 若本 rq 上可跑任务「几乎全是 fair」,且 prev 也不比 fair 更高类,
     * 就直接调 fair,免去 for 循环问 stop/dl/rt/…。
     *
     * sched_class_above(a, b):a 是否比 b「更高类」(地址更小更优先)。
     * nr_running:本 rq 可跑任务总数。
     * cfs.h_nr_queued:fair/cfs 侧排队任务数。
     * 两者相等 ≈ 没有 rt/dl 等其它类的可跑任务。
     */

    if
 (likely(!sched_class_above(prev->sched_class, &fair_sched_class) &&
           rq->nr_running == rq->cfs.h_nr_queued)) {

        p = pick_next_task_fair(rq, prev, rf);
        /*
         * RETRY_TASK:特殊哨兵值,表示「现在定不了,请重来」
         * (例如 balance 后队列变了)。不是真正的 task_struct。
         */

        if
 (unlikely(p == RETRY_TASK))
            goto
 restart;

        /*
         * fair 也没人可跑 → 通常只剩 idle。
         * put_prev_set_next_task:从 prev 切到 p 时,通知两类
         * 「你不再是 curr / 你是 curr」的成对回调,初读知职责即可。
         */

        if
 (!p) {
            p = pick_task_idle(rq, rf);
            put_prev_set_next_task(rq, prev, p);
        }

        return
 p;    /* 捷径命中:next 已定,函数返回 */
    }

restart:
    /*
     * ========== 慢路径 / 主线:按调度类挨个问 ==========
     * prev_balance:挑 next 前,必要时做负载均衡(拉任务过来等)。
     * 与「类间顺序」无关,失败/变化可能配合 RETRY_TASK 重来。
     */

    prev_balance(rq, prev, rf);

    /*
     * for_each_active_class:从最高类扫到最低类(宏,第 3 步展开)。
     * 顺序大致:stop → dl → rt → fair → ext → idle。
     * 「前面的类只要交出非空任务,后面的类不会被问到」。
     */

    for_each_active_class(class) {
        if
 (class->pick_next_task) {
            /*
             * 目前主要是 fair 填了 .pick_next_task。
             * 接口更「完整」:知道 prev,便于 fair 做特殊优化。
             */

            p = class->pick_next_task(rq, prev, rf);
            if
 (unlikely(p == RETRY_TASK))
                goto
 restart;
            if
 (p)
                return
 p;    /* 该类有可跑任务 → 就是 next */
        } else {
            /*
             * stop / dl / rt / ext / idle 等多走 .pick_task:
             * 「你这类里挑一个可跑的出来;没有就返回 NULL」。
             */

            p = class->pick_task(rq, rf);
            if
 (unlikely(p == RETRY_TASK))
                goto
 restart;
            if
 (p) {
                put_prev_set_next_task(rq, prev, p);
                return
 p;    /* 同上:有货就返回,不再问后面 */
            }
        }
        /* p == NULL:该类没人,循环继续问下一类 */

    }

    /*
     * 理论上走不到:idle 类在链接顺序最后,总能交出 per-CPU idle 任务。
     * 若仍为空,说明调度类链接/实现坏了。
     */

    BUG();
}

这一步只要带走三句话:

  1. 1. 有 scx 时不走 fair 捷径,直接按类扫。
  2. 2. 捷径只是「全是 fair」时的加速,语义上仍等价于「最终会落到 fair/idle」。
  3. 3. 主线是 for_each_active_class:按类顺序问,先交出任务的类赢得 next。
    类间优先级 = 这个 for 的遍历顺序,不是另写一张数字表。

CONFIG_SCHED_CORE 是什么(旁支)

Kconfig:kernel/Kconfig.preempt:151,全称 Core Scheduling for SMT

  • • 针对 SMT / 超线程:同一物理核上的多个逻辑 CPU(siblings)可以协同选任务。
  • • 打开后,可用 prctl(PR_SCHED_CORE) 把任务划进同一 core group;选 next 时尽量让 siblings 跑同组任务,对不上就让一侧 idle。
  • • 用途包括缓解部分 SMT 侧信道、减少同核干扰(确定性/性能)。
  • • 发行版常编进内核但默认不用;不用时几乎无额外开销。
  • • 和本文主线(sched_class 谁先被问)正交:有无 SCHED_CORE,类间顺序仍是那一套。

对调用的影响:

配置
pick_next_task
__schedule 调用的那个)
未开 SCHED_CORE
core.c:6465
:直接 return __pick_next_task(...)
开了 SCHED_CORE
core.c:6011
 起:可能走 core 协同逻辑;内部仍会用到按类扫描

开了之后,同文件还有一份更简单的按类扫描,只走 .pick_task(给 core 路径用):

/* core.c:5990–6003 */
static
 inline struct task_struct *pick_task(struct rq *rq, struct rq_flags *rf)
{
    const
struct sched_class *class;
struct task_struct *p;


    rq->dl_server = NULL;

    for_each_active_class(class) {
        p = class->pick_task(rq, rf);
        if
 (p)
            return
 p;
    }

    BUG(); /* The idle class should always have a runnable task. */
}

初读知「同样按类问」即可;SMT cookie / forced-idle 细节可后看。

下一步:这个 for 的头尾指针、以及「下一类」怎么跳,在 sched.h 里。


第 3 步:展开 for_each_active_class,看见头尾指针

第 2 步的 for_each_active_class(class) 不是普通 class++
它从链接排出的「最高类」走到「最低类」,每一步用 next_active_class 前进——多数时候加一格,有 scx 时可能再跳过一格空类

宏:从哪扫到哪

/* sched.h:2714–2716:头尾是链接脚本里的地址标号(第 4 步) */
extern
struct sched_class __sched_class_highest[];
extern
struct sched_class __sched_class_lowest[];

/* sched.h:2746–2750 */

#define for_active_class_range(class, _from, _to)                \
    for (class = (_from); class != (_to); class = next_active_class(class))


#define for_each_active_class(class)                        \
    for_active_class_range(class, __sched_class_highest, __sched_class_lowest)

/*
 * 展开后等价于:
 *   for (class = 最高类;
 *        class != 最低类标号;          // 不含终点
 *        class = next_active_class(class))
 * 注意:这里用 != 和 next_active_class,不是简单的 class++。
 * (另有 for_each_class 用 class++ 扫「全部类」,含未激活的;选 next 用 active 版。)
 */


/* sched.h:2752 */

#define sched_class_above(_a, _b)    ((_a) < (_b))

/* 地址更小 → 类更高 → 更先被 for 问到 */

next_active_class:重点看 scx 那两段

内存里类对象大致排成一排(第 4 步钉死):

stop → dl → rt → fair → ext → idle

class++ 就是顺着这排往后移一格。有 CONFIG_SCHED_CLASS_EXT 时,移完还可能再移一格,用来跳过当前没必要问的类:

/* sched.h:2724–2737 */
/*
 * Iterate only active classes. SCX can take over all fair tasks or be
 * completely disabled. If the former, skip fair. If the latter, skip SCX.
 *
 * 官方注释已经点明:全开 scx → 跳过 fair;没启用 scx → 跳过 ext。
 */

static
 inline const struct sched_class *
next_active_class
(const struct sched_class *class)
{
class++;
    /* 先正常走到「下一格」 */

#ifdef CONFIG_SCHED_CLASS_EXT

    /*
     * ---------- 情况 A:跳过 fair ----------
     * scx_switched_all():BPF 已「全量接管」——原来挂在 fair 上的
     * 普通任务大都迁到 ext 了(开关何时置位见第 7 步)。
     *
     * 此时 fair 队列几乎没人,每次 schedule 仍问 fair 是浪费。
     * 若本次 class++ 刚好落到 &fair_sched_class,再 ++ 一次,直接去 ext。
     *
     * 注意比较时机:是「++ 之后」的指针和 &fair 比。
     * 例如当前停在 rt,next 时:
     *   class++ → fair → 条件成立再 ++ → ext
     * 于是扫描序列变成:… → rt →(跳过 fair)→ ext → …
     *
     * 链接顺序仍是 fair 在 ext 前;这里只是迭代时少问空类,
     * 并没有把 ext「提升」到 fair 上面。
     */

    if
 (scx_switched_all() && class == &fair_sched_class)
class++;


    /*
     * ---------- 情况 B:跳过 ext ----------
     * scx_enabled():是否已加载并启用某个 BPF 调度器。
     *
     * 内核可能编译了 ext_sched_class(开了 CONFIG_SCHED_CLASS_EXT),
     * 但运行时还没 load 任何 BPF。这时问 ext 没有意义。
     * 若 class++ 落到 &ext_sched_class,再 ++,直接去 idle。
     *
     * 例如当前停在 fair,且没启用 scx:
     *   class++ → ext → 条件成立再 ++ → idle
     * 扫描序列:… → fair →(跳过 ext)→ idle
     */

    if
 (!scx_enabled() && class == &ext_sched_class)
class++;

#endif

    return
 class;
}

两个开关本身(sched.h:1830–1834):

DECLARE_STATIC_KEY_FALSE(__scx_enabled);    /* 已 load BPF 调度器 */
DECLARE_STATIC_KEY_FALSE(__scx_switched_all);    /* fair 类任务已迁到 SCX */

#define scx_enabled()        static_branch_unlikely(&__scx_enabled)

#define scx_switched_all()    static_branch_unlikely(&__scx_switched_all)

/*
 * static_branch:运行时可开关的「极便宜」分支(内核常用技巧)。
 * 默认都是 false:没 load BPF 时,上面两段 if 都不进,等价于纯 class++。
 */

对照三种常见扫描结果(链接顺序不变,变的是「跳谁」):

没 load BPF:     stop → dl → rt → fair → (跳过 ext) → idle
BPF + 部分接管:  stop → dl → rt → fair → ext → idle      // 两段 if 都不跳
BPF 全开:        stop → dl → rt → (跳过 fair) → ext → idle

没开 CONFIG_SCHED_CLASS_EXT 时:没有 ext 对象,也没有这两段 if;scx_enabled() 等编译成恒 false

这一步带走什么

  1. 1. for_each_active_class = 从 __sched_class_highest 走到 lowest,步进函数是 next_active_class
  2. 2. next_active_class 先 class++;有 scx 配置时,可能再跳过 空的 fair 或 未启用的 ext
  3. 3. 跳过 ≠ 改优先级:fair 在链接里仍排在 ext 前面。

运行时「怎么扫」说到这。还缺的几块,后面按这个顺序补:

第 4 步  这一排 stop→…→idle 谁写死的?     → 链接脚本 SCHED_DATA
第 5 步  每个 sched_class 对象从哪来?       → DEFINE_SCHED_CLASS + 编译
第 6 步  ext 何时出现在这一排里?           → CONFIG_SCHED_CLASS_EXT
第 7 步  第 3 步两个开关谁、何时置位?       → 加载 BPF / PARTIAL
第 8 步  任务怎么挂到某一类上?             → policy → __setscheduler_class

第 4 步:这一排顺序谁写死的?(链接脚本)

第 3 步里 class++ 能成立,是因为各 sched_class 在内存里已经排成连续一排
__sched_class_highest / lowest不是 C 里手写的数组,而是链接脚本里的地址标号。

/* include/asm-generic/vmlinux.lds.h:153–162 */
#define SCHED_DATA                \
    STRUCT_ALIGN();                \
    __sched_class_highest = .;        \
    *(__stop_sched_class)            \
    *(__dl_sched_class)            \
    *(__rt_sched_class)            \
    *(__fair_sched_class)            \
    *(__ext_sched_class)            \
    *(__idle_sched_class)            \
    __sched_class_lowest = .;

/*
 * 链接器按上面顺序,把名为 __xxx_sched_class 的 section
 * 拼进 vmlinux。拼完后:
 *   highest 标在第一格之前,lowest 标在最后一格之后。
 * for 从 highest 扫到 lowest,class++ 就是沿这排往后走。
 */

因此固定顺序是:

低地址 ← 先被 for 问到
  stop → dl → rt → fair → ext → idle
高地址 ← 最后才问到

「谁更高」用指针比大小(第 3 步已出现):

/* sched.h:2752 */
#define sched_class_above(_a, _b)    ((_a) < (_b))  /* 地址更小更优先 */

钉死:fair 在 ext 前面 → fair 先于 ext 被询问。
第 3 步的「跳过」只是少问空格,不能把 ext 插到 fair 前面。

下一问:链接脚本收集的是 section 名;那每个 xxx_sched_class 对象是谁放进这些 section 的?


第 5 步:每个 class 对象从哪来?

答:各调度实现文件里用宏定义全局表,编译进具名 section,再被第 4 步的 SCHED_DATA 收进去。
没有运行时 register_sched_class()

/* sched.h:2709–2712 */
#define DEFINE_SCHED_CLASS(name) \
const struct sched_class name##_sched_class \
    __aligned(__alignof__(struct sched_class)) \
    __section("__" 
#name "_sched_class")
/*
 * 例:DEFINE_SCHED_CLASS(fair) → 符号 fair_sched_class,
 * section 名 "__fair_sched_class",正好对上 SCHED_DATA 里的
 * *(__fair_sched_class)。
 */

三类示例(注意 fair 有 .pick_next_task,ext 通常只有 .pick_task——对应第 2 步分支):

/* stop_task.c:99 起 */
DEFINE_SCHED_CLASS(stop) = {
    .pick_task        = pick_task_stop,
    /* ... enqueue/dequeue/... */

};

/* fair.c:13953 起 */

DEFINE_SCHED_CLASS(fair) = {
    .pick_task        = pick_task_fair,
    .pick_next_task        = pick_next_task_fair,  /* 多数类没有 */
    /* ... */

};

/* ext.c:3521 起(需编进内核,见第 6 步) */

DEFINE_SCHED_CLASS(ext) = {
    .pick_task        = pick_task_scx,
    /* 没有 .pick_next_task */

    /* ... */

};
位置
对象
section
stop_task.c:99stop_sched_class__stop_sched_class
deadline.c:3427dl_sched_class__dl_sched_class
rt.c:2590rt_sched_class__rt_sched_class
fair.c:13953fair_sched_class__fair_sched_class
ext.c:3521ext_sched_class__ext_sched_class
idle.c:569idle_sched_class__idle_sched_class

这些 .c 怎么进镜像(仍不是运行时注册):

# kernel/sched/Makefile:39–42
obj-y += core.o fair.o build_policy.o build_utility.o
/* build_utility.c:84 */
#include "stop_task.c"


/* build_policy.c:50–64 */

#include "idle.c"

#include "rt.c"

#include "deadline.c"

#ifdef CONFIG_SCHED_CLASS_EXT

# include "ext.c"    /* 没开配置就没有 ext 对象 → 第 6 步 */

#endif

串起来:

DEFINE_SCHED_CLASS → 各自 section → SCHED_DATA 排序 → for_each_active_class 挨个问

第 6 步:ext 何时出现在这一排里?

第 5 步末尾的 #ifdef 说明:ext 不是永远在镜像里。

# kernel/Kconfig.preempt:169
config SCHED_CLASS_EXT
    bool "Extensible Scheduling Class"
    depends on BPF_SYSCALL && BPF_JIT && DEBUG_INFO_BTF
    /* 打开后才有 sched_ext 框架,允许 BPF 实现调度策略 */
  • • 打开 → build_policy.c 编入 ext.c → 链接段里有 ext_sched_class(夹在 fair 与 idle 之间)。
  • • 没开 → 没有 ext 对象;scx_enabled() / scx_switched_all() 在头文件里变成恒 falsesched.h:1851),第 3 步那两段额外 class++ 也不编译。
/* sched.h:1851–1853 */
#else /* !CONFIG_SCHED_CLASS_EXT */

#define scx_enabled()        false

#define scx_switched_all()    false

注意:编进内核 ≠ 正在用 BPF。只表示框架在;scx_enabled 要等加载 BPF 后才为真(第 7 步)。

开机时 sched_init 用 BUG_ON 验收第 4 步的链接顺序没排反(core.c:8581):

BUG_ON(!sched_class_above(&stop_sched_class, &dl_sched_class));
    BUG_ON(!sched_class_above(&dl_sched_class, &rt_sched_class));
    BUG_ON(!sched_class_above(&rt_sched_class, &fair_sched_class));
    BUG_ON(!sched_class_above(&fair_sched_class, &idle_sched_class));
#ifdef CONFIG_SCHED_CLASS_EXT

    BUG_ON(!sched_class_above(&fair_sched_class, &ext_sched_class));
    BUG_ON(!sched_class_above(&ext_sched_class, &idle_sched_class));
#endif

再次确认:fair < ext(地址)→ fair 先问。

到这里,「类排成一排、for 怎么走、ext 在不在镜像」都齐了。
第 3 步里两个运行时开关,还没说谁在何时打开——那是加载 BPF 的事。


第 7 步:第 3 步两个开关谁置位?(加载 BPF)

第 3 步已经说明:扫描时可能跳过 fair / ext。
这里补完整故事——很多读者没用过 sched_ext,先把「谁在用户态加载、用了哪些 API」说清楚,再落到内核里如何点亮那两个 static key。

7.1 背景:BPF 调度器是什么

内核开了 CONFIG_SCHED_CLASS_EXT 之后,多了一个 ext 调度类(空壳框架)。
真正的策略写在 BPF 程序里:实现一张回调表 struct sched_ext_ops(选 CPU、入队、dispatch……),加载进内核后,ext 类挑任务时就会调这些回调。

可以把它想成:

用户写一份「调度策略」.bpf.c
  → 用户态程序用 libbpf 加载并 attach
  → 内核启用 scx:打开 scx_enabled(以及可能的 switched_all)
  → 之后 schedule 按第 3 步的规则扫类;挂在 ext 上的任务由 BPF 策略安排

这和「写一个内核模块改 fair.c」不同:策略在 BPF 里,可热加载 / 卸载,不必重编内核。

7.2 用户态怎么加载:以内核自带 libbpf 示例为准

公开示例在内核树(不依赖任何内部仓库):

  • • 文档:https://docs.kernel.org/scheduler/sched-ext.html
  • • 代码:tools/sched_ext/scx_simple.c + scx_simple.bpf.c
  • • 社区镜像:https://github.com/sched-ext/scx-c-examples(C 示例)
  • • 构建:make -C tools/sched_ext,跑 tools/sched_ext/build/bin/scx_simple

用户态用 libbpf(加载 BPF、创建 map、attach)。sched_ext 示例再包一层宏,但底下就是 libbpf。

典型三步(摘自 tools/sched_ext/scx_simple.c):

/* 1) 打开 skeleton(bpftool 生成的 *.skel.h,少手写 fd) */
skel = SCX_OPS_OPEN(simple_ops, scx_simple);

/* 2) 加载进内核:验证器检查 BPF 字节码 */

SCX_OPS_LOAD(skel, simple_ops, scx_simple, uei);

/* 3) attach struct_ops → 内核走 scx enable,点亮开关 */

link = SCX_OPS_ATTACH(skel, simple_ops, scx_simple);

/* 进程在前台活着,调度器就保持启用;退出时: */

bpf_link__destroy(link);    /* libbpf:拆掉 link,卸载该 BPF 调度器 */
scx_simple__destroy(skel);

若不用 scx 宏,对应的 libbpf 概念是:

bpf_object__open* / skeleton open
  → bpf_object__load(或 skel load)
  → bpf_map__attach_struct_ops / bpf_link 挂上 sched_ext_ops
  → bpf_link__destroy 卸载
API
谁提供
干什么
libbpf
用户态库(libbpf-dev 等)
加载 .bpf.o、map、link
*.bpf.skel.hbpftool gen skeleton
把 BPF 对象变成 C 结构体
SCX_OPS_OPEN/LOAD/ATTACH
内核 tools/sched_ext 辅助宏
对上面三步的薄封装
bpf_link__destroy
libbpf
断开 attach;调度器退出

BPF 侧把回调填进 sched_ext_opstools/sched_ext/scx_simple.bpf.c):

SCX_OPS_DEFINE(simple_ops,
           .select_cpu    = (void *)simple_select_cpu,
           .enqueue        = (void *)simple_enqueue,
           .dispatch    = (void *)simple_dispatch,
           /* ... */

           .name        = "simple");

内核结构体:struct sched_ext_opsext_internal.h:272)——函数指针表 + flags + name

7.3 全开还是 PARTIAL:看 ops->flags

/* ext_internal.h:137–140 */
    /*
     * If set, only tasks with policy set to SCHED_EXT are attached.
     * If clear, SCHED_NORMAL tasks are also included.
     */

    SCX_OPS_SWITCH_PARTIAL        = 1LLU << 3,
不设 PARTIAL(默认全开)
设了 SCX_OPS_SWITCH_PARTIAL
谁进 ext
普通 SCHED_NORMAL 等也会被迁过去
只有policy == SCHED_EXT
 的任务
scx_switched_all
会打开 → 扫描跳过 fair
不打开 → fair 与 ext 都会被问
典型用途
整机换策略做实验(如默认的 scx_simple
只托管部分进程,其余仍走 fair

用户态在 ATTACH 之前改 flags 即可,例如(tools/sched_ext/scx_qmap.c 的 -p):

skel->struct_ops.qmap_ops->flags |= SCX_OPS_SWITCH_PARTIAL;

PARTIAL 下还要把进程改成 SCHED_EXT(标准 POSIX / Linux API):

struct sched_param param = { .sched_priority = 0 };
sched_setscheduler(pid, SCHED_EXT, &param);
/* 或 sched_setattr();也可用 syscall(__NR_sched_setscheduler, ...) */

含义:改 task_struct 的 policy → 第 8 步 __setscheduler_class / task_should_scx 才会把任务挂到 ext_sched_class

7.4 内核 enable:两个开关在这里点亮

SCX_OPS_ATTACH 成功后,内核进入 enable(ext.c:5257 一带)。与本文相关的几句:

/* 根据 BPF 是否带 PARTIAL,记下「要不要全开」 */
    WRITE_ONCE(scx_switching_all, !(ops->flags & SCX_OPS_SWITCH_PARTIAL));
    static_branch_enable(&__scx_enabled);
    /* → 此后 scx_enabled() == true:第 3 步不再跳过 ext */


    /* ... 遍历任务,把合格的迁到 ext 类(换 p->sched_class)... */


    if
 (!(ops->flags & SCX_OPS_SWITCH_PARTIAL))
        static_branch_enable(&__scx_switched_all);
    /* → 全开时 scx_switched_all() == true:第 3 步跳过 fair */

对应关系(和第 3 步对上):

BPF flags
效果
扫描
尚未 load
两开关都关
… → fair → 跳过 ext → idle
全开(无 PARTIAL)
enabled + switched_all
… → rt → 跳过 fair → ext → idle
PARTIAL
仅 enabled
… → fair → ext → idle(两格都问)

「某个 policy 该不该进 scx」:

/* ext.c:3852 */
bool
 task_should_scx(int policy)
{
    if
 (!scx_enabled() || /* 正在关闭 */ ...)
        return
 false;
    if
 (READ_ONCE(scx_switching_all))
        return
 true;        /* 全开:普通策略也进 */
    return
 policy == SCHED_EXT;    /* PARTIAL:仅 SCHED_EXT */
}

7.5 本机怎么确认「加载成功了没有」

不必读内核变量,看 sysfs(实现见 ext.c:3700 一带):

# 是否启用 / 状态字符串
cat
 /sys/kernel/sched_ext/state

# 1 ≈ 全开(switching_all);0 ≈ PARTIAL 或未全开

cat
 /sys/kernel/sched_ext/switch_all

# 当前 BPF 调度器名字(ops.name,如 "simple")

cat
 /sys/kernel/sched_ext/root/ops

# 某进程是否在 ext 上

grep ext.enabled /proc/<pid>/sched

7.6 这一步带走什么

  1. 1. 加载靠用户态 libbpf(内核示例:tools/sched_ext/scx_simple):OPEN → LOAD → ATTACH;ATTACH 触发内核 enable。
  2. 2. 两个开关在 enable 里置位:scx_enabled 几乎总开;scx_switched_all 仅全开时开。
  3. 3. PARTIAL 用 SCX_OPS_SWITCH_PARTIAL;进程侧再 sched_setscheduler(..., SCHED_EXT, ...)
  4. 4. 跳过 fair/ext 的扫描语义回第 3 步;这里只回答「开关谁打的」。

第 8 步:任务怎么挂到某一类上?

到此为止讲的都是:问类的顺序(以及何时跳过空类)。
另一半:某个进程的 p->sched_class 指向哪一类,由 policy 等决定。
只有任务挂在某类的队列上,该类被问到时才可能交出 next

用户可见 policy:

/* include/uapi/linux/sched.h:114–121 */
#define SCHED_NORMAL        0

#define SCHED_FIFO        1

#define SCHED_RR        2

#define SCHED_BATCH        3

#define SCHED_IDLE        5

#define SCHED_DEADLINE        6

#define SCHED_EXT        7

policy → 调度类(接上第 7 步的 task_should_scx):

/* core.c:7231–7244 */
const
struct sched_class *__setscheduler_class(int policy, int prio)
{

    if
 (dl_prio(prio))
        return
 &dl_sched_class;

    if
 (rt_prio(prio))
        return
 &rt_sched_class;

#ifdef CONFIG_SCHED_CLASS_EXT

    if
 (task_should_scx(policy))
        return
 &ext_sched_class;
#endif


    return
 &fair_sched_class;
}

读法:

  • • FIFO/RR → rt;DEADLINE → dl。
  • • 否则若 task_should_scx(policy) → ext(全开时 NORMAL 也会;PARTIAL 时需 SCHED_EXT)。
  • • 否则 → fair。

PARTIAL 下想让某进程走 BPF:先加载带 SCX_OPS_SWITCH_PARTIAL 的调度器,再用 sched_setscheduler(pid, SCHED_EXT, …) / sched_setattr() 改 policy(见第 7.3 节)。

cat /sys/kernel/sched_ext/state
cat
 /sys/kernel/sched_ext/switch_all
cat
 /sys/kernel/sched_ext/root/ops
grep ext.enabled /proc/<pid>/sched

两条线合在一起:

链接顺序决定「先问谁」;
policy(+ scx 是否全开)决定「任务站在哪一类的队列里」。
BPF 接管 = 换任务所属 class + 扫描时跳过空类,不是改链接顺序。

本篇小结

整条逻辑链:

schedule → __pick_next_task
  → for_each_active_class(可跳过空 fair / 未启用 ext)
  → 顺序来自 SCHED_DATA(fair 在 ext 前)
  → 对象来自 DEFINE_SCHED_CLASS(ext 受 Kconfig 约束)
  → 跳过开关在加载 BPF 时置位(PARTIAL 与否)
  → 任务进哪一类看 policy(+ task_should_scx)
  1. 1. __pick_next_taskcore.c:5910)按类询问 next。
  2. 2. 顺序来自 SCHED_DATAvmlinux.lds.h:153):stop → dl → rt → fair → ext → idle
  3. 3. 各 class 由 DEFINE_SCHED_CLASSsched.h:2709)进 section,无运行时注册表。
  4. 4. scx 靠 换任务所属 class + 扫描跳过空类sched.h:2728),不是把 ext 排到 fair 前。
  5. 5. 全开 / PARTIAL 由 BPF ops->flagsext_internal.h:140,启用时 ext.c:5261 / 5298)决定。

类间谁先被问到,看链接顺序;任务进哪一类,看 policy(以及 scx 是否全开)。

最新文章

随机文章

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