oskit/oskit-20020317/threads/pthread_scheduler.c

675 lines
16 KiB
C
Raw Permalink Normal View History

2016-02-19 15:02:31 +03:00
/*
* Copyright (c) 1996, 1998-2001 University of Utah and the Flux Group.
* All rights reserved.
*
* This file is part of the Flux OSKit. The OSKit is free software, also known
* as "open source;" you can redistribute it and/or modify it under the terms
* of the GNU General Public License (GPL), version 2, as published by the Free
* Software Foundation (FSF). To explore alternate licensing terms, contact
* the University of Utah at csl-dist@cs.utah.edu or +1-801-585-3271.
*
* The OSKit 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 GPL for more details. You should have
* received a copy of the GPL along with the OSKit; see the file COPYING. If
* not, write to the FSF, 59 Temple Place #330, Boston, MA 02111-1307, USA.
*/
#ifdef DEFAULT_SCHEDULER
/*
* Default POSIX scheduler.
*/
#include <threads/pthread_internal.h>
#include "pthread_schedconf.h"
#include "pthread_signal.h"
#include "sched_realtime.h"
#ifdef HIGHRES_THREADTIMES
#include "machine/hirestime.h"
#endif
/*
* The scheduler lock is used elsewhere. It must always be taken with
* interrupts disabled since it is used within interrupt handlers to
* change the run queue.
*/
pthread_lock_t pthread_sched_lock = PTHREAD_LOCK_INITIALIZER;
#ifdef SCHED_STATS
static struct pthread_gstats stats;
void dump_scheduler_stats();
#endif
#ifdef THREAD_STATS
static struct pthread_history pthread_stat_history[PTHREAD_HISTORY_SIZE];
static struct pthread_history *php = pthread_stat_history;
void dump_thread_stats();
#endif
static oskit_pthread_csw_hook_t aftercsw_hook;
/*
* Initialize whatever schedulers are linked in.
*/
void
pthread_init_scheduler(void)
{
int i, okay = 0;
for (i = 0; i < nschedulers; i++) {
if (scheduler_array[i].init) {
scheduler_array[i].init();
okay++;
}
}
if (!okay)
panic(__FUNCTION__ ": No schedulers linked in!");
#ifdef SCHED_STATS
atexit(dump_scheduler_stats);
#endif
#ifdef THREAD_STATS
atexit(dump_thread_stats);
#endif
}
/*
* This handles the details of finding another thread to switch to.
* Return value to indicate whether a switch actually happened.
*/
static inline int
SCHED_DISPATCH(resched_flags_t reason, pthread_thread_t *pthread)
{
assert(pthread->scheduler);
return pthread->scheduler->dispatch(reason, pthread);
}
int
pthread_sched_dispatch(resched_flags_t reason, pthread_thread_t *pnext)
{
pthread_thread_t *pthread = CURPTHREAD();
int rerun, preemptable;
#ifdef SCHED_STATS
stat_stamp_t before;
#endif
save_preemption_enable(preemptable);
assert_interrupts_disabled();
#ifdef SCHED_STATS
before = STAT_STAMPGET();
#endif
/*
* Going to muck with the scheduler queues, so take that lock.
*/
pthread_lock(&pthread_sched_lock);
/*
* Decide what to do with the current thread.
*/
rerun = SCHED_DISPATCH(reason, pthread);
/*
* Clear preemption flag
*/
pthread->preempt = PREEMPT_NEEDED = 0;
/*
* See if we need to keep running the same thread.
*/
if (rerun) {
if (pnext) {
#ifdef THREAD_STATS
pnext->stats.qstamp = STAT_STAMPGET();
#endif
pthread_unlock(&pnext->lock);
rerun = SCHED_DISPATCH(RESCHED_YIELD, pnext);
assert(!rerun);
}
#ifdef THREAD_STATS
pthread->stats.rescheds++;
#endif
pthread_unlock(&pthread_sched_lock);
pthread_unlock(&pthread->schedlock);
/*
* Deliver pending signals as long as we have the
* the opportunity to do so.
*/
SIGCHECK(pthread);
return 0;
}
/*
* Now find a thread to run.
*/
if (pnext) {
/*
* Locked thread was provided, so done with the scheduler.
*/
pthread_unlock(&pthread_sched_lock);
}
else {
int i;
/*
* Ask all schedulers in order. Looks bad, but not really.
*/
for (i = 0; i < nschedulers; i++) {
if (scheduler_array[i].thread_next &&
(pnext = scheduler_array[i].thread_next()))
break;
}
/*
* Nothing to run, so idle.
*/
if (!pnext) {
#ifdef THREADS_DEBUG
pthread_lock(&threads_sleepers_lock);
if (! threads_sleepers)
panic("RESCHED: "
"Nothing to run. Might be deadlock!");
pthread_unlock(&threads_sleepers_lock);
#endif
pnext = IDLETHREAD;
}
pthread_unlock(&pthread_sched_lock);
/*
* Avoid switch into same thread.
*/
if (pnext == pthread) {
#ifdef SCHED_STATS
stats.switchessame++;
stats.switchsame_cycles += STAT_STAMPDIFF(before);
#endif
#ifdef THREAD_STATS
pthread->stats.rescheds++;
#endif
pthread_unlock(&pthread->schedlock);
/*
* Deliver pending signals as long as we have the
* the opportunity to do so.
*/
SIGCHECK(pthread);
return 0;
}
pthread_lock(&pnext->schedlock);
}
#ifdef SCHED_STATS
stats.switches++;
stats.switch_cycles += STAT_STAMPDIFF(before);
#endif
#ifdef HIGHRES_THREADTIMES
{
long long sstamp;
pthread_hirestime_get(&sstamp);
pthread->cpucycles += (sstamp - pthread->rstamp);
pthread_hirestime_get(&pnext->rstamp);
}
#endif
#ifdef THREAD_STATS
/* XXX somewhat redundent with HIRES_THREADTIMES */
{
stat_time_t t;
/* XXX idle thread is never actually queued */
if (pthread->runq.next != 0 || pthread == IDLETHREAD) {
pthread->stats.qstamp = STAT_STAMPGET();
pthread->stats.preempts++;
} else {
pthread->stats.blocks++;
}
t = STAT_STAMPDIFF(pthread->stats.rstamp);
pthread->stats.rtime += t;
if (t < pthread->stats.rmin)
pthread->stats.rmin = t;
if (t > pthread->stats.rmax)
pthread->stats.rmax = t;
t = STAT_STAMPDIFF(pnext->stats.qstamp);
pnext->stats.qtime += t;
if (t < pnext->stats.qmin)
pnext->stats.qmin = t;
if (t > pnext->stats.qmax)
pnext->stats.qmax = t;
pnext->stats.scheds++;
pnext->stats.rstamp = STAT_STAMPGET();
}
#endif
#ifdef PTHREAD_SCHED_REALTIME
if (SCHED_POLICY_REALTIME(pnext->policy))
ENTER_REALTIME_MODE();
else
ENTER_STANDARD_MODE();
#endif
/*
* Switch to next thread. The schedlock for the current thread
* is released in the context switch code, while the lock for
* next thread is carried across the context switch and is released
* once the switch is committed.
*/
thread_switch(pnext, &pthread->schedlock, CURPTHREAD());
/* Must set the current thread pointer! */
SETCURPTHREAD(pthread);
/* Call the after context switch hook. */
if (aftercsw_hook) {
(*aftercsw_hook)();
}
/*
* Thread switch is complete. Unlock the schedlock and proceed.
*/
pthread_unlock(&pthread->schedlock);
/*
* Look for exceptions before letting it return to whatever it
* was doing before it yielded.
*/
if (pthread->sleeprec) {
/*
* Thread is returning to an osenv_sleep. The thread must
* be allowed to return, even if it was killed, so the driver
* state can be cleaned up. The death will be noticed later.
*/
goto done;
}
/*
* These flag bits are protected by the pthread lock.
*/
pthread_lock(&pthread->lock);
if ((pthread->flags &
(THREAD_KILLED|THREAD_EXITING)) == THREAD_KILLED) {
/*
* Thread was canceled. Time to actually reap it, but only
* if its not already in the process of exiting.
*
* XXX: The problem is if the process lock is still
* held. Since the rest of the oskit is not set up to
* handle cancelation of I/O operations, let threads
* continue to run until the process lock is released.
* At that point the death will be noticed.
*/
if (! osenv_process_locked()) {
pthread_exit_locked((void *) PTHREAD_CANCELED);
/*
* Never returns
*/
}
else {
pthread_unlock(&pthread->lock);
goto done;
}
}
pthread_unlock(&pthread->lock);
/*
* Look for signals. Deliver them in the context of the thread.
*/
SIGCHECK(pthread);
/* Need to really *restore* the flag since we are in a new context */
PREEMPT_ENABLE = preemptable;
return 1;
done:
/* Need to really *restore* the flag since we are in a new context */
PREEMPT_ENABLE = preemptable;
return 1;
}
/*
* Here starts the external interface functions.
*/
/*
* Generic switch code. Find a new thread to run and switch to it.
*/
int
pthread_sched_reschedule(resched_flags_t reason, pthread_lock_t *plock)
{
pthread_thread_t *pthread = CURPTHREAD();
int enabled, rc;
enabled = save_disable_interrupts();
/*
* Lock the thread schedlock. This provides atomicity with respect
* to the switch, since another CPU will will not be able to switch
* into a thread until it is completely switched out someplace else.
* Once the schedlock is taken, the provided lock can be released,
* since the thread can now be woken up, although it cannot be
* switched into for the reason just given.
*/
assert(plock != &(pthread->lock));
pthread_lock(&(pthread->schedlock));
if (plock)
pthread_unlock(plock);
rc = pthread_sched_dispatch(reason, 0);
restore_interrupt_enable(enabled);
return rc;
}
/*
* Directed switch. The current thread is blocked with the waitstate.
*/
void
pthread_sched_handoff(int waitstate, pthread_thread_t *pnext)
{
pthread_thread_t *pthread = CURPTHREAD();
int enabled;
enabled = save_disable_interrupts();
if (waitstate) {
pthread_lock(&pthread->waitlock);
pthread->waitflags |= waitstate;
pthread_lock(&pthread->schedlock);
pthread_lock(&pnext->schedlock);
pthread_unlock(&pthread->waitlock);
pthread_sched_dispatch(RESCHED_BLOCK, pnext);
}
else {
pthread_lock(&pthread->schedlock);
pthread_lock(&pnext->schedlock);
pthread_sched_dispatch(RESCHED_YIELD, pnext);
}
restore_interrupt_enable(enabled);
}
/*
* Initialize schedule parameters for a thread. Called when thread is first
* created. No locking needed cause no side effects.
*/
void
pthread_sched_init_schedstate(pthread_thread_t *pthread,
int policy, const struct sched_param *param)
{
int i;
pthread->scheduler = 0;
for (i = 0; i < nschedulers; i++) {
if (scheduler_array[i].schedules &&
scheduler_array[i].schedules(policy)) {
pthread->scheduler = &scheduler_array[i];
break;
}
}
if (!pthread->scheduler)
panic(__FUNCTION__ ": Invalid schedule policy!");
pthread->policy = policy;
pthread->scheduler->init_schedstate(pthread, param);
}
/*
* Place a thread that was blocked, back on the runq. The pthread lock
* is *not* taken. This routine is called from interrupt level to
* reschedule threads waiting in timed conditions or sleep.
*
* Return a boolean value indicating whether the current thread is still
* the thread that should be running.
*/
int
pthread_sched_setrunnable(pthread_thread_t *pthread)
{
int enabled, resched;
#ifdef SCHED_STATS
stat_stamp_t before;
#endif
enabled = save_disable_interrupts();
#ifdef SCHED_STATS
before = STAT_STAMPGET();
#endif
pthread_lock(&pthread_sched_lock);
assert(pthread->scheduler);
resched = pthread->scheduler->setrunnable(pthread);
pthread_unlock(&pthread_sched_lock);
#ifdef SCHED_STATS
stats.wakeups++;
stats.wakeup_cycles += STAT_STAMPDIFF(before);
#endif
#ifdef THREAD_STATS
pthread->stats.qstamp = STAT_STAMPGET();
#endif
restore_interrupt_enable(enabled);
return resched;
}
/*
* Change the state of a thread. In this case, its the priority and
* policy that are possibly being changed. Return an indicator to the
* caller, if the change requires a reschedule.
*
* Currently, the priority inheritance code is not SMP safe. Also, it is
* specific to this scheduler module.
*
* Does it make sense to allow a thread to change between POSIX and
* REALTIME? Probably not, but we support it for now.
*/
int
pthread_sched_change_state(pthread_thread_t *pthread,
int policy, const struct sched_param *param)
{
int resched = 0;
assert_interrupts_enabled();
disable_interrupts();
pthread_lock(&pthread->lock);
assert(pthread->scheduler);
if (policy != -1 &&
pthread->policy != policy) {
int i;
pthread->scheduler->disassociate(pthread);
pthread->scheduler = 0;
for (i = 0; i < nschedulers; i++) {
if (scheduler_array[i].schedules &&
scheduler_array[i].schedules(policy))
pthread->scheduler = &scheduler_array[i];
break;
}
if (!pthread->scheduler)
panic(__FUNCTION__ ": Invalid schedule policy!");
pthread->policy = policy;
pthread->scheduler->init_schedstate(pthread, param);
resched = 1;
}
else
resched = pthread->scheduler->change_state(pthread, param);
pthread_unlock(&pthread->lock);
enable_interrupts();
return resched;
}
/*
* Install the context switch hook. Only one hook can be set.
* Returns previous hook function.
*/
oskit_pthread_csw_hook_t
oskit_pthread_aftercsw_hook_set(oskit_pthread_csw_hook_t hook)
{
oskit_pthread_csw_hook_t ohook = aftercsw_hook;
aftercsw_hook = hook;
return ohook;
}
#ifdef SIMPLE_PRI_INHERIT
/*
* The target thread is holding a resource (well, a mutex), and the
* the current thread wants it, and may or may not be a higher priority.
* If the current thread *is* higher priority, transfer its priority
* to the target. When the target unlocks the mutex, its priority will
* be restored.
*
* You cannot transfer *to* a realtime thread.
*/
void
pthread_sched_priority_transfer_and_wait(pthread_thread_t *target,
pthread_lock_t *plock)
{
pthread_thread_t *pthread = CURPTHREAD();
assert_interrupts_enabled();
disable_interrupts();
pthread_lock(&target->lock);
if (pthread->priority > target->priority) {
#if 0
printf("SIMPLE_PRI_TRANSFER: "
"0x%x(%p) 0x%x(%p) P:%d --> P:%d\n",
(int) pthread, pthread->tid,
(int) target, target->tid,
pthread->priority, target->priority);
#endif
assert(target->scheduler);
target->scheduler->priority_bump(target, pthread->priority);
}
pthread_unlock(&target->lock);
enable_interrupts();
pthread_sched_reschedule(RESCHED_BLOCK, plock);
}
/*
* Restore thread priority.
*/
void
pthread_sched_priority_transfer_undo(pthread_thread_t *pthread)
{
int enabled = save_disable_interrupts();
pthread_lock(&pthread->lock);
if (pthread->priority != pthread->base_priority) {
#if 0
printf("SIMPLE_PRI_TRANSFER: "
"0x%x(%p) P:%d --> P:%d\n",
(int) pthread, pthread->tid,
pthread->priority, pthread->base_priority);
#endif
assert(pthread->scheduler);
pthread->scheduler->priority_bump(pthread,
pthread->base_priority);
}
pthread_unlock(&pthread->lock);
restore_interrupt_enable(enabled);
}
#endif /* SIMPLE_PRI_INHERIT */
#ifdef SCHED_STATS
void
dump_scheduler_stats()
{
printf("Wakeups: %d\n", stats.wakeups);
printf("Wakeup Cycles %lu\n", stats.wakeup_cycles);
printf("Switches: %d\n", stats.switches);
printf("Switch Cycles %lu\n", stats.switch_cycles);
printf("Switch to same: %d\n", stats.switchessame);
printf("Switch to same Cycles %lu\n", stats.switchsame_cycles);
}
#endif
#ifdef THREAD_STATS
void
save_thread_stats(pthread_thread_t *pthread)
{
php->tid = pthread->tid;
php->priority = pthread->priority;
php->stats = pthread->stats;
if (++php == &pthread_stat_history[PTHREAD_HISTORY_SIZE])
php = pthread_stat_history;
}
void
dump_thread_stats()
{
struct pthread_history *p;
pthread_thread_t *pt;
int i;
#ifdef LATENCY_THREAD
extern pthread_thread_t *hiprio;
extern unsigned long hiprio_missed, hiprio_delayed;
pthread_thread_t *hpt = hiprio;
#endif
#ifdef LATENCY_THREAD
hiprio = 0;
printf("Hi-priority thread:\n");
printf("T(%d): scheds %d (%lu/%lu/%lu), delayed %lu, missed %lu\n",
hpt->priority, hpt->stats.scheds,
hpt->stats.qtime, hpt->stats.qmin, hpt->stats.qmax,
hiprio_delayed, hiprio_missed);
#endif
printf("Idle thread:\n");
pt = IDLETHREAD;
printf("T(%d): scheds %d (%lu/%lu/%lu), "
"rescheds %d, preempts %d, blocks %d\n",
pt->priority, pt->stats.scheds,
pt->stats.qtime, pt->stats.qmin, pt->stats.qmax,
pt->stats.rescheds, pt->stats.preempts,
pt->stats.blocks);
printf("Current threads:\n");
for (i = 0; i < THREADS_MAX_THREAD; i++) {
if ((pt = threads_tidtothread[i]) == 0 ||
pt == IDLETHREAD)
continue;
#ifdef LATENCY_THREAD
if (pt == hpt)
continue;
#endif
printf("T%d(%d): scheds %d (%lu/%lu/%lu), "
"rescheds %d, preempts %d, blocks %d\n",
(int)pt->tid, pt->priority, pt->stats.scheds,
pt->stats.qtime, pt->stats.qmin, pt->stats.qmax,
pt->stats.rescheds, pt->stats.preempts,
pt->stats.blocks);
}
printf("Past threads:\n");
p = php;
do {
if (p->tid != 0)
printf("T%d(%d): scheds %d (%lu/%lu/%lu), "
"rescheds %d, preempts %d, blocks %d\n",
(int)p->tid, p->priority, p->stats.scheds,
p->stats.qtime, p->stats.qmin, p->stats.qmax,
p->stats.rescheds, p->stats.preempts,
p->stats.blocks);
if (++p == &pthread_stat_history[PTHREAD_HISTORY_SIZE])
p = pthread_stat_history;
} while (p != php);
}
#endif
#endif /* DEFAULT_SCHEDULER */