где происходит переключение контекста в исходном коде ядра linux? - PullRequest
1 голос
/ 28 апреля 2020

В linux планирование процесса происходит после всех прерываний (прерывания по таймеру и других прерываний) или когда процесс освобождает ЦП (вызывая явную функцию schedule ()). Сегодня я пытался увидеть, где происходит переключение контекста в linux источнике (версия ядра 2.6.23)
(я думаю, что проверял это несколько лет go, но сейчас я не уверен ... Я смотрел на spar c arch.)
Я посмотрел его из main_timer_handler (в arch / x86_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 есть вызов preempt_schedule_irq! и preempt_schedule_irq определено в kernel / sched. c, как показано ниже (в середине вызывается schedule ()).

/*  
 * 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; 
}   

Итак, я нашел, где происходит планирование, но мой вопрос: «где в исходном коде происходит переключение контекста? " Для переключения контекста необходимо переключить стек, настройки мм, регистры и установить P C (программный счетчик) для новой задачи. Где я могу найти исходный код для этого? Я следовал за расписанием () -> context_switch () -> switch_to (). Ниже приведена функция context_switch, которая вызывает функцию switch_to (). (Kernel / sched. c)

/*
 * 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);
}

Switch_to - это код сборки в include / asm-x86_64 / system.h. у меня вопрос, процессор переключен на новую задачу внутри функции switch_to ()? Тогда, барьер кодов (); finish_task_switch (this_rq (), prev); ' бежать в другое время позже? Между прочим, это было в контексте прерывания, поэтому, если to_switch () является только концом этого ISR, кто заканчивает это прерывание? Или, если запустится finish_task_switch, как ЦП занят новой задачей? Я был бы очень признателен, если бы кто-то мог мне объяснить и уточнить.

1 Ответ

0 голосов
/ 28 апреля 2020

Почти вся работа по переключению контекста выполняется с помощью обычного механизма SYSCALL / SYSRET. Процесс помещает свое состояние в стек «текущего» текущего запущенного процесса. Вызов do_sched_yield просто меняет значение current, поэтому return просто восстанавливает состояние другой задачи.

Вытеснение становится сложнее, поскольку оно не происходит на нормальной границе. Код вытеснения должен сохранять и восстанавливать все состояние задачи, что является медленным. Вот почему ядра без RT избегают вытеснения. Специфичный для arch c код switch_to - это то, что сохраняет все предыдущее состояние задачи и устанавливает следующее состояние задачи, чтобы SYSRET правильно выполнял следующую задачу. В коде нет магических c прыжков или чего-либо еще, это просто настройка оборудования для пространства пользователя.

...