/* * Copyright (c) 2012, 2013 Richard Braun. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * By convention, kernel threads are named after their start function. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Default time slice for real-time round-robin scheduling. */ #define THREAD_DEFAULT_RR_TIME_SLICE (HZ / 10) /* * Run queue properties for real-time threads. */ struct thread_rt_runq { unsigned int bitmap; struct list threads[THREAD_SCHED_RT_PRIO_MAX + 1]; }; /* * Group of threads sharing the same weight. */ struct thread_ts_group { struct list node; struct list threads; unsigned int weight; unsigned int work; }; /* * Run queue properties for time-sharing threads. * * The current group pointer has a valid address only when the run queue isn't * empty. */ struct thread_ts_runq { struct thread_ts_group group_array[THREAD_SCHED_TS_PRIO_MAX + 1]; struct list groups; struct thread_ts_group *current; unsigned int weight; unsigned int work; }; /* * Per processor run queue. */ struct thread_runq { struct spinlock lock; struct thread *current; struct thread_rt_runq rt_runq; struct thread_ts_runq ts_runqs[2]; struct thread_ts_runq *ts_runq_active; struct thread_ts_runq *ts_runq_expired; struct thread *idler; } __aligned(CPU_L1_SIZE); /* * Operations of a scheduling class. */ struct thread_sched_ops { void (*init_thread)(struct thread *thread, unsigned short priority); void (*add)(struct thread_runq *runq, struct thread *thread); void (*remove)(struct thread_runq *runq, struct thread *thread); void (*put_prev)(struct thread_runq *runq, struct thread *thread); struct thread * (*get_next)(struct thread_runq *runq); void (*tick)(struct thread_runq *runq, struct thread *thread); }; static struct thread_runq thread_runqs[MAX_CPUS]; /* * Statically allocating the idler thread structures enables their use as * "current" threads during system bootstrap, which prevents migration and * preemption control functions from crashing. */ static struct thread thread_idlers[MAX_CPUS]; /* * Caches for allocated threads and their stacks. */ static struct kmem_cache thread_cache; static struct kmem_cache thread_stack_cache; /* * Table used to quickly map policies to classes. */ static unsigned char thread_policy_table[THREAD_NR_SCHED_POLICIES]; /* * Scheduling class operations. */ static struct thread_sched_ops thread_sched_ops[THREAD_NR_SCHED_CLASSES]; static struct thread_attr thread_default_attr = { NULL, NULL, THREAD_SCHED_POLICY_TS, THREAD_SCHED_TS_PRIO_DEFAULT }; static void __init thread_runq_init_rt(struct thread_runq *runq) { struct thread_rt_runq *rt_runq; size_t i; rt_runq = &runq->rt_runq; rt_runq->bitmap = 0; for (i = 0; i < ARRAY_SIZE(rt_runq->threads); i++) list_init(&rt_runq->threads[i]); } static void __init thread_ts_group_init(struct thread_ts_group *group) { list_init(&group->threads); group->weight = 0; group->work = 0; } static void __init thread_ts_runq_init(struct thread_ts_runq *ts_runq) { size_t i; for (i = 0; i < ARRAY_SIZE(ts_runq->group_array); i++) thread_ts_group_init(&ts_runq->group_array[i]); list_init(&ts_runq->groups); ts_runq->weight = 0; ts_runq->work = 0; } static void __init thread_runq_init_ts(struct thread_runq *runq) { runq->ts_runq_active = &runq->ts_runqs[0]; runq->ts_runq_expired = &runq->ts_runqs[1]; thread_ts_runq_init(runq->ts_runq_active); thread_ts_runq_init(runq->ts_runq_expired); } static void __init thread_runq_init_idle(struct thread_runq *runq) { struct thread *idler; /* Initialize what's needed during bootstrap */ idler = &thread_idlers[runq - thread_runqs]; idler->flags = 0; idler->preempt = 1; idler->sched_policy = THREAD_SCHED_POLICY_IDLE; idler->sched_class = THREAD_SCHED_CLASS_IDLE; idler->task = kernel_task; runq->idler = idler; } static void __init thread_runq_init(struct thread_runq *runq) { spinlock_init(&runq->lock); thread_runq_init_rt(runq); thread_runq_init_ts(runq); thread_runq_init_idle(runq); runq->current = runq->idler; } static void thread_runq_add(struct thread_runq *runq, struct thread *thread) { assert(!cpu_intr_enabled()); spinlock_assert_locked(&runq->lock); thread_sched_ops[thread->sched_class].add(runq, thread); if (thread->sched_class < runq->current->sched_class) runq->current->flags |= THREAD_RESCHEDULE; } static void thread_runq_remove(struct thread_runq *runq, struct thread *thread) { assert(!cpu_intr_enabled()); spinlock_assert_locked(&runq->lock); thread_sched_ops[thread->sched_class].remove(runq, thread); } static void thread_runq_put_prev(struct thread_runq *runq, struct thread *thread) { assert(!cpu_intr_enabled()); spinlock_assert_locked(&runq->lock); thread_sched_ops[thread->sched_class].put_prev(runq, thread); } static struct thread * thread_runq_get_next(struct thread_runq *runq) { struct thread *thread; unsigned int i; assert(!cpu_intr_enabled()); spinlock_assert_locked(&runq->lock); for (i = 0; i < ARRAY_SIZE(thread_sched_ops); i++) { thread = thread_sched_ops[i].get_next(runq); if (thread != NULL) { runq->current = thread; return thread; } } /* The idle class should never be empty */ panic("thread: unable to find next thread"); } static inline struct thread_runq * thread_runq_local(void) { assert(!thread_preempt_enabled() || thread_pinned()); return &thread_runqs[cpu_id()]; } static void thread_sched_rt_init_thread(struct thread *thread, unsigned short priority) { assert(priority <= THREAD_SCHED_RT_PRIO_MAX); thread->rt_ctx.priority = priority; thread->rt_ctx.time_slice = THREAD_DEFAULT_RR_TIME_SLICE; } static void thread_sched_rt_add(struct thread_runq *runq, struct thread *thread) { struct thread_rt_runq *rt_runq; struct list *threads; rt_runq = &runq->rt_runq; threads = &rt_runq->threads[thread->rt_ctx.priority]; list_insert_tail(threads, &thread->rt_ctx.node); if (list_singular(threads)) rt_runq->bitmap |= (1U << thread->rt_ctx.priority); if ((thread->sched_class == runq->current->sched_class) && (thread->rt_ctx.priority > runq->current->rt_ctx.priority)) runq->current->flags |= THREAD_RESCHEDULE; } static void thread_sched_rt_remove(struct thread_runq *runq, struct thread *thread) { struct thread_rt_runq *rt_runq; struct list *threads; rt_runq = &runq->rt_runq; threads = &rt_runq->threads[thread->rt_ctx.priority]; list_remove(&thread->rt_ctx.node); if (list_empty(threads)) rt_runq->bitmap &= ~(1U << thread->rt_ctx.priority); } static void thread_sched_rt_put_prev(struct thread_runq *runq, struct thread *thread) { thread_sched_rt_add(runq, thread); } static struct thread * thread_sched_rt_get_next(struct thread_runq *runq) { struct thread_rt_runq *rt_runq; struct thread *thread; struct list *threads; unsigned int priority; rt_runq = &runq->rt_runq; if (rt_runq->bitmap == 0) return NULL; priority = THREAD_SCHED_RT_PRIO_MAX - __builtin_clz(rt_runq->bitmap); threads = &rt_runq->threads[priority]; assert(!list_empty(threads)); thread = list_first_entry(threads, struct thread, rt_ctx.node); thread_sched_rt_remove(runq, thread); return thread; } static void thread_sched_rt_tick(struct thread_runq *runq, struct thread *thread) { (void)runq; if (thread->sched_policy != THREAD_SCHED_POLICY_RR) return; thread->rt_ctx.time_slice--; if (thread->rt_ctx.time_slice > 0) return; thread->rt_ctx.time_slice = THREAD_DEFAULT_RR_TIME_SLICE; thread->flags |= THREAD_RESCHEDULE; } static void thread_sched_ts_init_thread(struct thread *thread, unsigned short priority) { assert(priority <= THREAD_SCHED_TS_PRIO_MAX); thread->ts_ctx.ts_runq = NULL; thread->ts_ctx.weight = priority + 1; thread->ts_ctx.work = 0; } static unsigned int thread_sched_ts_enqueue_scale(unsigned int work, unsigned int old_weight, unsigned int new_weight) { assert(old_weight != 0); #ifndef __LP64__ if (likely((work < 0x10000) && (new_weight < 0x10000))) return (work * new_weight) / old_weight; #endif /* __LP64__ */ return (unsigned int)(((unsigned long long)work * new_weight) / old_weight); } static void thread_sched_ts_enqueue(struct thread_ts_runq *ts_runq, struct thread *thread) { struct thread_ts_group *group, *tmp; struct list *node, *init_node; unsigned int thread_work, group_work, group_weight, total_weight; assert(thread->ts_ctx.ts_runq == NULL); group = &ts_runq->group_array[thread->ts_ctx.weight - 1]; group_weight = group->weight + thread->ts_ctx.weight; /* TODO Limit the maximum number of threads to prevent this situation */ if (group_weight < group->weight) panic("thread: weight overflow"); total_weight = ts_runq->weight + thread->ts_ctx.weight; if (total_weight < ts_runq->weight) panic("thread: weight overflow"); node = (group->weight == 0) ? list_last(&ts_runq->groups) : list_prev(&group->node); init_node = node; while (!list_end(&ts_runq->groups, node)) { tmp = list_entry(node, struct thread_ts_group, node); if (tmp->weight >= group_weight) break; node = list_prev(node); } if (group->weight == 0) list_insert_after(node, &group->node); else if (node != init_node) { list_remove(&group->node); list_insert_after(node, &group->node); } if (ts_runq->weight == 0) thread_work = 0; else { group_work = (group->weight == 0) ? thread_sched_ts_enqueue_scale(ts_runq->work, ts_runq->weight, thread->ts_ctx.weight) : thread_sched_ts_enqueue_scale(group->work, group->weight, group_weight); thread_work = group_work - group->work; ts_runq->work += thread_work; group->work = group_work; } ts_runq->weight = total_weight; group->weight = group_weight; thread->ts_ctx.work = thread_work; list_insert_tail(&group->threads, &thread->ts_ctx.node); thread->ts_ctx.ts_runq = ts_runq; } static void thread_sched_ts_restart(struct thread_runq *runq) { struct thread_ts_runq *ts_runq; struct list *node; ts_runq = runq->ts_runq_active; node = list_first(&ts_runq->groups); assert(node != NULL); ts_runq->current = list_entry(node, struct thread_ts_group, node); if (runq->current->sched_class == THREAD_SCHED_CLASS_TS) runq->current->flags |= THREAD_RESCHEDULE; } static void thread_sched_ts_add(struct thread_runq *runq, struct thread *thread) { struct thread_ts_runq *ts_runq; ts_runq = runq->ts_runq_active; thread_sched_ts_enqueue(ts_runq, thread); thread_sched_ts_restart(runq); } static unsigned int thread_sched_ts_dequeue_scale(unsigned int group_work, unsigned int old_group_weight, unsigned int new_group_weight) { #ifndef __LP64__ if (likely((group_work < 0x10000) && (new_group_weight < 0x10000))) return DIV_CEIL(group_work * new_group_weight, old_group_weight); #endif /* __LP64__ */ return (unsigned int)DIV_CEIL((unsigned long long)group_work * new_group_weight, old_group_weight); } static void thread_sched_ts_dequeue(struct thread *thread) { struct thread_ts_runq *ts_runq; struct thread_ts_group *group, *tmp; struct list *node, *init_node; unsigned int thread_work, group_work, group_weight; assert(thread->ts_ctx.ts_runq != NULL); ts_runq = thread->ts_ctx.ts_runq; group = &ts_runq->group_array[thread->ts_ctx.weight - 1]; thread->ts_ctx.ts_runq = NULL; list_remove(&thread->ts_ctx.node); group_weight = group->weight - thread->ts_ctx.weight; group_work = thread_sched_ts_dequeue_scale(group->work, group->weight, group_weight); thread_work = group->work - group_work; ts_runq->work -= thread_work; group->work = group_work; ts_runq->weight -= thread->ts_ctx.weight; group->weight -= thread->ts_ctx.weight; if (group->weight == 0) list_remove(&group->node); else { node = list_next(&group->node); init_node = node; while (!list_end(&ts_runq->groups, node)) { tmp = list_entry(node, struct thread_ts_group, node); if (tmp->weight <= group->weight) break; node = list_next(node); } if (node != init_node) { list_remove(&group->node); list_insert_before(node, &group->node); } } } static void thread_sched_ts_start_next_round(struct thread_runq *runq) { struct thread_ts_runq *ts_runq; ts_runq = runq->ts_runq_expired; runq->ts_runq_expired = runq->ts_runq_active; runq->ts_runq_active = ts_runq; if (ts_runq->weight != 0) thread_sched_ts_restart(runq); } static void thread_sched_ts_remove(struct thread_runq *runq, struct thread *thread) { struct thread_ts_runq *ts_runq; ts_runq = thread->ts_ctx.ts_runq; thread_sched_ts_dequeue(thread); if (ts_runq == runq->ts_runq_active) { if (ts_runq->weight == 0) thread_sched_ts_start_next_round(runq); else thread_sched_ts_restart(runq); } } static void thread_sched_ts_deactivate(struct thread_runq *runq, struct thread *thread) { assert(thread->ts_ctx.ts_runq == runq->ts_runq_active); thread_sched_ts_dequeue(thread); thread_sched_ts_enqueue(runq->ts_runq_expired, thread); if (runq->ts_runq_active->weight == 0) thread_sched_ts_start_next_round(runq); } static void thread_sched_ts_put_prev(struct thread_runq *runq, struct thread *thread) { static int unfair = 0; struct thread_ts_runq *ts_runq; struct thread_ts_group *group; ts_runq = runq->ts_runq_active; group = &ts_runq->group_array[thread->ts_ctx.weight - 1]; list_insert_tail(&group->threads, &thread->ts_ctx.node); if (thread->ts_ctx.work >= thread->ts_ctx.weight) { if (likely(!unfair)) if (unlikely(thread->ts_ctx.work > thread->ts_ctx.weight)) { unfair = 1; printk("thread: warning: preemption disabled too long is " "causing scheduling unfairness\n"); } thread_sched_ts_deactivate(runq, thread); } } static int thread_sched_ts_ratio_exceeded(struct thread_ts_group *current, struct thread_ts_group *next) { unsigned long long a, b; #ifndef __LP64__ unsigned int ia, ib; if (likely((current->weight < 0x10000) && (next->weight < 0x10000))) { ia = (current->work + 1) * next->weight; ib = (next->work + 1) * current->weight; return ia > ib; } #endif /* __LP64__ */ a = ((unsigned long long)current->work + 1) * next->weight; b = ((unsigned long long)next->work + 1) * current->weight; return a > b; } static struct thread * thread_sched_ts_get_next(struct thread_runq *runq) { struct thread_ts_runq *ts_runq; struct thread_ts_group *group, *next; struct thread *thread; struct list *node; ts_runq = runq->ts_runq_active; if (ts_runq->weight == 0) return NULL; group = ts_runq->current; node = list_next(&group->node); if (list_end(&ts_runq->groups, node)) { node = list_first(&ts_runq->groups); group = list_entry(node, struct thread_ts_group, node); } else { next = list_entry(node, struct thread_ts_group, node); if (thread_sched_ts_ratio_exceeded(group, next)) group = next; else { node = list_first(&ts_runq->groups); group = list_entry(node, struct thread_ts_group, node); } } ts_runq->current = group; thread = list_first_entry(&group->threads, struct thread, ts_ctx.node); list_remove(&thread->ts_ctx.node); return thread; } static void thread_sched_ts_tick(struct thread_runq *runq, struct thread *thread) { struct thread_ts_runq *ts_runq; struct thread_ts_group *group; ts_runq = runq->ts_runq_active; ts_runq->work++; group = &ts_runq->group_array[thread->ts_ctx.weight - 1]; group->work++; thread->flags |= THREAD_RESCHEDULE; thread->ts_ctx.work++; } static void thread_sched_idle_init_thread(struct thread *thread, unsigned short priority) { (void)thread; (void)priority; } static void __noreturn thread_sched_idle_panic(void) { panic("thread: only idle threads are allowed in the idle class"); } static void thread_sched_idle_add(struct thread_runq *runq, struct thread *thread) { (void)runq; (void)thread; thread_sched_idle_panic(); } static void thread_sched_idle_remove(struct thread_runq *runq, struct thread *thread) { (void)runq; (void)thread; thread_sched_idle_panic(); } static void thread_sched_idle_put_prev(struct thread_runq *runq, struct thread *thread) { (void)runq; (void)thread; } static struct thread * thread_sched_idle_get_next(struct thread_runq *runq) { return runq->idler; } static void thread_sched_idle_tick(struct thread_runq *runq, struct thread *thread) { (void)runq; (void)thread; } void __init thread_bootstrap(void) { size_t i; for (i = 0; i < ARRAY_SIZE(thread_runqs); i++) thread_runq_init(&thread_runqs[i]); tcb_set_current(&thread_idlers[0].tcb); } void __init thread_ap_bootstrap(void) { tcb_set_current(&thread_idlers[cpu_id()].tcb); } void __init thread_setup(void) { struct thread_sched_ops *ops; thread_policy_table[THREAD_SCHED_POLICY_FIFO] = THREAD_SCHED_CLASS_RT; thread_policy_table[THREAD_SCHED_POLICY_RR] = THREAD_SCHED_CLASS_RT; thread_policy_table[THREAD_SCHED_POLICY_TS] = THREAD_SCHED_CLASS_TS; thread_policy_table[THREAD_SCHED_POLICY_IDLE] = THREAD_SCHED_CLASS_IDLE; ops = &thread_sched_ops[THREAD_SCHED_CLASS_RT]; ops->init_thread = thread_sched_rt_init_thread; ops->add = thread_sched_rt_add; ops->remove = thread_sched_rt_remove; ops->put_prev = thread_sched_rt_put_prev; ops->get_next = thread_sched_rt_get_next; ops->tick = thread_sched_rt_tick; ops = &thread_sched_ops[THREAD_SCHED_CLASS_TS]; ops->init_thread = thread_sched_ts_init_thread; ops->add = thread_sched_ts_add; ops->remove = thread_sched_ts_remove; ops->put_prev = thread_sched_ts_put_prev; ops->get_next = thread_sched_ts_get_next; ops->tick = thread_sched_ts_tick; ops = &thread_sched_ops[THREAD_SCHED_CLASS_IDLE]; ops->init_thread = thread_sched_idle_init_thread; ops->add = thread_sched_idle_add; ops->remove = thread_sched_idle_remove; ops->put_prev = thread_sched_idle_put_prev; ops->get_next = thread_sched_idle_get_next; ops->tick = thread_sched_idle_tick; kmem_cache_init(&thread_cache, "thread", sizeof(struct thread), CPU_L1_SIZE, NULL, NULL, NULL, 0); kmem_cache_init(&thread_stack_cache, "thread_stack", STACK_SIZE, DATA_ALIGN, NULL, NULL, NULL, 0); } static void thread_main(void) { struct thread *thread; assert(!cpu_intr_enabled()); assert(!thread_preempt_enabled()); spinlock_unlock(&thread_runq_local()->lock); cpu_intr_enable(); thread_preempt_enable(); thread = thread_self(); thread->fn(thread->arg); /* TODO Thread destruction */ for (;;) cpu_idle(); } static void thread_init_sched(struct thread *thread, unsigned short priority) { thread_sched_ops[thread->sched_class].init_thread(thread, priority); } /* * This function initializes most thread members. * * It leaves the cpu member uninitialized. */ static void thread_init(struct thread *thread, void *stack, const struct thread_attr *attr, void (*fn)(void *), void *arg) { const char *name; struct task *task; tcb_init(&thread->tcb, stack, thread_main); if (attr == NULL) attr = &thread_default_attr; task = (attr->task == NULL) ? thread_self()->task : attr->task; assert(task != NULL); name = (attr->name == NULL) ? task->name : attr->name; assert(name != NULL); assert(attr->sched_policy < THREAD_NR_SCHED_POLICIES); /* * The expected interrupt, preemption and run queue lock state when * dispatching a thread is : * - interrupts disabled * - preemption disabled * - run queue locked * * Locking the run queue increases the preemption counter once more, * making its value 2. */ thread->flags = 0; thread->state = THREAD_SLEEPING; thread->pinned = 0; thread->preempt = 2; thread->on_rq = 0; thread->sched_policy = attr->sched_policy; thread->sched_class = thread_policy_table[attr->sched_policy]; thread_init_sched(thread, attr->priority); thread->task = task; thread->stack = stack; strlcpy(thread->name, name, sizeof(thread->name)); thread->fn = fn; thread->arg = arg; task_add_thread(task, thread); } int thread_create(struct thread **threadp, const struct thread_attr *attr, void (*fn)(void *), void *arg) { struct thread *thread; void *stack; int error; thread = kmem_cache_alloc(&thread_cache); if (thread == NULL) { error = ERROR_NOMEM; goto error_thread; } stack = kmem_cache_alloc(&thread_stack_cache); if (stack == NULL) { error = ERROR_NOMEM; goto error_stack; } thread_init(thread, stack, attr, fn, arg); /* TODO Multiprocessor thread dispatching */ thread->cpu = cpu_id(); thread_wakeup(thread); *threadp = thread; return 0; error_stack: kmem_cache_free(&thread_cache, thread); error_thread: return error; } void thread_sleep(void) { thread_self()->state = THREAD_SLEEPING; thread_schedule(); } void thread_wakeup(struct thread *thread) { struct thread_runq *runq; unsigned long on_rq, flags; /* TODO Multiprocessor thread dispatching */ assert(thread->cpu == cpu_id()); on_rq = atomic_cas(&thread->on_rq, 0, 1); if (on_rq) return; thread->state = THREAD_RUNNING; thread_pin(); runq = thread_runq_local(); spinlock_lock_intr_save(&runq->lock, &flags); thread_runq_add(runq, thread); spinlock_unlock_intr_restore(&runq->lock, flags); thread_unpin(); thread_reschedule(); } static void thread_idler(void *arg) { (void)arg; for (;;) cpu_idle(); } static void __init thread_setup_idler(void) { char name[THREAD_NAME_SIZE]; struct thread_attr attr; struct thread *idler; unsigned int cpu; void *stack; stack = kmem_cache_alloc(&thread_stack_cache); if (stack == NULL) panic("thread: unable to allocate idler thread stack"); /* * Having interrupts enabled was required to allocate the stack, but * at this stage, the idler thread is still the current thread, so disable * interrupts while initializing it. */ cpu_intr_disable(); cpu = cpu_id(); snprintf(name, sizeof(name), "thread_idler/%u", cpu); attr.task = kernel_task; attr.name = name; attr.sched_policy = THREAD_SCHED_POLICY_IDLE; idler = &thread_idlers[cpu]; thread_init(idler, stack, &attr, thread_idler, NULL); idler->state = THREAD_RUNNING; idler->cpu = cpu; } void __init thread_run(void) { struct thread_runq *runq; struct thread *thread; assert(cpu_intr_enabled()); /* This call disables interrupts */ thread_setup_idler(); runq = thread_runq_local(); spinlock_lock(&runq->lock); thread = thread_runq_get_next(thread_runq_local()); /* * Locking the run queue increased the preemption counter to 3. * Artificially reduce it to the expected value. */ thread_preempt_enable_no_resched(); if (thread->task != kernel_task) pmap_load(thread->task->map->pmap); tcb_load(&thread->tcb); } static inline void thread_switch(struct thread *prev, struct thread *next) { if ((prev->task != next->task) && (next->task != kernel_task)) pmap_load(next->task->map->pmap); tcb_switch(&prev->tcb, &next->tcb); } void thread_schedule(void) { struct thread_runq *runq; struct thread *prev, *next; unsigned long flags; assert(thread_preempt_enabled()); prev = thread_self(); do { thread_preempt_disable(); runq = thread_runq_local(); spinlock_lock_intr_save(&runq->lock, &flags); prev->flags &= ~THREAD_RESCHEDULE; thread_runq_put_prev(runq, prev); if (prev->state != THREAD_RUNNING) { thread_runq_remove(runq, prev); atomic_swap(&prev->on_rq, 0); } next = thread_runq_get_next(runq); if (prev != next) { /* * That's where the true context switch occurs. The next thread * must unlock the run queue and reenable preemption. */ thread_switch(prev, next); /* * When dispatched again, the thread might have been moved to * another processor. */ runq = thread_runq_local(); } spinlock_unlock_intr_restore(&runq->lock, flags); thread_preempt_enable_no_resched(); } while (prev->flags & THREAD_RESCHEDULE); } void thread_reschedule(void) { if ((thread_self()->flags & THREAD_RESCHEDULE) && thread_preempt_enabled()) thread_schedule(); } void thread_tick(void) { struct thread_runq *runq; struct thread *thread; assert(!cpu_intr_enabled()); assert(!thread_preempt_enabled()); runq = thread_runq_local(); thread = thread_self(); spinlock_lock(&runq->lock); thread_sched_ops[thread->sched_class].tick(runq, thread); spinlock_unlock(&runq->lock); }