当前位置:首页>Linux>[LNX016] 深入Linux内核:ARM64 架构下进程切换全过程深度解析

[LNX016] 深入Linux内核:ARM64 架构下进程切换全过程深度解析

  • 2026-08-18 23:11:36
[LNX016] 深入Linux内核:ARM64 架构下进程切换全过程深度解析

文章内容

1. 如何管理进程

    1.1 TIF_* 标志

    1.2 内核栈与用户栈

    1.3 进程优先级

2. 进程切换的过程

    2.1 context_switch

    2.2 switch_mm 切换页表

    2.3 switch_to 切换寄存器

    2.4 ret_from_fork

3. 进程切换的时机

    3.1 时间片耗尽

    3.2 进程主动放弃 CPU

    3.3 更高优先级进程可运行

1. 如何管理进程

Linux 并没有区分进程和线程,两者没有差别,统一是 task,使用 task_struct 描述

  • • thread_info 和 thread 记录和架构相关的 task 信息
  • • stack 指向内核栈,默认大小 16KB,stack_vm_area 表示使用 vmalloc 分配的内核栈内存区域
  • • mm->start_stack 指向户栈空间
  • • fs 和 files 分别表示文件系统和打开的文件
  • • signal 表示信号

1.1 TIF_* 标志

thread_info.flags 保存了线程信息标志,主要用于描述线程在运行时的各种状态或需求,直接影响当前线程的执行路径(如调度、异常处理、系统调用返回等)

标志 (Flag)
值 (Value)
描述 (Description)
TIF_SIGPENDING
0
有信号等待处理 (signal pending)
TIF_NEED_RESCHED
1
需要重新调度 (rescheduling necessary)
TIF_NEED_RESCHED_LAZY
2
需要延迟重新调度 (Lazy rescheduling needed)
TIF_NOTIFY_RESUME
3
返回用户态前需要回调 (callback before returning to user)
TIF_FOREIGN_FPSTATE
4
CPU 的浮点状态不属于当前任务 (CPU's FP state is not current's)
TIF_UPROBE
5
uprobe 断点或单步执行 (uprobe breakpoint or singlestep)
TIF_MTE_ASYNC_FAULT
6
MTE 异步标签检查错误 (MTE Asynchronous Tag Check Fault)
TIF_NOTIFY_SIGNAL
7
存在信号通知 (signal notifications exist)
TIF_SYSCALL_TRACE
8
系统调用跟踪激活 (syscall trace active)
TIF_SYSCALL_AUDIT
9
系统调用审计 (syscall auditing)
TIF_SYSCALL_TRACEPOINT
10
ftrace 的系统调用跟踪点 (syscall tracepoint for ftrace)
TIF_SECCOMP
11
系统调用安全计算 (syscall secure computing)
TIF_SYSCALL_EMU
12
系统调用模拟激活 (syscall emulation active)
TIF_PATCH_PENDING
13
有待处理的实时补丁更新 (pending live patching update)
TIF_MEMDIE
18
因 OOM killer 而终止 (is terminating due to OOM killer)
TIF_FREEZE
19
(无描述)
TIF_RESTORE_SIGMASK
20
(无描述)
TIF_SINGLESTEP
21
(无描述)
TIF_32BIT
22
32位进程 (32bit process)
TIF_SVE
23
正在使用可伸缩向量扩展 (Scalable Vector Extension in use)
TIF_SVE_VL_INHERIT
24
跨 exec 继承 SVE vl_onexec
TIF_SSBD
25
需要 SSB 缓解措施 (Wants SSB mitigation)
TIF_TAGGED_ADDR
26
允许带标签的用户地址 (Allow tagged user addresses)
TIF_SME
27
正在使用 SME (SME in use)
TIF_SME_VL_INHERIT
28
跨 exec 继承 SME vl_onexec
TIF_KERNEL_FPSTATE
29
任务处于内核模式 FPSIMD 部分
TIF_TSC_SIGSEGV
30
访问计数器-定时器时触发 SIGSEGV
TIF_LAZY_MMU
31
任务处于延迟 MMU 模式 (Task in lazy mmu mode)
TIF_LAZY_MMU_PENDING
32
延迟 MMU 模式退出的待处理操作

need_resched() 函数返回当前进程是否需要切换

static __always_inline bool need_resched(void){    return unlikely(tif_need_resched());}static __always_inline bool tif_need_resched(void){    return tif_test_bit(TIF_NEED_RESCHED);}

在 flags 中写入 TIF_NEED_RESCHED 来标记需要切换某个进程

static inline void set_tsk_need_resched(struct task_struct *tsk){    if (tracepoint_enabled(sched_set_need_resched_tp) &&        !test_tsk_thread_flag(tsk, TIF_NEED_RESCHED))        __trace_set_need_resched(tsk, TIF_NEED_RESCHED);    set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);}static inline void clear_tsk_need_resched(struct task_struct *tsk){    atomic_long_andnot(_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY,               (atomic_long_t *)&task_thread_info(tsk)->flags);}

1.2 内核栈与用户栈

task_stack_page() 函数返回内核栈内存,加上 THREAD_SIZE 就是内核栈栈顶

/* * When accessing the stack of a non-current task that might exit, use * try_get_task_stack() instead.  task_stack_page will return a pointer * that could get freed out from under you. */static __always_inline void *task_stack_page(const struct task_struct *task){    return task->stack;}

