/* * Copyright (c) 1996, 1998, 1999 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 CPU_INHERIT /* * Simplified Rate Monotonic scheduler. Can be preemptive or not. */ #include #include #include #include #include #include #include #include #include "hash.h" /* * The scheduler structures are per-scheduler instantiation. * * The runq is a single doubly linked queue, ordered by schedule period. * TIDs are mapped back to the state structure using a key value. */ typedef struct rmsched { queue_head_t runq; /* Runq */ int runq_count; /* Number of ready threads */ short ready; /* Scheduler is ready to go */ short preemptible; /* Scheduler is preemptive */ pthread_t tid; /* Back pointer to thread */ hash_table_t *hashtable; /* Hash TID to pstate */ } rmsched_t; /* * Per-thread state information structure. */ typedef struct rm_state { queue_chain_t runq; /* Queueing element */ pthread_t tid; /* TID of thread */ int period; /* In ms. */ int duration; /* In ms. */ } rm_state_t; #define NULL_RMSTATE ((rm_state_t *) 0) extern int ffs(); void *ratemono_schedloop(void *arg); static void ratemono_canceled(void *arg); static int ratemono_pedantic = 1; /* * Map TID to pstate structure with a hash table. */ static void ratemono_setstate(rmsched_t *psched, pthread_t tid, rm_state_t *pstate) { if (tidhash_add(psched->hashtable, (void *) pstate, tid)) panic("ratemono_setstate: " "hashtable add failed: tid:%d pstate:0x%x", tid, (int) pstate); } static rm_state_t * ratemono_getstate(rmsched_t *psched, pthread_t tid) { rm_state_t *pstate; if ((pstate = (rm_state_t *) tidhash_lookup(psched->hashtable, tid)) == NULL) panic("ratemono_getstate: " "hashtable lookup failed: tid:%d", tid); return pstate; } static void ratemono_remstate(rmsched_t *psched, pthread_t tid) { tidhash_rem(psched->hashtable, tid); } /* * Create a Rate Monotonic scheduler. This creates the thread and makes sure * it gets run so that it exists and is ready to handle scheduling * messages. */ int create_ratemono_scheduler(pthread_t *tid, const pthread_attr_t *attr, int preemptible) { rmsched_t *psched; if ((psched = (rmsched_t *) calloc(1, sizeof(rmsched_t))) == NULL) panic("create_ratemono_scheduler: No more memory"); if (tidhash_create(&psched->hashtable, 0)) panic("create_ratemono_scheduler: Hash Table allocation"); psched->preemptible = preemptible; queue_init(&psched->runq); pthread_create(tid, attr, ratemono_schedloop, (void *) psched); /* * The scheduler has to run. */ while (! psched->ready) oskit_pthread_sleep(1); /* * Back in this thread. Just return. */ return 0; } /* * Initialize the per-thread scheduler state structure. This is called * when a new thread is created. The state structure is then stored * in the key table so that TIDs can be mapped back to the state structure. */ rm_state_t * ratemono_thread_init(rmsched_t *psched, pthread_t tid, int period) { rm_state_t *pstate; if ((pstate = (rm_state_t *) calloc(1, sizeof(rm_state_t))) == NULL) panic("ratemono_thread_init: No more memory"); pstate->tid = tid; pstate->period = period; ratemono_setstate(psched, tid, pstate); return pstate; } /* * Are there any threads on the runq? */ static inline int runq_empty(rmsched_t *psched) { return (psched->runq_count == 0); } /* * Determine if a thread is on the runq. Use the runq.next pointer since * its not doing much else. */ static inline int runq_onrunq(rmsched_t *psched, rm_state_t *pstate) { return (int) pstate->runq.next; } /* * Add and remove threads from the runq. */ /* * Insert into the runq. The queue is ordered by the scheduling period, * with lowest at the beginning. */ static void runq_insert(rmsched_t *psched, rm_state_t *pstate) { queue_head_t *phdr = &(psched->runq); rm_state_t *ptmp; if (queue_empty(phdr)) { queue_enter(phdr, pstate, rm_state_t *, runq); goto done; } queue_iterate(&(psched->runq), ptmp, rm_state_t *, runq) { if (pstate->period <= ptmp->period) { queue_enter_before(phdr, ptmp, pstate, rm_state_t *, runq); goto done; } } queue_enter(phdr, pstate, rm_state_t *, runq); done: psched->runq_count++; } /* * Dequeue highest priority thread, which is actually the first thread * on the runq. */ static rm_state_t * runq_dequeue(rmsched_t *psched) { queue_head_t *phdr = &(psched->runq); rm_state_t *pnext; queue_remove_first(phdr, pnext, rm_state_t *, runq); pnext->runq.next = (queue_entry_t) 0; psched->runq_count--; return pnext; } /* * Remove an arbitrary thread from the runq. */ static inline void runq_remove(rmsched_t *psched, rm_state_t *pstate) { queue_head_t *phdr = &(psched->runq); queue_remove(phdr, pstate, rm_state_t *, runq); pstate->runq.next = (queue_entry_t) 0; psched->runq_count--; } /* * Debug the runq. */ static void runq_check(rmsched_t *psched) { int count; rm_state_t *pstate; count = 0; queue_iterate(&(psched->runq), pstate, rm_state_t *, runq) { count++; if (count > psched->runq_count) panic("ratemono scheduler: Bad runq(%d): 0x%x\n", pthread_self(), psched); } } /* * Debug */ static void rmono_debug(rmsched_t *psched) { rm_state_t *pstate; printf("ratemono(%d):\n", (int) pthread_self()); queue_iterate(&(psched->runq), pstate, rm_state_t *, runq) { printf("0x%x(%d) Period %d\n", (int) pstate, (int) pstate->tid, pstate->period); } } /* * This is the scheduler loop. */ void * ratemono_schedloop(void *arg) { rmsched_t *psched = (rmsched_t *) arg; schedmsg_t msg; rm_state_t *current_thread = 0, *pstate = 0; sched_wakecond_t wakeup_cond = 0; int pedantic, rc; oskit_s32_t timeout; psched->tid = pthread_self(); /* * Must tell the main scheduling code ... */ pthread_sched_become_scheduler(); /* * Preemption means donate with non-zero timeout. */ if (psched->preemptible) timeout = PTHREAD_TICK; else timeout = 0; /* * Cancelation cleanup handler to cleanup resources at exit. */ pthread_cleanup_push(ratemono_canceled, (void *) psched); pedantic = ratemono_pedantic; psched->ready = 1; while (1) { pthread_testcancel(); CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): qcount(%d)\n", psched->tid, psched->runq_count); /* * Consume any pending messages until there are no more. */ if (! pthread_sched_message_recv(&msg, 0)) goto consume; if (CPUDEBUG_ISSET(RATEMONO)) rmono_debug(psched); /* * Find a thread to run. */ if (runq_empty(psched)) current_thread = NULL_RMSTATE; else current_thread = runq_dequeue(psched); /* * If we found a thread, switch into it and wait for * a message. Otherwise, wait for messages to arrive * that indicate something has changed. */ if (current_thread) { CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): pstate 0x%x(%d)\n", psched->tid, (int) current_thread, current_thread->tid); if (pedantic || !runq_empty(psched)) wakeup_cond = WAKEUP_ON_BLOCK; else wakeup_cond = WAKEUP_ON_SWITCH; /* * Donate and check the return condition. */ rc = pthread_sched_donate_wait_recv(current_thread->tid, wakeup_cond, &msg, timeout); CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): Donated: %d %s\n", psched->tid, current_thread->tid, msg_sched_rcodes[rc & ~SCHEDULE_MSGRECV]); switch (rc & ~SCHEDULE_MSGRECV) { case SCHEDULE_NOTREADY: /* * Thread was not ready to recv donation. * Forget about this thread. */ break; case SCHEDULE_BLOCKED: /* Thread blocked, so forget about it. */ break; case SCHEDULE_PREEMPTED: case SCHEDULE_YIELDED: case SCHEDULE_TIMEDOUT: assert(! runq_onrunq(psched, current_thread)); runq_insert(psched, current_thread); break; default: if (rc == SCHEDULE_MSGRECV) runq_insert(psched, current_thread); else panic("ratemono_schedloop: " "Bad return code:%d", rc); break; } /* Back to the top to try again. */ if (! (rc & SCHEDULE_MSGRECV)) continue; } else { /* * No threads to run so block waiting for a message. */ CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): Recv\n",psched->tid); rc = pthread_sched_message_recv(&msg, -1); if (rc == OSKIT_ECANCELED) pthread_exit((void *) PTHREAD_CANCELED); } /* * Process messages. */ consume: /* * Map tid in message to thread state structure. Avoid * lookup if possible. */ assert(msg.tid); if (msg.type != MSG_SCHED_NEWTHREAD) { if (current_thread && msg.tid == current_thread->tid) pstate = current_thread; else pstate = ratemono_getstate(psched, msg.tid); } CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): Message: %s 0x%x(%d)\n", psched->tid, msg_sched_typenames[msg.type], (int) pstate, (pstate ? pstate->tid : 0)); switch (msg.type) { case MSG_SCHED_NEWTHREAD: /* * New thread has joined us. Create a state structure * and add it to the runq. */ pstate = ratemono_thread_init(psched, msg.tid, msg.opaque); /* and add it to the runq */ runq_insert(psched, pstate); break; case MSG_SCHED_UNBLOCK: /* * A thread wants to be rescheduled. */ if (! runq_onrunq(psched, pstate)) runq_insert(psched, pstate); break; case MSG_SCHED_SETSTATE: /* * Thread parameters have been changed. The opaque * value is the thread period, which is the same * as its priority in this scheduling model. */ if (runq_onrunq(psched, pstate)) { runq_remove(psched, pstate); pstate->period = (int) msg.opaque; runq_insert(psched, pstate); } else pstate->period = (int) msg.opaque; break; case MSG_SCHED_EXITED: /* * The thread has exited. */ CPUDEBUG(RATEMONO, "ratemono_schedloop(%d): " "Exit: Thread 0x%x(%d)\n", pthread_self(), (int) pstate, pstate->tid); ratemono_remstate(psched, pstate->tid); free(pstate); break; default: panic("ratemono_schedloop: Bad message: %d 0x%x\n", msg.type, pstate); break; } runq_check(psched); } /* * Never reached. */ pthread_cleanup_pop(1); } /* * Handle async cancel of the scheduler. Cleanup resources before the thread * disappears completely. * * XXX NOT CLEANING UP THREADS! */ void ratemono_canceled(void *arg) { rmsched_t *psched = (rmsched_t *) arg; CPUDEBUG(RATEMONO, "ratemono_terminate: Scheduler exiting:%d\n", pthread_self()); free(psched); } #endif CPU_INHERIT