Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • kernel/generic/src/proc/scheduler.c

    rd23712e rec8ef12  
    11/*
    22 * Copyright (c) 2010 Jakub Jermar
    3  * Copyright (c) 2023 Jiří Zárevúcky
    43 * All rights reserved.
    54 *
     
    5150#include <time/delay.h>
    5251#include <arch/asm.h>
     52#include <arch/faddr.h>
    5353#include <arch/cycle.h>
    5454#include <atomic.h>
     
    6666#include <stacktrace.h>
    6767
     68static void scheduler_separated_stack(void);
     69
    6870atomic_size_t nrdy;  /**< Number of ready threads in the system. */
     71
     72/** Take actions before new thread runs.
     73 *
     74 * Perform actions that need to be
     75 * taken before the newly selected
     76 * thread is passed control.
     77 *
     78 * THREAD->lock is locked on entry
     79 *
     80 */
     81static void before_thread_runs(void)
     82{
     83        before_thread_runs_arch();
     84
     85#ifdef CONFIG_FPU_LAZY
     86        /*
     87         * The only concurrent modification possible for fpu_owner here is
     88         * another thread changing it from itself to NULL in its destructor.
     89         */
     90        thread_t *owner = atomic_load_explicit(&CPU->fpu_owner,
     91            memory_order_relaxed);
     92
     93        if (THREAD == owner)
     94                fpu_enable();
     95        else
     96                fpu_disable();
     97#elif defined CONFIG_FPU
     98        fpu_enable();
     99        if (THREAD->fpu_context_exists)
     100                fpu_context_restore(&THREAD->fpu_context);
     101        else {
     102                fpu_init();
     103                THREAD->fpu_context_exists = true;
     104        }
     105#endif
     106
     107#ifdef CONFIG_UDEBUG
     108        if (THREAD->btrace) {
     109                istate_t *istate = THREAD->udebug.uspace_state;
     110                if (istate != NULL) {
     111                        printf("Thread %" PRIu64 " stack trace:\n", THREAD->tid);
     112                        stack_trace_istate(istate);
     113                }
     114
     115                THREAD->btrace = false;
     116        }
     117#endif
     118}
     119
     120/** Take actions after THREAD had run.
     121 *
     122 * Perform actions that need to be
     123 * taken after the running thread
     124 * had been preempted by the scheduler.
     125 *
     126 * THREAD->lock is locked on entry
     127 *
     128 */
     129static void after_thread_ran(void)
     130{
     131        after_thread_ran_arch();
     132}
    69133
    70134#ifdef CONFIG_FPU_LAZY
     
    143207                list_remove(&thread->rq_link);
    144208
    145                 irq_spinlock_unlock(&(CPU->rq[i].lock), false);
     209                irq_spinlock_pass(&(CPU->rq[i].lock), &thread->lock);
     210
     211                thread->cpu = CPU;
     212                thread->priority = i;  /* Correct rq index */
     213
     214                /* Time allocation in microseconds. */
     215                uint64_t time_to_run = (i + 1) * 10000;
     216
     217                /* This is safe because interrupts are disabled. */
     218                CPU->preempt_deadline = CPU->current_clock_tick + us2ticks(time_to_run);
     219
     220                /*
     221                 * Clear the stolen flag so that it can be migrated
     222                 * when load balancing needs emerge.
     223                 */
     224                thread->stolen = false;
     225                irq_spinlock_unlock(&thread->lock, false);
    146226
    147227                *rq_index = i;
     
    177257                 * This improves energy saving and hyperthreading.
    178258                 */
    179                 CPU_LOCAL->idle = true;
     259                CPU->idle = true;
    180260
    181261                /*
     
    225305static void relink_rq(int start)
    226306{
    227         assert(interrupts_disabled());
    228 
    229         if (CPU_LOCAL->current_clock_tick < CPU_LOCAL->relink_deadline)
     307        if (CPU->current_clock_tick < CPU->relink_deadline)
    230308                return;
    231309
    232         CPU_LOCAL->relink_deadline = CPU_LOCAL->current_clock_tick + NEEDS_RELINK_MAX;
     310        CPU->relink_deadline = CPU->current_clock_tick + NEEDS_RELINK_MAX;
    233311
    234312        /* Temporary cache for lists we are moving. */
     
    262340}
    263341
    264 /**
    265  * Do whatever needs to be done with current FPU state before we switch to
    266  * another thread.
    267  */
    268 static void fpu_cleanup(void)
    269 {
     342void scheduler(void)
     343{
     344        ipl_t ipl = interrupts_disable();
     345
     346        if (atomic_load(&haltstate))
     347                halt();
     348
     349        if (THREAD) {
     350                irq_spinlock_lock(&THREAD->lock, false);
     351        }
     352
     353        scheduler_locked(ipl);
     354}
     355
     356/** The scheduler
     357 *
     358 * The thread scheduling procedure.
     359 * Passes control directly to
     360 * scheduler_separated_stack().
     361 *
     362 */
     363void scheduler_locked(ipl_t ipl)
     364{
     365        assert(CPU != NULL);
     366
     367        if (THREAD) {
     368                /* Update thread kernel accounting */
     369                THREAD->kcycles += get_cycle() - THREAD->last_cycle;
     370
    270371#if (defined CONFIG_FPU) && (!defined CONFIG_FPU_LAZY)
    271         fpu_context_save(&THREAD->fpu_context);
     372                fpu_context_save(&THREAD->fpu_context);
    272373#endif
    273 }
    274 
    275 /**
    276  * Set correct FPU state for this thread after switch from another thread.
    277  */
    278 static void fpu_restore(void)
    279 {
    280 #ifdef CONFIG_FPU_LAZY
    281         /*
    282          * The only concurrent modification possible for fpu_owner here is
    283          * another thread changing it from itself to NULL in its destructor.
    284          */
    285         thread_t *owner = atomic_load_explicit(&CPU->fpu_owner,
    286             memory_order_relaxed);
    287 
    288         if (THREAD == owner)
    289                 fpu_enable();
    290         else
    291                 fpu_disable();
    292 
    293 #elif defined CONFIG_FPU
    294         fpu_enable();
    295         if (THREAD->fpu_context_exists)
    296                 fpu_context_restore(&THREAD->fpu_context);
    297         else {
    298                 fpu_init();
    299                 THREAD->fpu_context_exists = true;
    300         }
    301 #endif
    302 }
    303 
    304 /** Things to do before we switch to THREAD context.
    305  */
    306 static void prepare_to_run_thread(int rq_index)
    307 {
     374                if (!context_save(&THREAD->saved_context)) {
     375                        /*
     376                         * This is the place where threads leave scheduler();
     377                         */
     378
     379                        /* Save current CPU cycle */
     380                        THREAD->last_cycle = get_cycle();
     381
     382                        irq_spinlock_unlock(&THREAD->lock, false);
     383                        interrupts_restore(THREAD->saved_ipl);
     384
     385                        return;
     386                }
     387
     388                /*
     389                 * Interrupt priority level of preempted thread is recorded
     390                 * here to facilitate scheduler() invocations from
     391                 * interrupts_disable()'d code (e.g. waitq_sleep_timeout()).
     392                 *
     393                 */
     394                THREAD->saved_ipl = ipl;
     395        }
     396
     397        /*
     398         * Through the 'CURRENT' structure, we keep track of THREAD, TASK, CPU, AS
     399         * and preemption counter. At this point CURRENT could be coming either
     400         * from THREAD's or CPU's stack.
     401         *
     402         */
     403        current_copy(CURRENT, (current_t *) CPU->stack);
     404
     405        /*
     406         * We may not keep the old stack.
     407         * Reason: If we kept the old stack and got blocked, for instance, in
     408         * find_best_thread(), the old thread could get rescheduled by another
     409         * CPU and overwrite the part of its own stack that was also used by
     410         * the scheduler on this CPU.
     411         *
     412         * Moreover, we have to bypass the compiler-generated POP sequence
     413         * which is fooled by SP being set to the very top of the stack.
     414         * Therefore the scheduler() function continues in
     415         * scheduler_separated_stack().
     416         *
     417         */
     418        context_t ctx;
     419        context_save(&ctx);
     420        context_set(&ctx, FADDR(scheduler_separated_stack),
     421            (uintptr_t) CPU->stack, STACK_SIZE);
     422        context_restore(&ctx);
     423
     424        /* Not reached */
     425}
     426
     427/** Scheduler stack switch wrapper
     428 *
     429 * Second part of the scheduler() function
     430 * using new stack. Handling the actual context
     431 * switch to a new thread.
     432 *
     433 */
     434void scheduler_separated_stack(void)
     435{
     436        assert((!THREAD) || (irq_spinlock_locked(&THREAD->lock)));
     437        assert(CPU != NULL);
     438        assert(interrupts_disabled());
     439
     440        if (THREAD) {
     441                /* Must be run after the switch to scheduler stack */
     442                after_thread_ran();
     443
     444                switch (THREAD->state) {
     445                case Running:
     446                        irq_spinlock_unlock(&THREAD->lock, false);
     447                        thread_ready(THREAD);
     448                        break;
     449
     450                case Exiting:
     451                        irq_spinlock_unlock(&THREAD->lock, false);
     452                        waitq_close(&THREAD->join_wq);
     453
     454                        /*
     455                         * Release the reference CPU has for the thread.
     456                         * If there are no other references (e.g. threads calling join),
     457                         * the thread structure is deallocated.
     458                         */
     459                        thread_put(THREAD);
     460                        break;
     461
     462                case Sleeping:
     463                        /*
     464                         * Prefer the thread after it's woken up.
     465                         */
     466                        THREAD->priority = -1;
     467                        irq_spinlock_unlock(&THREAD->lock, false);
     468                        break;
     469
     470                default:
     471                        /*
     472                         * Entering state is unexpected.
     473                         */
     474                        panic("tid%" PRIu64 ": unexpected state %s.",
     475                            THREAD->tid, thread_states[THREAD->state]);
     476                        break;
     477                }
     478
     479                THREAD = NULL;
     480        }
     481
     482        int rq_index;
     483        THREAD = find_best_thread(&rq_index);
     484
    308485        relink_rq(rq_index);
    309486
    310487        switch_task(THREAD->task);
    311488
    312         assert(atomic_get_unordered(&THREAD->cpu) == CPU);
    313 
    314         atomic_set_unordered(&THREAD->state, Running);
    315         atomic_set_unordered(&THREAD->priority, rq_index);  /* Correct rq index */
    316 
    317         /*
    318          * Clear the stolen flag so that it can be migrated
    319          * when load balancing needs emerge.
    320          */
    321         THREAD->stolen = false;
     489        irq_spinlock_lock(&THREAD->lock, false);
     490        THREAD->state = Running;
    322491
    323492#ifdef SCHEDULER_VERBOSE
    324493        log(LF_OTHER, LVL_DEBUG,
    325494            "cpu%u: tid %" PRIu64 " (priority=%d, ticks=%" PRIu64
    326             ", nrdy=%zu)", CPU->id, THREAD->tid, rq_index,
     495            ", nrdy=%zu)", CPU->id, THREAD->tid, THREAD->priority,
    327496            THREAD->ticks, atomic_load(&CPU->nrdy));
    328497#endif
     
    336505         * function must be executed before the switch to the new stack.
    337506         */
    338         before_thread_runs_arch();
    339 
    340 #ifdef CONFIG_UDEBUG
    341         if (atomic_get_unordered(&THREAD->btrace)) {
    342                 istate_t *istate = THREAD->udebug.uspace_state;
    343                 if (istate != NULL) {
    344                         printf("Thread %" PRIu64 " stack trace:\n", THREAD->tid);
    345                         stack_trace_istate(istate);
    346                 } else {
    347                         printf("Thread %" PRIu64 " interrupt state not available\n", THREAD->tid);
    348                 }
    349 
    350                 atomic_set_unordered(&THREAD->btrace, false);
    351         }
    352 #endif
    353 
    354         fpu_restore();
    355 
    356         /* Time allocation in microseconds. */
    357         uint64_t time_to_run = (rq_index + 1) * 10000;
    358 
    359         /* Set the time of next preemption. */
    360         CPU_LOCAL->preempt_deadline =
    361             CPU_LOCAL->current_clock_tick + us2ticks(time_to_run);
    362 
    363         /* Save current CPU cycle */
    364         THREAD->last_cycle = get_cycle();
    365 }
    366 
    367 static void add_to_rq(thread_t *thread, cpu_t *cpu, int i)
    368 {
    369         /* Add to the appropriate runqueue. */
    370         runq_t *rq = &cpu->rq[i];
    371 
    372         irq_spinlock_lock(&rq->lock, false);
    373         list_append(&thread->rq_link, &rq->rq);
    374         rq->n++;
    375         irq_spinlock_unlock(&rq->lock, false);
    376 
    377         atomic_inc(&nrdy);
    378         atomic_inc(&cpu->nrdy);
    379 }
    380 
    381 /** Requeue a thread that was just preempted on this CPU.
    382  */
    383 static void thread_requeue_preempted(thread_t *thread)
    384 {
    385         assert(interrupts_disabled());
    386         assert(atomic_get_unordered(&thread->state) == Running);
    387         assert(atomic_get_unordered(&thread->cpu) == CPU);
    388 
    389         int prio = atomic_get_unordered(&thread->priority);
    390 
    391         if (prio < RQ_COUNT - 1) {
    392                 prio++;
    393                 atomic_set_unordered(&thread->priority, prio);
    394         }
    395 
    396         atomic_set_unordered(&thread->state, Ready);
    397 
    398         add_to_rq(thread, CPU, prio);
    399 }
    400 
    401 void thread_requeue_sleeping(thread_t *thread)
    402 {
    403         ipl_t ipl = interrupts_disable();
    404 
    405         assert(atomic_get_unordered(&thread->state) == Sleeping || atomic_get_unordered(&thread->state) == Entering);
    406 
    407         atomic_set_unordered(&thread->priority, 0);
    408         atomic_set_unordered(&thread->state, Ready);
    409 
    410         /* Prefer the CPU on which the thread ran last */
    411         cpu_t *cpu = atomic_get_unordered(&thread->cpu);
    412 
    413         if (!cpu) {
    414                 cpu = CPU;
    415                 atomic_set_unordered(&thread->cpu, CPU);
    416         }
    417 
    418         add_to_rq(thread, cpu, 0);
    419 
    420         interrupts_restore(ipl);
    421 }
    422 
    423 static void cleanup_after_thread(thread_t *thread)
    424 {
    425         assert(CURRENT->mutex_locks == 0);
    426         assert(interrupts_disabled());
    427 
    428         int expected;
    429 
    430         switch (atomic_get_unordered(&thread->state)) {
    431         case Running:
    432                 thread_requeue_preempted(thread);
    433                 break;
    434 
    435         case Exiting:
    436                 waitq_close(&thread->join_wq);
    437 
    438                 /*
    439                  * Release the reference CPU has for the thread.
    440                  * If there are no other references (e.g. threads calling join),
    441                  * the thread structure is deallocated.
    442                  */
    443                 thread_put(thread);
    444                 break;
    445 
    446         case Sleeping:
    447                 expected = SLEEP_INITIAL;
    448 
    449                 /* Only set SLEEP_ASLEEP in sleep pad if it's still in initial state */
    450                 if (!atomic_compare_exchange_strong_explicit(&thread->sleep_state,
    451                     &expected, SLEEP_ASLEEP,
    452                     memory_order_acq_rel, memory_order_acquire)) {
    453 
    454                         assert(expected == SLEEP_WOKE);
    455                         /* The thread has already been woken up, requeue immediately. */
    456                         thread_requeue_sleeping(thread);
    457                 }
    458                 break;
    459 
    460         default:
    461                 /*
    462                  * Entering state is unexpected.
    463                  */
    464                 panic("tid%" PRIu64 ": unexpected state %s.",
    465                     thread->tid, thread_states[atomic_get_unordered(&thread->state)]);
    466                 break;
    467         }
    468 }
    469 
    470 /** Switch to scheduler context to let other threads run. */
    471 void scheduler_enter(state_t new_state)
    472 {
    473         ipl_t ipl = interrupts_disable();
    474 
    475         assert(CPU != NULL);
    476         assert(THREAD != NULL);
    477 
    478         if (atomic_load(&haltstate))
    479                 halt();
    480 
    481         /* Check if we have a thread to switch to. */
    482 
    483         int rq_index;
    484         thread_t *new_thread = try_find_thread(&rq_index);
    485 
    486         if (new_thread == NULL && new_state == Running) {
    487                 /* No other thread to run, but we still have work to do here. */
    488                 interrupts_restore(ipl);
    489                 return;
    490         }
    491 
    492         atomic_set_unordered(&THREAD->state, new_state);
    493 
    494         /* Update thread kernel accounting */
    495         atomic_time_increment(&THREAD->kcycles, get_cycle() - THREAD->last_cycle);
    496 
    497         fpu_cleanup();
    498 
    499         /*
    500          * On Sparc, this saves some extra userspace state that's not
    501          * covered by context_save()/context_restore().
    502          */
    503         after_thread_ran_arch();
    504 
    505         if (new_thread) {
    506                 thread_t *old_thread = THREAD;
    507                 CPU_LOCAL->prev_thread = old_thread;
    508                 THREAD = new_thread;
    509                 /* No waiting necessary, we can switch to the new thread directly. */
    510                 prepare_to_run_thread(rq_index);
    511 
    512                 current_copy(CURRENT, (current_t *) new_thread->kstack);
    513                 context_swap(&old_thread->saved_context, &new_thread->saved_context);
    514         } else {
    515                 /*
    516                  * A new thread isn't immediately available, switch to a separate
    517                  * stack to sleep or do other idle stuff.
    518                  */
    519                 current_copy(CURRENT, (current_t *) CPU_LOCAL->stack);
    520                 context_swap(&THREAD->saved_context, &CPU_LOCAL->scheduler_context);
    521         }
    522 
    523         assert(CURRENT->mutex_locks == 0);
    524         assert(interrupts_disabled());
    525 
    526         /* Check if we need to clean up after another thread. */
    527         if (CPU_LOCAL->prev_thread) {
    528                 cleanup_after_thread(CPU_LOCAL->prev_thread);
    529                 CPU_LOCAL->prev_thread = NULL;
    530         }
    531 
    532         interrupts_restore(ipl);
    533 }
    534 
    535 /** Enter main scheduler loop. Never returns.
    536  *
    537  * This function switches to a runnable thread as soon as one is available,
    538  * after which it is only switched back to if a thread is stopping and there is
    539  * no other thread to run in its place. We need a separate context for that
    540  * because we're going to block the CPU, which means we need another context
    541  * to clean up after the previous thread.
    542  */
    543 void scheduler_run(void)
    544 {
    545         assert(interrupts_disabled());
    546 
    547         assert(CPU != NULL);
    548         assert(TASK == NULL);
    549         assert(THREAD == NULL);
    550         assert(interrupts_disabled());
    551 
    552         while (!atomic_load(&haltstate)) {
    553                 assert(CURRENT->mutex_locks == 0);
    554 
    555                 int rq_index;
    556                 THREAD = find_best_thread(&rq_index);
    557                 prepare_to_run_thread(rq_index);
    558 
    559                 /*
    560                  * Copy the knowledge of CPU, TASK, THREAD and preemption counter to
    561                  * thread's stack.
    562                  */
    563                 current_copy(CURRENT, (current_t *) THREAD->kstack);
    564 
    565                 /* Switch to thread context. */
    566                 context_swap(&CPU_LOCAL->scheduler_context, &THREAD->saved_context);
    567 
    568                 /* Back from another thread. */
    569                 assert(CPU != NULL);
    570                 assert(THREAD != NULL);
    571                 assert(CURRENT->mutex_locks == 0);
    572                 assert(interrupts_disabled());
    573 
    574                 cleanup_after_thread(THREAD);
    575 
    576                 /*
    577                  * Necessary because we're allowing interrupts in find_best_thread(),
    578                  * so we need to avoid other code referencing the thread we left.
    579                  */
    580                 THREAD = NULL;
    581         }
    582 
    583         halt();
    584 }
    585 
    586 /** Thread wrapper.
    587  *
    588  * This wrapper is provided to ensure that a starting thread properly handles
    589  * everything it needs to do when first scheduled, and when it exits.
    590  */
    591 void thread_main_func(void)
    592 {
    593         assert(interrupts_disabled());
    594 
    595         void (*f)(void *) = THREAD->thread_code;
    596         void *arg = THREAD->thread_arg;
    597 
    598         /* This is where each thread wakes up after its creation */
    599 
    600         /* Check if we need to clean up after another thread. */
    601         if (CPU_LOCAL->prev_thread) {
    602                 cleanup_after_thread(CPU_LOCAL->prev_thread);
    603                 CPU_LOCAL->prev_thread = NULL;
    604         }
    605 
    606         interrupts_enable();
    607 
    608         f(arg);
    609 
    610         thread_exit();
     507        before_thread_runs();
     508
     509        /*
     510         * Copy the knowledge of CPU, TASK, THREAD and preemption counter to
     511         * thread's stack.
     512         */
     513        current_copy(CURRENT, (current_t *) THREAD->kstack);
     514
     515        context_restore(&THREAD->saved_context);
    611516
    612517        /* Not reached */
     
    633538        /* Search rq from the back */
    634539        list_foreach_rev(old_rq->rq, rq_link, thread_t, thread) {
     540
     541                irq_spinlock_lock(&thread->lock, false);
    635542
    636543                /*
     
    640547                 * FPU context is still in the CPU.
    641548                 */
    642                 if (thread->stolen || thread->nomigrate || thread == fpu_owner) {
     549                if (thread->stolen || thread->nomigrate ||
     550                    thread == fpu_owner) {
     551                        irq_spinlock_unlock(&thread->lock, false);
    643552                        continue;
    644553                }
    645554
    646555                thread->stolen = true;
    647                 atomic_set_unordered(&thread->cpu, CPU);
     556                thread->cpu = CPU;
     557
     558                irq_spinlock_unlock(&thread->lock, false);
    648559
    649560                /*
     
    748659                 *
    749660                 */
    750                 thread_yield();
     661                scheduler();
    751662        } else {
    752663                /*
     
    775686                        continue;
    776687
    777                 printf("cpu%u: address=%p, nrdy=%zu\n",
    778                     cpus[cpu].id, &cpus[cpu], atomic_load(&cpus[cpu].nrdy));
     688                /* Technically a data race, but we don't really care in this case. */
     689                int needs_relink = cpus[cpu].relink_deadline - cpus[cpu].current_clock_tick;
     690
     691                printf("cpu%u: address=%p, nrdy=%zu, needs_relink=%d\n",
     692                    cpus[cpu].id, &cpus[cpu], atomic_load(&cpus[cpu].nrdy),
     693                    needs_relink);
    779694
    780695                unsigned int i;
     
    790705                            thread) {
    791706                                printf("%" PRIu64 "(%s) ", thread->tid,
    792                                     thread_states[atomic_get_unordered(&thread->state)]);
     707                                    thread_states[thread->state]);
    793708                        }
    794709                        printf("\n");
Note: See TracChangeset for help on using the changeset viewer.