创建新的进程(包括 pthread_create() 创建线程)时,都会创建内核栈

static int alloc_thread_stack_node(struct task_struct *tsk, int node){struct vm_struct *vm_area;    void *stack;    int i;    // 遍历当前 CPU 的缓存池(cached_stacks),尝试获取之前被释放但保留下来的栈内存    for (i = 0; i < NR_CACHED_STACKS; i++) {        vm_area = this_cpu_xchg(cached_stacks[i], NULL);        if (!vm_area)            continue;        if (memcg_charge_kernel_stack(vm_area)) {            vfree(vm_area->addr);            return -ENOMEM;        }        /* Reset stack metadata. */        kasan_unpoison_range(vm_area->addr, THREAD_SIZE);        stack = kasan_reset_tag(vm_area->addr);        /* Clear stale pointers from reused stack. */        memset(stack, 0, THREAD_SIZE);        tsk->stack_vm_area = vm_area;        tsk->stack = stack; // 设置内核栈内存        return 0;    }    // 调用 vmalloc 分配一块新的连续虚拟内存    stack = __vmalloc_node(THREAD_SIZE, THREAD_ALIGN,                     GFP_VMAP_STACK,                     node, __builtin_return_address(0));    if (!stack)        return -ENOMEM;    vm_area = find_vm_area(stack);    if (memcg_charge_kernel_stack(vm_area)) {        vfree(stack);        return -ENOMEM;    }    /*     * We can't call find_vm_area() in interrupt context, and     * free_thread_stack() can be called in interrupt context,     * so cache the vm_struct.     */    tsk->stack_vm_area = vm_area;    stack = kasan_reset_tag(stack);    tsk->stack = stack; // 设置内核栈内存    return 0;}

task_pt_regs 宏返回内核栈栈顶保存寄存器的内存地址

#define task_pt_regs(p) \    ((struct pt_regs *)(THREAD_SIZE + task_stack_page(p)) - 1)#define KSTK_EIP(tsk)    ((unsigned long)task_pt_regs(tsk)->pc)#define KSTK_ESP(tsk)    user_stack_pointer(task_pt_regs(tsk))

在 copy_thread() 拷贝父进程资源时,在新进程的内核栈地址上,构建返回用户态时寄存器值

int copy_thread(struct task_struct *p, const struct kernel_clone_args *args){    u64 clone_flags = args->flags;    unsigned long stack_start = args->stack;  // user 栈空间    unsigned long tls = args->tls;              // user TLS 内存空间空间struct pt_regs *childregs = task_pt_regs(p); // 子进程内核栈构建返回用户态寄存器值    int ret;    memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));    // ...    if (likely(!args->fn)) {        *childregs = *current_pt_regs();     // 继承父进程所有寄存器        childregs->regs[0] = 0;              // 子进程返回 0        /*         * Read the current TLS pointer from tpidr_el0 as it may be         * out-of-sync with the saved value.         */        *task_user_tls(p) = read_sysreg(tpidr_el0); // 继承父进程 TLS 地址空间        if (system_supports_poe())            p->thread.por_el0 = read_sysreg_s(SYS_POR_EL0);        if (stack_start) {                     // 使用 user 传入的用户栈空间            if (is_compat_thread(task_thread_info(p)))                childregs->compat_sp = stack_start;            else                childregs->sp = stack_start;        }        // ...        if (clone_flags & CLONE_SETTLS)            p->thread.uw.tp_value = tls;      // 使用 user TLS 地址空间        ret = copy_thread_gcs(p, args);        if (ret != 0)            return ret;    } else {        // ...    }    p->thread.cpu_context.pc = (unsigned long)ret_from_fork;    p->thread.cpu_context.sp = (unsigned long)childregs;    /*     * For the benefit of the unwinder, set up childregs->stackframe     * as the final frame for the new task.     */    p->thread.cpu_context.fp = (unsigned long)&childregs->stackframe;    ptrace_hw_copy_thread(p);    return 0;}

当子进程被调度,进程切换时,将新任务的内核栈地址 tsk->stack 写入 SP_EL1 寄存器

1.3 进程优先级

Linux 进程优先级范围 [-1, 140),其中 [-1, 100) 表示实时进程优先级,[100, 140) 是普通进程优先级,-1 表示 deadline 进程(也是实时进程)

普通进程优先级通过 nice 值调整

task_struct 中有四个成员表示优先级

  • • prio:有效优先级,也是调度器使用的优先级,数值越小,优先级越高
  • • static_prio:普通进程 nice 值对应的优先级,数值越小,优先级越高
  • • normal_prio:统一普通进程和实时进程的优先级
  • • rt_priority:实时进程设置的优先级,数值越大,优先级越高

区分 prio 和 normal_prio,是因为实时进程优先级调整并不一定立即生效,和 rtmutex 实现有关系

sched_setscheduler() 函数根据优先级 prio 设置进程调度策略,把进程添加到对应的运行队列中

static void __setscheduler_prio(struct task_struct *p, int prio){    if (dl_prio(prio))        p->sched_class = &dl_sched_class;    else if (rt_prio(prio))        p->sched_class = &rt_sched_class;    else        p->sched_class = &fair_sched_class;    p->prio = prio;}

用户空间可用操作的是 dl/rt/fair 三种调度器,支持设置不同的调度策略

每个 CPU 都有一个运行队列 runqueues,管理所有等待执行的 task

task 根据优先级添加相应的队列中,等待调度器调度,获取 CPU 后开始执行

dl_sched_class 就绪队列使用红黑树管理所有的 task,红黑树使用 deadline 当作 key,每次选择红黑树最左节点

static struct sched_dl_entity *pick_next_dl_entity(struct dl_rq *dl_rq){struct rb_node *left = rb_first_cached(&dl_rq->root);    if (!left)        return NULL;    return __node_2_dle(left);}

rt_sched_class 就绪队列按照优先级 prio 添加到 active.queue[prio] 队列中,同一优先级队列中并没有区分 SCHED_RR 和 SCHED_FIFO 调度策略,pick_next_task_rt() 总是选择队首元素

  • • 如果进程是 SCHED_RR 调度策略,递减 time_slice,time_slice 递减为 0 时将自己移动到队尾,重新调度
  • • 如果进程是 SCHED_FIFO 调度策略,直到运行结束就返回

fair_sched_class 调度器 CFS 算法已经被 EEVDF 取代,EEVDF 算法的核心思想是:在所有 e 时刻到来的进程中,选择具有最小虚拟截止时间的进程,分配其 CPU 运行 q 个时间单位

在运行队列 runqueues 的进程属于 TASK_RUNNING 状态,但是不一定正在运行,只有被调度才正在运行

2. 进程切换的过程

ARM64 架构下 Linux 内核的进程切换(上下文切换)是一个极其精密、软硬件高度协同的过程

整个过程可以清晰地划分四个核心阶段

  • • 软件状态准备
  • • 内存空间切换
  • • 硬件寄存器交接
  • • 新进程启动

2.1 context_switch

context_switch() 进程上下文切换重要的工作

  • • switch_mm_irqs_off() 切换进程地址空间,加载新的页表,刷新 TLB 缓存
  • • switch_to() 保存和恢复寄存器
context_switch(rq, pref, next, rf)    prepare_task_switch(rq, prev, next)        prepare_task(next)            WRITE_ONCE(next->on_cpu, 1)         // 标记 next 占用 CPU    if (!next->mm)        next->active_mm = prev->active_mm        // next 是 kernel 进程,复用 prev 进程空间    else        switch_mm_irqs_off(prev->active_mm, next->mm, next) // switch_mm    switch_to(prev, next, prev)    finish_task_switch(prev)        finish_task(prev)            smp_store_release(&prev->on_cpu, 0) // 标记 prev 不占用 CPU        fire_sched_in_preempt_notifiers(current)

2.2 switch_mm 切换页表

ARM64 架构拥有两个页表基址寄存器

  • • TTBR0_EL1:负责用户态地址空间
  • • TTBR1_EL1:负责内核态地址空间

ARM64 中 switch_mm_irqs_off 其实就是 switch_mm() 函数

#ifndef switch_mm_irqs_off# define switch_mm_irqs_off switch_mm#endif

如果 next 是内核线程,不用切换地址空间,只是将用户态地址空间设置为 reserved_pg_dir:一个内容全是零的只读页表

否则使用 check_and_switch_context() 切换用户态地址空间

switch_mm(prev, next, tsk)    if (prev != next)              // 不同的地址空间才需要切换        __switch_mm(next)            // next 是内核进程            if (next == &init_mm)                 cpu_set_reserved_ttbr0()                    cpu_set_reserved_ttbr0_nosync()                        ttbr = phys_to_ttbr(__pa_symbol(reserved_pg_dir))                        write_sysreg(ttbr, ttbr0_el1) // 更新用户态地址空间                    isb()    // 刷新当前 CPU 的指令流水线                return            // next 是用户态进程            check_and_switch_context(next)

check_and_switch_context() 并不一定刷新 TLB:只有在分配 ASID 导致 Generation 递增的情况下,才会刷新 TLB

void check_and_switch_context(struct mm_struct *mm){    // ...    asid = atomic64_read(&mm->context.id);    /*     * The memory ordering here is subtle.     * If our active_asids is non-zero and the ASID matches the current     * generation, then we update the active_asids entry with a relaxed     * cmpxchg. Racing with a concurrent rollover means that either:     *     * - We get a zero back from the cmpxchg and end up waiting on the     *   lock. Taking the lock synchronises with the rollover and so     *   we are forced to see the updated generation.     *     * - We get a valid ASID back from the cmpxchg, which means the     *   relaxed xchg in flush_context will treat us as reserved     *   because atomic RmWs are totally ordered for a given location.     */    old_active_asid = atomic64_read(this_cpu_ptr(&active_asids));    // 新进程 ASID 还是有效的,直接切换页表,不执行刷新 TLB    if (old_active_asid && asid_gen_match(asid) &&        atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_asids),                     old_active_asid, asid))        goto switch_mm_fastpath;    raw_spin_lock_irqsave(&cpu_asid_lock, flags);    /* Check that our ASID belongs to the current generation. */    asid = atomic64_read(&mm->context.id);    if (!asid_gen_match(asid)) {        asid = new_context(mm); // 尝试分配 ASID,可能导致 Rollover        atomic64_set(&mm->context.id, asid);    }    cpu = smp_processor_id();    // 分配 ASID,导致递增 ASID generation,需要刷新 TLB    if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))        local_flush_tlb_all();    atomic64_set(this_cpu_ptr(&active_asids), asid);    raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);switch_mm_fastpath:    arm64_apply_bp_hardening();    /*     * Defer TTBR0_EL1 setting for user threads to uaccess_enable() when     * emulating PAN.     */    if (!system_uses_ttbr0_pan())        cpu_switch_mm(mm->pgd, mm); // 更新页表}

cpu_switch_mm() 封装 cpu_do_switch_mm

static inline void cpu_switch_mm(pgd_t *pgd, struct mm_struct *mm){    BUG_ON(pgd == swapper_pg_dir);    cpu_do_switch_mm(virt_to_phys(pgd),mm);}

cpu_do_switch_mm() 函数更新内核态和用户态页表寄存器

void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm){    unsigned long ttbr1 = read_sysreg(ttbr1_el1);  // 内核态页表    unsigned long asid = ASID(mm);    unsigned long ttbr0 = phys_to_ttbr(pgd_phys);  // 用户态页表    /* Skip CNP for the reserved ASID */    if (system_supports_cnp() && asid)        ttbr0 |= TTBR_CNP_BIT;    /* SW PAN needs a copy of the ASID in TTBR0 for entry */    if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))        ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);    /* Set ASID in TTBR1 since TCR.A1 is set */    ttbr1 &= ~TTBR_ASID_MASK;    ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);    cpu_set_reserved_ttbr0_nosync();    write_sysreg(ttbr1, ttbr1_el1);  // 更新内核态页表寄存器    write_sysreg(ttbr0, ttbr0_el1);  // 更新用户态页表寄存器    isb();                           // 刷新处理器的指令流水线    post_ttbr_update_workaround();}

local_flush_tlb_all() 刷新整个 TLB 缓存

static inline void local_flush_tlb_all(void){    dsb(nshst);    __tlbi(vmalle1);    dsb(nsh);    isb();}

2.3 switch_to 切换寄存器

switch_to 宏封装 __switch_to() 函数

#define switch_to(prev, next, last)                \    do {                                        \        ((last) = __switch_to((prev), (next)));    \    } while (0)

switch_to 主要职责是在真正切换硬件寄存器之前,处理所有与体系结构相关的软件状态、特殊寄存器和硬件特性的切换

struct task_struct *__switch_to(struct task_struct *prev,                struct task_struct *next){struct task_struct *last;    fpsimd_thread_switch(next);       // 切换浮点寄存器(FPSIMD)状态    tls_thread_switch(next);          // 切换 TLS    hw_breakpoint_thread_switch(next);    contextidr_thread_switch(next);    entry_task_switch(next);    ssbs_thread_switch(next);    cntkctl_thread_switch(prev, next);    ptrauth_thread_switch_user(next);    permission_overlay_switch(next);    gcs_thread_switch(next);    /*     *  vendor hook is needed before the dsb(),     *  because MPAM is related to cache maintenance.     */    trace_android_vh_mpam_set(prev, next);    /*     * Complete any pending TLB or cache maintenance on this CPU in case the     * thread migrates to a different CPU. This full barrier is also     * required by the membarrier system call. Additionally it makes any     * in-progress pgtable writes visible to the table walker; See     * emit_pte_barriers().     */    dsb(ish); // 确保在当前 CPU 上所有未完成的 TLB 或缓存维护操作(如页表写入)全部完成    /*     * MTE thread switching must happen after the DSB above to ensure that     * any asynchronous tag check faults have been logged in the TFSR*_EL1     * registers.     */    mte_thread_switch(next);    /* avoid expensive SCTLR_EL1 accesses if no change */    if (prev->thread.sctlr_user != next->thread.sctlr_user)        update_sctlr_el1(next->thread.sctlr_user);    trace_android_vh_is_fpsimd_save(prev, next);    /* the actual thread switch */    last = cpu_switch_to(prev, next);    return last;}

cpu_switch_to 负责保存旧进程(prev)的执行状态,并恢复新进程(next)的状态,最终通过 ret 指令完成程序计数器(PC)的跳转

执行到 cpu_switch_to 时,x0 保存的是 prev, x1 保存是 next

THREAD_CPU_CONTEXT 宏表示成员 thread.cpu_context 在 task_struct 中的偏移

DEFINE(THREAD_CPU_CONTEXT,    offsetof(struct task_struct, thread.cpu_context))

所以 x0 + THREAD_CPU_CONTEXT 就是 prev->thread.cpu_context

同理 x1 + THREAD_CPU_CONTEXT 就是 next->thread.cpu_context

SYM_FUNC_START(cpu_switch_to)    save_and_disable_daif x11   //  中断屏蔽    mov    x10, #THREAD_CPU_CONTEXT    add    x8, x0, x10             // x8 = prev->thread.cpu_context    mov    x9, sp    stp    x19, x20, [x8], #16        // store callee-saved registers    stp    x21, x22, [x8], #16    stp    x23, x24, [x8], #16    stp    x25, x26, [x8], #16    stp    x27, x28, [x8], #16    stp    x29, x9, [x8], #16      // 保存帧指针(x29)和栈指针(sp)    str    lr, [x8]                // 保存链接寄存器(lr),即 prev 的返回地址(PC)    add    x8, x1, x10             // x8 = next->thread.cpu_context    ldp    x19, x20, [x8], #16        // restore callee-saved registers    ldp    x21, x22, [x8], #16    ldp    x23, x24, [x8], #16    ldp    x25, x26, [x8], #16    ldp    x27, x28, [x8], #16    ldp    x29, x9, [x8], #16      // 恢复 next 的 fp 和 sp 到 x9    ldr    lr, [x8]                // 恢复 next 的返回地址(PC)到 lr    mov    sp, x9                  // 将内核栈指针切换为 next 的栈    msr    sp_el0, x1              // 将 next 的 task_struct 指针存入用户态栈指针寄存器    ptrauth_keys_install_kernel x1, x8, x9, x10    scs_save x0    scs_load_current    restore_irq x11             // 恢复之前保存的 DAIF 状态,重新允许中断    retSYM_FUNC_END(cpu_switch_to)

2.4 ret_from_fork

当进程创建出来后第一次被调度执行,执行的是 ret_from_fork() 函数

回顾 copy_thread() 函数构建的 cpu_context 内容

  • • pc 指向 ret_from_fork 函数
  • • sp 指向构造的栈,保存了 pt_regs
int copy_thread(struct task_struct *p, const struct kernel_clone_args *args){    u64 clone_flags = args->flags;    unsigned long stack_start = args->stack;  // user 栈空间    unsigned long tls = args->tls;              // user TLS 内存空间空间struct pt_regs *childregs = task_pt_regs(p); // 子进程内核栈构建返回用户态寄存器值    int ret;    memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));    // ...    p->thread.cpu_context.pc = (unsigned long)ret_from_fork;    p->thread.cpu_context.sp = (unsigned long)childregs;    /*     * For the benefit of the unwinder, set up childregs->stackframe     * as the final frame for the new task.     */    p->thread.cpu_context.fp = (unsigned long)&childregs->stackframe;    ptrace_hw_copy_thread(p);    return 0;}

完成调度收尾工作,并根据进程类型的不同,将执行流精准地分流到“内核线程执行路径”或“返回用户态路径”

SYM_CODE_START(ret_from_fork)    bl    schedule_tail       // 解锁、重新允许抢占、向用户态回写线程 ID    cbz    x19, 1f                // user 进程 x19 是 0,跳转到 1    mov    x0, x20    blr    x191:    get_current_task tsk    mov    x0, sp             // 将当前内核栈指针 (sp) 移动到 x0    bl    asm_exit_to_user_mode // CPU 正式从内核态 (EL1) 切换回用户态 (EL0)    b    ret_to_user           // 执行 ret_to_userSYM_CODE_END(ret_from_fork)

ret_to_user 调用 kernel_exit 使用在内核栈上 pt_regs 保存的寄存器值恢复 user 线程上下文

S_SP 宏记录的是 sp 在 pt_regs {} 数据结构的偏移

DEFINE(S_SP,            offsetof(struct pt_regs, sp));

kernel_exit() 是返回用户态最后的处理函数

  • • 设置用户态栈空间寄存器 SP_EL0
    .macro    kernel_exit, el    .if    \el != 0    disable_daif    .endif#ifdef CONFIG_ARM64_PSEUDO_NMI// ...#endif    ldp    x21, x22, [sp, #S_PC]        // load ELR, SPSR#ifdef CONFIG_ARM64_SW_TTBR0_PAN// ...#endif    .if    \el == 0    ldr    x23, [sp, #S_SP]           // 加载保存的用户态栈指针    msr    sp_el0, x23                // 将用户态栈指针写入 sp_el0    tst    x22, #PSR_MODE32_BIT        // native task?    b.eq    3f#ifdef CONFIG_ARM64_ERRATUM_845719alternative_if ARM64_WORKAROUND_845719#ifdef CONFIG_PID_IN_CONTEXTIDR    mrs    x29, contextidr_el1    msr    contextidr_el1, x29#else    msr contextidr_el1, xzr#endifalternative_else_nop_endif#endif3:    scs_save tsk    /* Ignore asynchronous tag check faults in the uaccess routines */    ldr    x0, [tsk, THREAD_SCTLR_USER]    clear_mte_async_tcf x0#ifdef CONFIG_ARM64_PTR_AUTHalternative_if ARM64_HAS_ADDRESS_AUTH    /*     * IA was enabled for in-kernel PAC. Disable it now if needed, or     * alternatively install the user's IA. All other per-task keys and     * SCTLR bits were updated on task switch.     *     * No kernel C function calls after this.     */    tbz    x0, SCTLR_ELx_ENIA_SHIFT, 1f    __ptrauth_keys_install_user tsk, x0, x1, x2    b    2f1:    mrs    x0, sctlr_el1    bic    x0, x0, SCTLR_ELx_ENIA    msr    sctlr_el1, x02:alternative_else_nop_endif#endif    mte_set_user_gcr tsk, x0, x1    apply_ssbd 0, x0, x1    .endif    msr    elr_el1, x21            // set up the return data    msr    spsr_el1, x22    ldp    x0, x1, [sp, #16 * 0]    ldp    x2, x3, [sp, #16 * 1]    ldp    x4, x5, [sp, #16 * 2]    ldp    x6, x7, [sp, #16 * 3]    ldp    x8, x9, [sp, #16 * 4]    ldp    x10, x11, [sp, #16 * 5]    ldp    x12, x13, [sp, #16 * 6]    ldp    x14, x15, [sp, #16 * 7]    ldp    x16, x17, [sp, #16 * 8]    ldp    x18, x19, [sp, #16 * 9]    ldp    x20, x21, [sp, #16 * 10]    ldp    x22, x23, [sp, #16 * 11]    ldp    x24, x25, [sp, #16 * 12]    ldp    x26, x27, [sp, #16 * 13]    ldp    x28, x29, [sp, #16 * 14]    .if    \el == 0#ifdef CONFIG_UNMAP_KERNEL_AT_EL0    alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0    msr    far_el1, x29    ldr_this_cpu    x30, this_cpu_vector, x29    tramp_alias    x29, tramp_exit    msr        vbar_el1, x30        // install vector table    ldr        lr, [sp, #S_LR]            // 恢复 x30 (LR)    add        sp, sp, #PT_REGS_SIZE    // 恢复内核栈指针,释放 pt_regs 内存    br        x29.L_skip_tramp_exit_\@:#endif    .endif    ldr    lr, [sp, #S_LR]    add    sp, sp, #PT_REGS_SIZE        // restore sp    .if \el == 0    /* This must be after the last explicit memory access */alternative_if ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD    tlbi    vale1, xzr    dsb    nshalternative_else_nop_endif    .else    /* Ensure any device/NC reads complete */    alternative_insn nop, "dmb sy", ARM64_WORKAROUND_1508412    .endif    eret    sb    .endm

3. 进程切换的时机

进程切换可以在多个时刻发生,三类典型触发点及其实现路径:时间片耗尽、进程主动放弃 CPU、以及更高优先级进程可运行(抢占)

3.1 时间片耗尽

平台 clockevent 硬件中断触发后,clockevent 框架会调用设备的 event_handler

根据运行模式的不同,处理路径分为两种

  • • 周期模式调用 tick_handle_periodic()
  • • 单触发/高精度模式调用 hrtimer_interrupt()

ARM64 默认的编译配置,内核会优先把可用的 clockevent 设备切到 oneshot/high‑res 模式,并尽早(启动期间)调用 hrtimer_switch_to_hres() 函数,把 clockevent 设备的 IRQ 中断处理函数设置为 hrtimer_interrupt() 函数,随后内核用 per‑CPU hrtimer(tick_sched)来模拟周期 jiffy

## Timers subsystem#CONFIG_TICK_ONESHOT=yCONFIG_NO_HZ_COMMON=y# CONFIG_HZ_PERIODIC is not setCONFIG_NO_HZ_IDLE=y# CONFIG_NO_HZ_FULL is not set# CONFIG_NO_HZ is not setCONFIG_HIGH_RES_TIMERS=y# CONFIG_POSIX_AUX_CLOCKS is not set# end of Timers subsystem

两个处理函数都调用 update_process_times() 函数,从而触发 sched_tick() 做时间片更新

hrtimer_interrupt    __hrtimer_run_queues        __run_hrtimer            tick_nohz_handler                tick_sched_do_timer                    tick_do_update_jiffies64                tick_sched_handle                    update_process_times                        account_process_tick    // CPU 时间记账                        run_local_timers        // 驱动定时器子系统                        rcu_sched_clock_irq                        sched_tick              // 调度器心跳                            sched_clock_tick    // 更新 sched_clock 的 tick                            update_rq_clock        // 更新 runqueue 的时间                            update_hw_load_avg    // 更新 runqueue 的硬件负载                            // 把 tick 事件交给调度类的 task_tick                            donor->sched_class->task_tick(rq, donor, 0)                            sched_core_tick        // 调度域/核心层级的周期性工作

DEADLINE 调度器 task_tick_dl() 函数更新当前进程剩余运行时间:时间消耗完就标记需要进程切换

task_tick_dl    update_curr_dl        delta_exec = update_curr_common(rq)     // 计算进程运行执行时间        update_curr_dl_se(rq, dl_se, delta_exec)            dl_se->runtime -= scaled_delta_exec // 更新剩余时间            if (dl_runtime_exceeded(dl_se) || dl_se->dl_yielded) {                // 可能重新加入红黑树队列,所以需要判断                if (!is_leftmost(dl_se, &rq->dl))                    resched_curr(rq);             // 标记需要进程切换            }    update_dl_rq_load_avg

REALTIME 调度器 task_tick_rt() 函数只会更新 SCHED_RR 类型的进程:时间消耗完就标记需要进程切换

SCHED_RR 进程默认占用 CPU 100ms 时间

task_tick_rt    update_curr_rt(rq)    update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1)    if (p->policy != SCHED_RR) return;     // SCHED_FIFO 一直运行    if (--p->rt.time_slice) return;        // SCHED_RR 时间片没有耗尽,继续运行    p->rt.time_slice = sched_rr_timeslice;    // 重新更新时间片    requeue_task_rt(rq, p, 0);            // 添加到队列末尾    resched_curr(rq);                    // 标记需要进程切换

FAIR 调度器 task_tick_fair() 函数判定当前进程需要切换的完整条件是

  • • 运行队列中存在其他就绪进程(nr_queued > 1)
  • • 当前当前进程达到截止时间 deadline
task_tick_fair    entity_tick(cfs_rq, se, queued)            // 在对应的 cfs_rq 上做实体级别的 tick        update_curr(cfs_rq, se)            delta_exec = update_se(rq, curr)            curr->vruntime += calc_delta_fair(delta_exec, curr)    // 更新虚拟运行时间            resched = update_deadline(cfs_rq, curr)     // EEVDF 算法判断是否需要切换进程                if (vruntime >= deadline) return true;    // 到达 deadline,需要切换            if (resched || !protect_slice(curr))                resched_curr_lazy(rq);                    // 需要进程切换        update_load_avg(cfs_rq, curr, UPDATE_TG)        update_cfs_group(curr)

这里仅仅时标记需要进程切换,需要等待"安全点"到来才真正切换进程

3.2 进程主动放弃 CPU

应用进程调用 sched_yield() 函数主动放弃 CPU,或者陷入内核态进入 sleep 某些事件未就绪时调用 schedule() 放弃主动放弃 CPU

schedule    __schedule(SM_NONE)        cpu = smp_processor_id();        rq = cpu_rq(cpu);        prev = rq->curr;        update_rq_clock(rq);        next = pick_next_task(rq, rq->donor, &rf)        rq_set_donor(rq, next)        if (prev != next)            context_switch(rq, prev, next, &rf)

立即进行进程切换

3.3 更高优先级进程可运行

当更高优先级进程被唤醒,尝试抢占当前进程,标记需要进程切换,需要等待"安全点"到来才真正切换进程

void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags){struct task_struct *donor = rq->donor;    // 被唤醒进程 p 和正在运行进程 donor 属于同一个调度类    if (p->sched_class == donor->sched_class)        donor->sched_class->wakeup_preempt(rq, p, flags);    // 不同调度类,被唤醒进程优先级更高,标记需要进程切换    else if (sched_class_above(p->sched_class, donor->sched_class))        resched_curr(rq);    /*     * A queue event has occurred, and we're going to schedule.  In     * this case, we can save a useless back to back clock update.     */    if (task_on_rq_queued(donor) && test_tsk_need_resched(rq->curr))        rq_clock_skip_update(rq);}

DEADLINE 调度器比较截止时间 deadline:deadline 更小的优先级更高

static void wakeup_preempt_dl(struct rq *rq, struct task_struct *p,                  int flags){    // 被唤醒进程 deadline 更小,需要进程切换    if (dl_entity_preempt(&p->dl, &rq->donor->dl)) {        resched_curr(rq);        return;    }    // deadline 相同,并且当前进程未设置切换标志    if ((p->dl.deadline == rq->donor->dl.deadline) &&        !test_tsk_need_resched(rq->curr))        check_preempt_equal_dl(rq, p);}

当当前进程和正在运行的进程优先级相同,不能盲目地直接抢占,要优先尝试通过多核迁移(Push/Pull)来避免不必要的上下文切换

static void check_preempt_equal_dl(struct rq *rq, struct task_struct *p){    // 当前进程绑定当前 CPU    // 或者其他 CPU 也没有适合运行当前进程    // 不需要切换    if (rq->curr->nr_cpus_allowed == 1 ||        !cpudl_find(&rq->rd->cpudl, rq->donor, NULL))        return;    // 被唤醒进程没有绑定 CPU 并且由其他 CPU 可以运行,不需要切换    if (p->nr_cpus_allowed != 1 &&        cpudl_find(&rq->rd->cpudl, p, NULL))        return;    // 当前任务可以迁移,但新任务 p 走不掉(比如 p 被绑死在当前核);    // 或者两者都走不掉。    resched_curr(rq); // 需要切换}

REALTIME 调度器比较优先级:被唤醒进程优先级更高就需要进程切换

static void wakeup_preempt_rt(struct rq *rq, struct task_struct *p, int flags){struct task_struct *donor = rq->donor;    // 被唤醒进程优先级更高,需要进程切换    if (p->prio < donor->prio) {        resched_curr(rq);        return;    }    /*     * If:     *     * - the newly woken task is of equal priority to the current task     * - the newly woken task is non-migratable while current is migratable     * - current will be preempted on the next reschedule     *     * we should check to see if current can readily move to a different     * cpu.  If so, we will reschedule to allow the push logic to try     * to move current somewhere else, making room for our non-migratable     * task.     */    if (p->prio == donor->prio && !test_tsk_need_resched(rq->curr))        check_preempt_equal_prio(rq, p);}

优先级相同尝试将当前进程和被唤醒进程迁移,都无法迁移切换进程

static void check_preempt_equal_prio(struct rq *rq, struct task_struct *p){    // 说明当前任务无法迁移到其他 CPU    if (rq->curr->nr_cpus_allowed == 1 ||        !cpupri_find(&rq->rd->cpupri, rq->donor, NULL))        return;    // p 可以迁移到其他 CPU    if (p->nr_cpus_allowed != 1 &&        cpupri_find(&rq->rd->cpupri, p, NULL))        return;    // 切换进程    requeue_task_rt(rq, p, 1);    resched_curr(rq);}

FAIR 调度器没有一个固定的优先级比较,而是根据 EEVDF 算法选出最 "Eligible" 的进程

  • • 被唤醒进程 p 是非 IDLE 进程,当前进程是 IDLE 进程,无条件抢占
  • • 根据 EEVDF 算法选出最 "Eligible" 正是 p,则抢占
static void check_preempt_wakeup_fair(struct rq *rq, struct task_struct *p, int wake_flags){enum preempt_wakeup_action preempt_action = PREEMPT_WAKEUP_PICK;struct task_struct *donor = rq->donor;struct sched_entity *nse, *se = &donor->se, *pse = &p->se;struct cfs_rq *cfs_rq = task_cfs_rq(donor);    int cse_is_idle, pse_is_idle;    if (unlikely(se == pse))        return;    // 被唤醒进程以及被节流,无法抢占    if (task_is_throttled(p))        return;    // 当前进程已经被标记进程切换,返回等待进程切换进行    if (test_tsk_need_resched(rq->curr))        return;    // 不支持抢占    if (!sched_feat(WAKEUP_PREEMPTION))        return;    find_matching_se(&se, &pse);    WARN_ON_ONCE(!pse);    cse_is_idle = se_is_idle(se);    pse_is_idle = se_is_idle(pse);    // 1. 当前进程是 IDLE 进程,被唤醒进程是非 IDLE 进程,无条件抢占    if (cse_is_idle && !pse_is_idle) {        /*         * When non-idle entity preempt an idle entity,         * don't give idle entity slice protection.         */        preempt_action = PREEMPT_WAKEUP_SHORT;        goto preempt;    }    // 当前进程是非 IDLE,被唤醒进程是 IDLE,不能抢占    if (cse_is_idle != pse_is_idle)        return;    /*     * BATCH and IDLE tasks do not preempt others.     */    if (unlikely(!normal_policy(p->policy)))        return;    cfs_rq = cfs_rq_of(se);    update_curr(cfs_rq);    /*     * If @p has a shorter slice than current and @p is eligible, override     * current's slice protection in order to allow preemption.     */    // 2. 被唤醒进程比当前进程 slice 时间片小,判断是不是最 Eligible    if (sched_feat(PREEMPT_SHORT) && (pse->slice < se->slice)) {        preempt_action = PREEMPT_WAKEUP_SHORT;        goto pick;    }    /*     * Ignore wakee preemption on WF_FORK as it is less likely that     * there is shared data as exec often follow fork. Do not     * preempt for tasks that are sched_delayed as it would violate     * EEVDF to forcibly queue an ineligible task.     */    // 刚 fork 出来的子进程不抢占,因为通常紧接着会 exec    // sched_delayed: 处于延迟调度状态的任务不能强制入队    if ((wake_flags & WF_FORK) || pse->sched_delayed)        return;    /* Prefer picking wakee soon if appropriate. */    if (sched_feat(NEXT_BUDDY) &&        set_preempt_buddy(cfs_rq, wake_flags, pse, se)) {        /*         * Decide whether to obey WF_SYNC hint for a new buddy. Old         * buddies are ignored as they may not be relevant to the         * waker and less likely to be cache hot.         */        if (wake_flags & WF_SYNC)            preempt_action = preempt_sync(rq, wake_flags, pse, se);    }    switch (preempt_action) {    case PREEMPT_WAKEUP_NONE:        return;    case PREEMPT_WAKEUP_RESCHED:        goto preempt;    case PREEMPT_WAKEUP_SHORT:        fallthrough;    case PREEMPT_WAKEUP_PICK:        break;    }pick:    nse = pick_next_entity(rq, cfs_rq, preempt_action != PREEMPT_WAKEUP_SHORT);    /* If @p has become the most eligible task, force preemption */    if (nse == pse)        goto preempt;    /*     * Because p is enqueued, nse being null can only mean that we     * dequeued a delayed task. If there are still entities queued in     * cfs, check if the next one will be p.     */    if (!nse && cfs_rq->nr_queued)        goto pick;    if (sched_feat(RUN_TO_PARITY))        update_protect_slice(cfs_rq, se);    return;preempt:    if (preempt_action == PREEMPT_WAKEUP_SHORT)        cancel_protect_slice(se);    resched_curr_lazy(rq);}

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 15:32:45 HTTP/2.0 GET : https://f.mffb.com.cn/a/508249.html
  2. 运行时间 : 0.352554s [ 吞吐率:2.84req/s ] 内存消耗:4,636.48kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=0ca15f6c51241727f52dc222615b66cd
  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.001045s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001410s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.007181s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000707s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001422s ]
  6. SELECT * FROM `set` [ RunTime:0.000612s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001556s ]
  8. SELECT * FROM `article` WHERE `id` = 508249 LIMIT 1 [ RunTime:0.055013s ]
  9. UPDATE `article` SET `lasttime` = 1787297565 WHERE `id` = 508249 [ RunTime:0.040493s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.001189s ]
  11. SELECT * FROM `article` WHERE `id` < 508249 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001429s ]
  12. SELECT * FROM `article` WHERE `id` > 508249 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001391s ]
  13. SELECT * FROM `article` WHERE `id` < 508249 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.048546s ]
  14. SELECT * FROM `article` WHERE `id` < 508249 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.009608s ]
  15. SELECT * FROM `article` WHERE `id` < 508249 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.003473s ]
0.356328s