linux内核源代码中的上下文切换最终发生在哪里?

linux内核源代码中的上下文切换最终发生在哪里?,linux,scheduling,Linux,Scheduling,在linux中,进程调度发生在所有中断(计时器中断和其他中断)之后或进程放弃CPU时(通过调用explicit schedule()函数)。今天我想看看linux源代码(内核版本2.6.23)中的上下文切换发生在哪里 (我想我几年前查过了,但现在不确定……当时我在看sparc arch。) 我从主计时器处理程序(在arch/x86\u 64/kernel/time.c中)中查找它,但找不到它 最后我在./arch/x86_64/kernel/entry.S中找到了它 ENTRY(comm

在linux中,进程调度发生在所有中断(计时器中断和其他中断)之后或进程放弃CPU时(通过调用explicit schedule()函数)。今天我想看看linux源代码(内核版本2.6.23)中的上下文切换发生在哪里
(我想我几年前查过了,但现在不确定……当时我在看sparc arch。)
我从主计时器处理程序(在arch/x86\u 64/kernel/time.c中)中查找它,但找不到它

最后我在./arch/x86_64/kernel/entry.S中找到了它

    ENTRY(common_interrupt)
        XCPT_FRAME
        interrupt do_IRQ
        /* 0(%rsp): oldrsp-ARGOFFSET */
    ret_from_intr:
        cli
        TRACE_IRQS_OFF
        decl %gs:pda_irqcount
        leaveq
        CFI_DEF_CFA_REGISTER    rsp
        CFI_ADJUST_CFA_OFFSET   -8
    exit_intr:
        GET_THREAD_INFO(%rcx)
        testl $3,CS-ARGOFFSET(%rsp)
        je retint_kernel

...(omit)
        GET_THREAD_INFO(%rcx)
        jmp retint_check

    #ifdef CONFIG_PREEMPT
        /* Returning to kernel space. Check if we need preemption */
        /* rcx:  threadinfo. interrupts off. */
    ENTRY(retint_kernel)
        cmpl $0,threadinfo_preempt_count(%rcx)
        jnz  retint_restore_args
        bt  $TIF_NEED_RESCHED,threadinfo_flags(%rcx)
        jnc  retint_restore_args
        bt   $9,EFLAGS-ARGOFFSET(%rsp)  /* interrupts off? */
        jnc  retint_restore_args
        call preempt_schedule_irq
        jmp exit_intr
    #endif

        CFI_ENDPROC
    END(common_interrupt)
在ISR的末尾是一个抢占计划irq的调用!preempt_schedule_irq在kernel/sched.c中定义如下(它在中间调用schedule()

所以我找到了调度发生的地方,但我的问题是,“在源代码中,上下文切换实际上发生在哪里?”。对于上下文切换,应切换堆栈、mm设置、寄存器,并将PC(程序计数器)设置为新任务。我在哪里可以找到它的源代码?我遵循了schedule()-->context_switch()-->switch_to()。下面是调用switch_to()函数的context_switch函数

/*
*上下文_开关-切换到新MM和新
*线程的寄存器状态。
*/
静态内联空洞
上下文开关(结构rq*rq,结构任务_结构*prev,
结构任务(结构*下一步)
{
结构mm_结构*mm,*oldmm;
准备任务开关(rq、上一个、下一个);
mm=下一个->mm;
oldmm=prev->active\u mm;
/*
*对于帕拉韦,这与开关_至中的出口相耦合
*将页表重新加载和交换机后端合并到
*一个超级球。
*/
拱_进入_lazy_cpu_模式();
如果(不太可能(!mm)){
下一步->活动毫米=旧毫米;
原子计数公司(&oldmm->mm计数);
输入_lazy_tlb(oldmm,next);
}否则
开关_mm(旧的mm,mm,下一个);
如果(不太可能(!prev->mm)){
prev->active_mm=NULL;
rq->prev_mm=oldmm;
}
/*
*因为运行队列锁将在下一个
*任务(这是无效的锁定操作,但在
*这是一个明显的特例),所以我们
*在此处提前释放lockdep:
*/
#如果要解锁,请单击CTXSW
自旋释放(&rq->lock.dep\u映射,1,\u THIS\u IP);
#恩迪夫
/*这里我们只需切换寄存器状态和堆栈*/

切换到(prev,next,prev);//上下文切换的几乎所有工作都是通过正常的SYSCALL/SYSRET机制完成的。进程将其状态推到当前正在运行的进程的“当前”堆栈上。调用do_sched_yield只会更改current的值,因此返回只会恢复不同任务的状态


抢占变得更加棘手,因为它不会在正常边界上发生。抢占代码必须保存和恢复所有任务状态,这很慢。这就是非RT内核避免执行抢占的原因。arch特定的切换到代码是保存所有上一个任务状态并设置下一个任务状态,以便SYSRET运行下一个任务状态的方法正确。代码中没有神奇的跳跃或任何东西,它只是为用户空间设置硬件。

上下文切换的几乎所有工作都是由正常的SYSCALL/SYSRET机制完成的。进程将其状态推到“current”堆栈上当前正在运行的进程。调用do_sched_yield只会更改current的值,因此返回只会恢复不同任务的状态


抢占变得更加棘手,因为它不会在正常边界上发生。抢占代码必须保存和恢复所有任务状态,这很慢。这就是非RT内核避免执行抢占的原因。arch特定的切换到代码是保存所有上一个任务状态并设置下一个任务状态,以便SYSRET运行下一个任务状态的方法正确。代码中没有神奇的跳跃或任何东西,它只是为用户空间设置硬件。

好的,但我仍然无法理解。当正常进程产生CPU时,它进入等待队列,并在所有任务状态恢复后被唤醒。我知道。但正如我所展示的,当调度程序使用计时器中断抢占时(jiffies),它是通过switch_to code来实现的。你把这个switch_称为一种SYSCALL/SYSRET吗?我想是的。那么我现在的问题是,如果这个switch_to code是在中断上下文中输入的(在定时器中断期间,没有任何任务信息),那么这个switch_to()之后的代码以后怎么才能输入?CPU如何到达finish_task_开关(..)函数?否。switch_to仅操纵特定于arch的状态,然后返回。所有调用uu schedule(调用switch_to)的代码都是u asmlinkage,这意味着它是一个系统调用,并以一个SYSRET结束。如果switch_to仅操纵状态并返回,那么,PC(程序计数器)何时返回实际上已更改为下一个任务的设置?我不认为更改寄存器集包括更改程序计数器寄存器。我说的对吗?SYSCALL/SYSRET,您是指使用堆栈的正常调用/返回吗?很抱歉,问了太多问题,我将为此做功课。好的,但我仍然无法理解。当正常进程产生CPU时,它会进入等待队列并在恢复所有任务状态时被唤醒。我知道。但正如我所展示的,当调度程序使用计时器中断(jiffies)抢占时,它使用切换到代码。您是否将此切换到一种SYSCALL/SYSRET?我想是的。现在我的问题是,此切换到代码是否输入到中断上下文中(在定时器中断期间,没有任何任务信息),切换到()后的代码如何稍后输入?CPU如何达到finish_task_switch(..)函数?否。切换到仅操作arch特定状态,然后返回。所有调用调度的代码(调用切换到)是uu asmlinkage,意思是它是一个系统调用,以一个SYSRET结束
/*  
 * this is the entry point to schedule() from kernel preemption
 * off of irq context.
 * Note, that this is called and return with irqs disabled. This will
 * protect us against recursive calling from irq. 
 */ 
asmlinkage void __sched preempt_schedule_irq(void)
{   
    struct thread_info *ti = current_thread_info();
#ifdef CONFIG_PREEMPT_BKL
    struct task_struct *task = current;
    int saved_lock_depth;
#endif
    /* Catch callers which need to be fixed */
    BUG_ON(ti->preempt_count || !irqs_disabled());

need_resched:
    add_preempt_count(PREEMPT_ACTIVE);
    /*
     * We keep the big kernel semaphore locked, but we
     * clear ->lock_depth so that schedule() doesnt
     * auto-release the semaphore:
     */
#ifdef CONFIG_PREEMPT_BKL
    saved_lock_depth = task->lock_depth;
    task->lock_depth = -1; 
#endif
    local_irq_enable();
    schedule();
    local_irq_disable();
#ifdef CONFIG_PREEMPT_BKL
    task->lock_depth = saved_lock_depth;
#endif
    sub_preempt_count(PREEMPT_ACTIVE); 

    /* we could miss a preemption opportunity between schedule and now */
    barrier();
    if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
        goto need_resched; 
}   
/*
 * context_switch - switch to the new MM and the new
 * thread's register state.
 */
static inline void
context_switch(struct rq *rq, struct task_struct *prev,
           struct task_struct *next)
{
    struct mm_struct *mm, *oldmm;

    prepare_task_switch(rq, prev, next);
    mm = next->mm;
    oldmm = prev->active_mm;
    /*
     * For paravirt, this is coupled with an exit in switch_to to
     * combine the page table reload and the switch backend into
     * one hypercall.
     */
    arch_enter_lazy_cpu_mode();

    if (unlikely(!mm)) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
    } else
        switch_mm(oldmm, mm, next);

    if (unlikely(!prev->mm)) {
        prev->active_mm = NULL;
        rq->prev_mm = oldmm;
    }
    /*
     * Since the runqueue lock will be released by the next
     * task (which is an invalid locking op but in the case
     * of the scheduler it's an obvious special-case), so we
     * do an early lockdep release here:
     */
#ifndef __ARCH_WANT_UNLOCKED_CTXSW
    spin_release(&rq->lock.dep_map, 1, _THIS_IP_);
#endif

    /* Here we just switch the register state and the stack. */
    switch_to(prev, next, prev);   // <---- this line

    barrier();
    /*
     * this_rq must be evaluated again because prev may have moved
     * CPUs since it called schedule(), thus the 'rq' on its stack
     * frame will be invalid.
     */
    finish_task_switch(this_rq(), prev);
}