Annotation of prex-old/sys/kern/sched.c, Revision 1.1
1.1 ! nbrk 1: /*-
! 2: * Copyright (c) 2005-2007, Kohsuke Ohtani
! 3: * All rights reserved.
! 4: *
! 5: * Redistribution and use in source and binary forms, with or without
! 6: * modification, are permitted provided that the following conditions
! 7: * are met:
! 8: * 1. Redistributions of source code must retain the above copyright
! 9: * notice, this list of conditions and the following disclaimer.
! 10: * 2. Redistributions in binary form must reproduce the above copyright
! 11: * notice, this list of conditions and the following disclaimer in the
! 12: * documentation and/or other materials provided with the distribution.
! 13: * 3. Neither the name of the author nor the names of any co-contributors
! 14: * may be used to endorse or promote products derived from this software
! 15: * without specific prior written permission.
! 16: *
! 17: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
! 18: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 19: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 20: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
! 21: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 22: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 23: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 24: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 25: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 26: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 27: * SUCH DAMAGE.
! 28: */
! 29:
! 30: /*
! 31: * sched.c - scheduler
! 32: */
! 33:
! 34: /**
! 35: * General design:
! 36: *
! 37: * The Prex scheduler is based on the algorithm known as priority based
! 38: * multi level queue. Each thread is assigned the priority between
! 39: * 0 and 255. The lower number means higher priority like BSD unix.
! 40: * The scheduler maintains 256 level run queues mapped to each priority.
! 41: * The lowest priority (=255) is used only by an idle thread.
! 42: *
! 43: * All threads have two different types of priorities:
! 44: *
! 45: * - Base priority
! 46: * This is a static priority used for priority computation. A user
! 47: * mode program can change this value via system call.
! 48: *
! 49: * - Current priority
! 50: * An actual scheduling priority. A kernel may adjust this priority
! 51: * dynamically if it's needed.
! 52: *
! 53: * Each thread has one of the following state.
! 54: *
! 55: * - TH_RUN Running or ready to run
! 56: * - TH_SLEEP Sleep for some event
! 57: * - TH_SUSPEND Suspend count is not 0
! 58: * - TH_EXIT Terminated
! 59: *
! 60: * The thread is always preemptive even in the kernel mode.
! 61: * There are following 4 reasons to switch thread.
! 62: *
! 63: * 1) Block
! 64: * Thread is blocked for sleep or suspend.
! 65: * It is put on the tail of the run queue when it becomes
! 66: * runnable again.
! 67: *
! 68: * 2) Preemption
! 69: * If higher priority thread becomes runnable, the current
! 70: * thread is put on the the _head_ of the run queue.
! 71: *
! 72: * 3) Quantum expiration
! 73: * If the thread consumes its time quantum, it is put on
! 74: * the tail of the run queue.
! 75: *
! 76: * 4) Yield
! 77: * If the thread releases CPU by itself, it is put on
! 78: * the tail of the run queue.
! 79: *
! 80: * There are following three types of scheduling policies.
! 81: *
! 82: * - SCHED_FIFO First in-first-out
! 83: * - SCHED_RR Round robin (SCHED_FIFO + timeslice)
! 84: * - SCHED_OTHER Another scheduling (not supported)
! 85: */
! 86:
! 87: #include <kernel.h>
! 88: #include <queue.h>
! 89: #include <event.h>
! 90: #include <irq.h>
! 91: #include <thread.h>
! 92: #include <timer.h>
! 93: #include <vm.h>
! 94: #include <task.h>
! 95: #include <system.h>
! 96: #include <sched.h>
! 97:
! 98: static struct queue runq[NR_PRIOS]; /* run queues */
! 99: static struct queue wakeq; /* queue for waking threads */
! 100: static struct queue dpcq; /* queue for DPC threads */
! 101: static int top_prio; /* highest priority in runq */
! 102:
! 103: static struct event dpc_event; /* event for dpc */
! 104:
! 105: /*
! 106: * Search for highest-priority runnable thread.
! 107: */
! 108: static int
! 109: runq_top(void)
! 110: {
! 111: int prio;
! 112:
! 113: for (prio = 0; prio < MIN_PRIO; prio++)
! 114: if (!queue_empty(&runq[prio]))
! 115: break;
! 116: return prio;
! 117: }
! 118:
! 119: /*
! 120: * Put a thread on the tail of the run queue.
! 121: * The rescheduling flag is set if the priority is better than
! 122: * the currently running process.
! 123: */
! 124: static void
! 125: runq_enqueue(thread_t th)
! 126: {
! 127:
! 128: enqueue(&runq[th->prio], &th->link);
! 129: if (th->prio < top_prio) {
! 130: top_prio = th->prio;
! 131: cur_thread->need_resched = 1;
! 132: }
! 133: }
! 134:
! 135: /*
! 136: * Insert a thread to the head of the run queue.
! 137: * We don't change rescheduling flag here because this is called
! 138: * while thread switching.
! 139: */
! 140: static void
! 141: runq_insert(thread_t th)
! 142: {
! 143:
! 144: queue_insert(&runq[th->prio], &th->link);
! 145: if (th->prio < top_prio)
! 146: top_prio = th->prio;
! 147: }
! 148:
! 149: /*
! 150: * Pick up and remove the highest-priority thread from the run
! 151: * queue. At least, an idle thread will be returned because it
! 152: * always residents in the lowest priority queue.
! 153: */
! 154: static thread_t
! 155: runq_dequeue(void)
! 156: {
! 157: queue_t q;
! 158: thread_t th;
! 159:
! 160: q = dequeue(&runq[top_prio]);
! 161: th = queue_entry(q, struct thread, link);
! 162: if (queue_empty(&runq[top_prio]))
! 163: top_prio = runq_top();
! 164: return th;
! 165: }
! 166:
! 167: /*
! 168: * Remove the specified thread from the run queue.
! 169: */
! 170: static void
! 171: runq_remove(thread_t th)
! 172: {
! 173:
! 174: queue_remove(&th->link);
! 175: top_prio = runq_top();
! 176: }
! 177:
! 178: /*
! 179: * Process all pending woken threads.
! 180: * Rescheduling flag may be set.
! 181: * Note: The thread may be still in a suspend state after wakeup.
! 182: */
! 183: static void
! 184: wakeq_flush(void)
! 185: {
! 186: queue_t q;
! 187: thread_t th;
! 188:
! 189: while (!queue_empty(&wakeq)) {
! 190: /*
! 191: * Set a thread runnable.
! 192: */
! 193: q = dequeue(&wakeq);
! 194: th = queue_entry(q, struct thread, link);
! 195: th->sleep_event = 0;
! 196: th->state &= ~TH_SLEEP;
! 197:
! 198: if (th != cur_thread && th->state == TH_RUN)
! 199: runq_enqueue(th);
! 200: }
! 201: }
! 202:
! 203: /*
! 204: * sleep_expire - sleep timer is expired:
! 205: * @arg: thread to unsleep.
! 206: *
! 207: * Wake up the passed thread that is sleeping in sched_tsleep().
! 208: */
! 209: static void
! 210: sleep_expire(void *arg)
! 211: {
! 212:
! 213: sched_unsleep((thread_t)arg, SLP_TIMEOUT);
! 214: }
! 215:
! 216: /*
! 217: * sched_switch - This routine is called to reschedule the CPU.
! 218: *
! 219: * If the scheduling reason is preemption, the current thread
! 220: * will remain at the head of the run queue. So, the thread
! 221: * still has right to run first again among the same priority
! 222: * threads. For other scheduling reason, the current thread is
! 223: * inserted into the tail of the run queue.
! 224: */
! 225: static void
! 226: sched_switch(void)
! 227: {
! 228: thread_t prev, next;
! 229:
! 230: ASSERT(irq_level == 0);
! 231:
! 232: prev = cur_thread;
! 233: if (prev->state == TH_RUN) {
! 234: if (prev->prio > top_prio)
! 235: runq_insert(prev); /* preemption */
! 236: else
! 237: runq_enqueue(prev);
! 238: }
! 239: prev->need_resched = 0;
! 240:
! 241: /*
! 242: * This is the scheduler proper.
! 243: */
! 244: next = runq_dequeue();
! 245: if (next == prev)
! 246: return;
! 247: cur_thread = next;
! 248:
! 249: if (prev->task != next->task)
! 250: vm_switch(next->task->map);
! 251:
! 252: context_switch(&prev->context, &next->context);
! 253: }
! 254:
! 255: /*
! 256: * sched_tsleep - sleep the current thread until a wakeup is
! 257: * performed on the specified event.
! 258: * @timeout: time out value in msec. (0 means no timeout)
! 259: *
! 260: * This routine returns a sleep result. If the thread is woken
! 261: * by sched_wakeup()/sched_wakeone(), it returns 0. Otherwise,
! 262: * it will return the result value which is passed by sched_unsleep().
! 263: * We allow calling sched_sleep() with interrupt disabled.
! 264: *
! 265: * sched_sleep() is also defined as a wrapper macro for sched_tsleep()
! 266: * without timeout.
! 267: * Note that all sleep requests are interruptible with this kernel.
! 268: */
! 269: int
! 270: sched_tsleep(struct event *evt, u_long timeout)
! 271: {
! 272: int s;
! 273:
! 274: ASSERT(irq_level == 0);
! 275: ASSERT(evt);
! 276:
! 277: sched_lock();
! 278: interrupt_save(&s);
! 279: interrupt_disable();
! 280:
! 281: cur_thread->sleep_event = evt;
! 282: cur_thread->state |= TH_SLEEP;
! 283: enqueue(&evt->sleepq, &cur_thread->link);
! 284:
! 285: if (timeout != 0) {
! 286: timer_callout(&cur_thread->timeout, sleep_expire,
! 287: cur_thread, timeout);
! 288: }
! 289: wakeq_flush();
! 290: sched_switch(); /* Sleep here. Zzzz.. */
! 291:
! 292: interrupt_restore(s);
! 293: sched_unlock();
! 294: return cur_thread->sleep_result;
! 295: }
! 296:
! 297: /*
! 298: * sched_wakeup - wake up all threads sleeping on event.
! 299: *
! 300: * A thread can have sleep and suspend state simultaneously.
! 301: * So, the thread does not always run even if it woke up.
! 302: *
! 303: * Since this routine can be called from ISR at interrupt level, it
! 304: * should not touch any data of runq. Otherwise, we must frequently
! 305: * disable interrupts while accessing runq. Thus, this routine will
! 306: * temporary move the waking thread into wakeq, and the thread is
! 307: * moved to runq at more safer time in wakeq_flush().
! 308: *
! 309: * The woken thread will be put on the tail of runq regardless
! 310: * of its policy. If woken threads have same priority, next running
! 311: * thread is selected by FIFO order.
! 312: */
! 313: void
! 314: sched_wakeup(struct event *evt)
! 315: {
! 316: queue_t q;
! 317: thread_t th;
! 318:
! 319: irq_lock();
! 320: while (!queue_empty(&evt->sleepq)) {
! 321: /*
! 322: * Move a sleeping thread to the wake queue.
! 323: */
! 324: q = dequeue(&evt->sleepq);
! 325: th = queue_entry(q, struct thread, link);
! 326: th->sleep_result = 0;
! 327: enqueue(&wakeq, q);
! 328: timer_stop(&th->timeout);
! 329: }
! 330: irq_unlock();
! 331: }
! 332:
! 333: /*
! 334: * sched_wakeone - wake up one thread sleeping for the event.
! 335: *
! 336: * The highest priority thread is woken among sleeping threads.
! 337: * sched_wakeone() returns the thread ID of the woken thread, or
! 338: * NULL if no threads are sleeping.
! 339: */
! 340: thread_t
! 341: sched_wakeone(struct event *evt)
! 342: {
! 343: queue_t head, q;
! 344: thread_t top, th = NULL;
! 345:
! 346: irq_lock();
! 347: head = &evt->sleepq;
! 348: if (!queue_empty(head)) {
! 349: /*
! 350: * Select the highet priority thread in
! 351: * the sleeping threads, and move it to
! 352: * the wake queue.
! 353: */
! 354: q = queue_first(head);
! 355: top = queue_entry(q, struct thread, link);
! 356: while (!queue_end(head, q)) {
! 357: th = queue_entry(q, struct thread, link);
! 358: if (th->prio < top->prio)
! 359: top = th;
! 360: q = queue_next(q);
! 361: }
! 362: queue_remove(&top->link);
! 363: enqueue(&wakeq, &top->link);
! 364: timer_stop(&top->timeout);
! 365: }
! 366: irq_unlock();
! 367: return th;
! 368: }
! 369:
! 370: /*
! 371: * sched_unsleep - cancel sleep.
! 372: *
! 373: * sched_unsleep() removes the specified thread from its sleep
! 374: * queue. The specified sleep result will be passed to the sleeping
! 375: * thread as a return value of sched_tsleep().
! 376: */
! 377: void
! 378: sched_unsleep(thread_t th, int result)
! 379: {
! 380: sched_lock();
! 381: if (th->state & TH_SLEEP) {
! 382: irq_lock();
! 383: queue_remove(&th->link);
! 384: th->sleep_result = result;
! 385: enqueue(&wakeq, &th->link);
! 386: timer_stop(&th->timeout);
! 387: irq_unlock();
! 388:
! 389: }
! 390: sched_unlock();
! 391: }
! 392:
! 393: /*
! 394: * Yield the current processor to another thread.
! 395: *
! 396: * If a thread switching occurs, the current thread will be moved
! 397: * on the tail of the run queue regardless of its policy.
! 398: * Note that the current thread may run immediately again, if no
! 399: * other thread exists in the same priority queue.
! 400: */
! 401: void
! 402: sched_yield(void)
! 403: {
! 404: ASSERT(irq_level == 0);
! 405:
! 406: sched_lock();
! 407:
! 408: if (!queue_empty(&runq[cur_thread->prio]))
! 409: cur_thread->need_resched = 1;
! 410:
! 411: sched_unlock(); /* Switch current thread here */
! 412: }
! 413:
! 414: /*
! 415: * Suspend the specified thread.
! 416: * The scheduler must be locked before calling this routine.
! 417: * Note that the suspend count is handled in thread_suspend().
! 418: */
! 419: void
! 420: sched_suspend(thread_t th)
! 421: {
! 422: ASSERT(cur_thread->lock_count > 0);
! 423:
! 424: if (th->state == TH_RUN) {
! 425: if (th == cur_thread)
! 426: cur_thread->need_resched = 1;
! 427: else
! 428: runq_remove(th);
! 429: }
! 430: th->state |= TH_SUSPEND;
! 431: }
! 432:
! 433: /*
! 434: * Resume the specified thread.
! 435: * The scheduler must be locked before calling this routine.
! 436: */
! 437: void
! 438: sched_resume(thread_t th)
! 439: {
! 440: ASSERT(cur_thread->lock_count > 0);
! 441:
! 442: if (th->state & TH_SUSPEND) {
! 443: th->state &= ~TH_SUSPEND;
! 444: if (th->state == TH_RUN)
! 445: runq_enqueue(th);
! 446: }
! 447: }
! 448:
! 449: /*
! 450: * sched_tick() is called from timer_tick() once every tick.
! 451: * Check quantum expiration, and mark a rescheduling flag.
! 452: * We don't need locking in here.
! 453: */
! 454: void
! 455: sched_tick(void)
! 456: {
! 457:
! 458: cur_thread->total_ticks++;
! 459:
! 460: if (cur_thread->policy == SCHED_RR) {
! 461: if (--cur_thread->ticks_left <= 0) {
! 462: cur_thread->ticks_left = QUANTUM;
! 463: cur_thread->need_resched = 1;
! 464: }
! 465: }
! 466: }
! 467:
! 468: /*
! 469: * Set up stuff for thread scheduling.
! 470: */
! 471: void
! 472: sched_start(thread_t th)
! 473: {
! 474:
! 475: th->state = TH_RUN | TH_SUSPEND;
! 476: th->policy = SCHED_RR;
! 477: th->prio = PRIO_USER;
! 478: th->base_prio = PRIO_USER;
! 479: th->ticks_left = QUANTUM;
! 480: }
! 481:
! 482: /*
! 483: * Stop thread scheduling.
! 484: */
! 485: void
! 486: sched_stop(thread_t th)
! 487: {
! 488: ASSERT(irq_level == 0);
! 489: ASSERT(cur_thread->lock_count > 0);
! 490:
! 491: if (th == cur_thread) {
! 492: /*
! 493: * If specified thread is current thread, the
! 494: * scheduling lock count is force set to 1 to
! 495: * ensure the thread switching in the next
! 496: * sched_unlock().
! 497: */
! 498: cur_thread->lock_count = 1;
! 499: cur_thread->need_resched = 1;
! 500: } else {
! 501: if (th->state == TH_RUN)
! 502: runq_remove(th);
! 503: else if (th->state & TH_SLEEP)
! 504: queue_remove(&th->link);
! 505: }
! 506: timer_stop(&th->timeout);
! 507: th->state = TH_EXIT;
! 508: }
! 509:
! 510: /*
! 511: * sched_lock - lock the scheduler.
! 512: *
! 513: * The thread switch is disabled during scheduler locked. This
! 514: * is mainly used to synchronize the thread execution to protect
! 515: * global resources. Even when scheduler is locked, any interrupt
! 516: * handler can run. So, we have to use irq_lock() to synchronize
! 517: * a global data with ISR.
! 518: *
! 519: * Since the scheduling lock can be nested any number of times,
! 520: * the caller has the responsible to unlock the same number of
! 521: * locks.
! 522: */
! 523: void
! 524: sched_lock(void)
! 525: {
! 526:
! 527: cur_thread->lock_count++;
! 528: }
! 529:
! 530: /*
! 531: * sched_unlock - unlock scheduler.
! 532: *
! 533: * If nobody locks the scheduler anymore, it runs pending wake
! 534: * threads and check the reschedule flag. The thread switch is
! 535: * invoked if the rescheduling request exists.
! 536: *
! 537: * Note that this routine will be called at the end of the
! 538: * interrupt handler.
! 539: */
! 540: void
! 541: sched_unlock(void)
! 542: {
! 543: int s;
! 544:
! 545: ASSERT(cur_thread->lock_count > 0);
! 546:
! 547: interrupt_save(&s);
! 548: interrupt_disable();
! 549:
! 550: if (cur_thread->lock_count == 1) {
! 551: wakeq_flush();
! 552: while (cur_thread->need_resched) {
! 553:
! 554: /* Kick scheduler */
! 555: sched_switch();
! 556:
! 557: /*
! 558: * Now we run pending interrupts which fired
! 559: * during the thread switch. So, we can catch
! 560: * the rescheduling request from such ISRs.
! 561: * Otherwise, the reschedule may be deferred
! 562: * until _next_ sched_unlock() call.
! 563: */
! 564: interrupt_restore(s);
! 565: interrupt_disable();
! 566: wakeq_flush();
! 567: }
! 568: }
! 569: cur_thread->lock_count--;
! 570:
! 571: interrupt_restore(s);
! 572: }
! 573:
! 574: int
! 575: sched_getprio(thread_t th)
! 576: {
! 577:
! 578: return th->prio;
! 579: }
! 580:
! 581: /*
! 582: * sched_setprio - set priority of thread.
! 583: * @base: Base priority
! 584: * @prio: Current priority
! 585: *
! 586: * Thread switch may be invoked here by priority change.
! 587: * Called with scheduler locked.
! 588: */
! 589: void
! 590: sched_setprio(thread_t th, int base, int prio)
! 591: {
! 592:
! 593: th->base_prio = base;
! 594:
! 595: if (th == cur_thread) {
! 596: /*
! 597: * If we change the current thread's priority,
! 598: * rescheduling may be happened.
! 599: */
! 600: th->prio = prio;
! 601: top_prio = runq_top();
! 602: if (prio != top_prio)
! 603: cur_thread->need_resched = 1;
! 604: } else {
! 605: if (th->state == TH_RUN) {
! 606: /*
! 607: * Update the thread priority and adjust the
! 608: * run queue position for new priority.
! 609: */
! 610: runq_remove(th);
! 611: th->prio = prio;
! 612: runq_enqueue(th);
! 613: } else
! 614: th->prio = prio;
! 615: }
! 616: }
! 617:
! 618: int
! 619: sched_getpolicy(thread_t th)
! 620: {
! 621:
! 622: return th->policy;
! 623: }
! 624:
! 625: int
! 626: sched_setpolicy(thread_t th, int policy)
! 627: {
! 628: int err = 0;
! 629:
! 630: switch (policy) {
! 631: case SCHED_RR:
! 632: case SCHED_FIFO:
! 633: th->ticks_left = QUANTUM;
! 634: th->policy = policy;
! 635: break;
! 636: default:
! 637: err = -1;
! 638: break;
! 639: }
! 640: return err;
! 641: }
! 642:
! 643: /*
! 644: * DPC thread
! 645: *
! 646: * This is a kernel thread to process the pending call back request
! 647: * within DPC queue. Each DPC routine is called with the following
! 648: * conditions.
! 649: * - Interrupt is enabled.
! 650: * - Scheduler is unlocked.
! 651: */
! 652: static void
! 653: dpc_thread(u_long unused)
! 654: {
! 655: queue_t q;
! 656: struct dpc *dpc;
! 657:
! 658: for (;;) {
! 659:
! 660: /* Wait until next DPC request. */
! 661: sched_sleep(&dpc_event);
! 662:
! 663: while (!queue_empty(&dpcq)) {
! 664: q = dequeue(&dpcq);
! 665: dpc = queue_entry(q, struct dpc, link);
! 666: dpc->state = DPC_FREE;
! 667:
! 668: interrupt_enable();
! 669: (*dpc->func)(dpc->arg);
! 670: interrupt_disable();
! 671: }
! 672: }
! 673: /* NOTREACHED */
! 674: }
! 675:
! 676: /*
! 677: * Qeueue DPC (Deferred Procedure Call) request
! 678: *
! 679: * Call function at some later time in a DPC priority. This is
! 680: * typically used by device drivers to do the low-priority jobs.
! 681: * This routine can be called from ISR.
! 682: */
! 683: void
! 684: sched_dpc(struct dpc *dpc, void (*func)(void *), void *arg)
! 685: {
! 686: ASSERT(dpc);
! 687: ASSERT(func);
! 688:
! 689: irq_lock();
! 690: dpc->func = func;
! 691: dpc->arg = arg;
! 692: if (dpc->state != DPC_PENDING)
! 693: enqueue(&dpcq, &dpc->link);
! 694: dpc->state = DPC_PENDING;
! 695: sched_wakeup(&dpc_event);
! 696: irq_unlock();
! 697: }
! 698:
! 699: /*
! 700: * Initialize the global scheduler state.
! 701: */
! 702: void
! 703: sched_init(void)
! 704: {
! 705: int i;
! 706:
! 707: for (i = 0; i < NR_PRIOS; i++)
! 708: queue_init(&runq[i]);
! 709:
! 710: queue_init(&wakeq);
! 711: queue_init(&dpcq);
! 712: event_init(&dpc_event, "dpc");
! 713: top_prio = PRIO_IDLE;
! 714: cur_thread->need_resched = 1;
! 715:
! 716: /*
! 717: * Create a DPC thread.
! 718: */
! 719: if (kernel_thread(PRIO_DPC, dpc_thread, 0) == NULL)
! 720: panic("sched_init");
! 721:
! 722: printk("Time slice is %d msec\n", CONFIG_TIME_SLICE);
! 723: }
CVSweb