-
Armin Luntzer authoredArmin Luntzer authored
core.c 7.19 KiB
/**
* @file kernel/sched/core.c
*
* @brief the core scheduling code
*/
#include <kernel/err.h>
#include <kernel/kthread.h>
#include <kernel/sched.h>
#include <kernel/init.h>
#include <kernel/tick.h>
#include <asm-generic/irqflags.h>
#include <asm-generic/spinlock.h>
#include <asm/switch_to.h>
#include <string.h>
#include <asm/leon.h>
#define MSG "SCHEDULER: "
static LIST_HEAD(kernel_schedulers);
/* XXX: per-cpu */
extern struct thread_info *current_set[];
ktime sched_last_time;
uint32_t sched_ev;
void kthread_lock(void);
void kthread_unlock(void);
void schedule(void)
{
struct scheduler *sched;
struct task_struct *next = NULL;
struct task_struct *prev = NULL;
struct task_struct *current;
int64_t slot_ns = 1000000LL;
int64_t wake_ns = 1000000000;
ktime rt;
ktime now;
static int once[2];
if (!once[leon3_cpuid()]) {
// tick_set_mode(TICK_MODE_PERIODIC);
tick_set_next_ns(1e9); /* XXX default to 1s ticks initially */
once[leon3_cpuid()] = 1;
return;
}
arch_local_irq_disable();
// if (leon3_cpuid() != 0)
// printk("cpu %d\n", leon3_cpuid());
/* get the current task for this CPU */
/* XXX leon3_cpuid() should be smp_cpu_id() arch call*/
current = current_set[leon3_cpuid()]->task;
// if (leon3_cpuid() != 0)
// if (current)
// printk("current %s\n", current->name);
now = ktime_get();
rt = ktime_sub(now, current->exec_start);
/** XXX need timeslice_update callback for schedulers */
/* update remaining runtime of current thread */
current->runtime = ktime_sub(current->runtime, rt);
current->total = ktime_add(current->total, rt);
current->state = TASK_RUN;
// current->runtime = 0;
retry:
next = NULL;
wake_ns = 1000000000;
/* XXX: for now, try to wake up any threads not running
* this is a waste of cycles and instruction space; should be
* done in the scheduler's code (somewhere) */
kthread_lock();
list_for_each_entry(sched, &kernel_schedulers, node)
sched->wake_next_task(&sched->tq, now);
kthread_unlock();
/* XXX need sorted list: highest->lowest scheduler priority, e.g.:
* EDF -> RMS -> FIFO -> RR
* TODO: scheduler priority value
*/
list_for_each_entry(sched, &kernel_schedulers, node) {
/* if one of the schedulers have a task which needs to run now,
* next is non-NULL
*/
next = sched->pick_next_task(&sched->tq, now);
#if 0
if (next)
printk("next %s %llu %llu\n", next->name, next->first_wake, ktime_get());
else
printk("NULL %llu\n", ktime_get());
#endif
/* check if we need to limit the next tasks timeslice;
* since our scheduler list is sorted by scheduler priority,
* only update the value if wake_next is not set;
*
* because our schedulers are sorted, this means that if next
* is set, the highest priority scheduler will both tell us
* whether it has another task pending soon. If next is not set,
* a lower-priority scheduler may set the next thread to run,
* but we will take the smallest timeout from high to low
* priority schedulers, so we enter this function again when
* the timeslice of the next thread is over and we can determine
* what needs to be run in the following scheduling cycle. This
* way, we will distribute CPU time even to the lowest priority
* scheduler, if available, but guarantee, that the highest
* priority threads are always ranked and executed on time
*
* we assume that the timeslice is reasonable; if not fix it in
* the corresponding scheduler
*/
if (next) {
#if 0
if (next->on_cpu != KTHREAD_CPU_AFFINITY_NONE) {
if (next->on_cpu != leon3_cpuid()) {
// printk("%s on_cpu: %d but am %d\n", next->name, next->on_cpu, leon3_cpuid());
if (prev == next)
continue;
prev = next;
next = NULL;
goto retry2;
}
// else
// printk("yay %s on_cpu: %d and am %d\n", next->name, next->on_cpu, leon3_cpuid());
}
if (next->sched) {
#endif
slot_ns = next->sched->timeslice_ns(next);
#if 0
if (slot_ns < 0)
printk("<0 ! %s\n", next->name);
}
else continue;
#endif
/* we found something to execute, off we go */
break;
}
}
if (!next) {
/* there is absolutely nothing nothing to do, check again later */
tick_set_next_ns(wake_ns);
goto exit;
}
// if (leon3_cpuid() != 0)
// printk("next %s\n", next->name);
/* see if the remaining runtime in a thread is smaller than the wakeup
* timeout. In this case, we will restrict ourselves to the remaining
* runtime. This is particularly needeed for strictly periodic
* schedulers, e.g. EDF
*/
wake_ns = sched->task_ready_ns(&sched->tq, now);
if (wake_ns > 0)
if (wake_ns < slot_ns)
slot_ns = wake_ns;
/* ALWAYS get current time here */
next->exec_start = ktime_get();
/* subtract readout overhead */
tick_set_next_ns(ktime_sub(slot_ns, 1000LL));
#if 1
if (slot_ns < 19000UL) {
printk("wake %lld slot %lld %s\n", wake_ns, slot_ns, next->name);
now = ktime_get();
goto retry;
BUG();
}
sched_ev++;
sched_last_time = ktime_add(sched_last_time, ktime_delta(ktime_get(), now));
#endif
prepare_arch_switch(1);
switch_to(next);
exit:
arch_local_irq_enable();
}
/**
* @brief enqueue a task
*/
int sched_enqueue(struct task_struct *task)
{
if (!task->sched) {
pr_err(MSG "no scheduler configured for task %s\n", task->name);
return -EINVAL;
}
/** XXX retval **/
if (task->sched->check_sched_attr(&task->attr))
return -EINVAL;
task->sched->enqueue_task(&task->sched->tq, task);
return 0;
}
/**
* @brief set a scheduling attribute for a task
*
* @returns 0 on success, < 0 on error
*
* XXX: should implement list of all threads, so we can use pid_t pid
*
* XXX: no error checking of attr params
*/
int sched_set_attr(struct task_struct *task, struct sched_attr *attr)
{
struct scheduler *sched;
if (!task)
goto error;
if (!attr)
goto error;
list_for_each_entry(sched, &kernel_schedulers, node) {
if (sched->policy == attr->policy) {
memcpy(&task->attr, attr, sizeof(struct sched_attr));
if (sched->check_sched_attr(attr))
goto error;
task->sched = sched;
/* XXX other stuff */
return 0;
}
}
pr_crit(MSG "specified policy %d not available\n", attr->policy);
error:
task->sched = NULL;
return -EINVAL;
}
/**
* @brief get a scheduling attribute for a task
* XXX: should implement list of all threads, so we can use pid_t pid
*/
int sched_get_attr(struct task_struct *task, struct sched_attr *attr)
{
if (!task)
return -EINVAL;
if (!attr)
return -EINVAL;
memcpy(attr, &task->attr, sizeof(struct sched_attr));
return 0;
}
/**
* @brief set a task to the default scheduling policy
*/
int sched_set_policy_default(struct task_struct *task)
{
struct sched_attr attr = {.policy = SCHED_RR,
.priority = 1};
return sched_set_attr(task, &attr);
}
/**
* @brief register a new scheduler
*/
int sched_register(struct scheduler *sched)
{
/* XXX locks */
/* XXX stupid */
if (!sched->sched_priority)
list_add_tail(&sched->node, &kernel_schedulers);
else
list_add(&sched->node, &kernel_schedulers);
return 0;
}
/**
* @brief scheduler initcall
*
* @note sets tick mode to oneshot
*/
static int sched_init(void)
{
tick_set_mode(TICK_MODE_ONESHOT);
tick_set_next_ns(1e9); /* XXX default to 1s ticks initially */
return 0;
}
late_initcall(sched_init);