X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fthreads%2Fthread.c;h=fc358de69a4fe6a3cab2dff5dc594d43515d242c;hb=e49318880e6420e9b5a4ae9ffb986b49f89798e0;hp=c9bc8ee2bb8c9fc75bfc799c10e615fd033a2fe3;hpb=c9d103e3fc1f398acb10bbaa66bf5a61318d26e9;p=pintos-anon diff --git a/src/threads/thread.c b/src/threads/thread.c index c9bc8ee..fc358de 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -1,24 +1,38 @@ -#include "thread.h" +#include "threads/thread.h" +#include #include -#include "debug.h" -#include "interrupt.h" -#include "intr-stubs.h" -#include "lib.h" -#include "gdt.h" -#include "mmu.h" -#include "palloc.h" -#include "random.h" -#include "switch.h" - -#define THREAD_MAGIC 0x1234abcdu +#include +#include +#include +#include "threads/flags.h" +#include "threads/interrupt.h" +#include "threads/intr-stubs.h" +#include "threads/mmu.h" +#include "threads/palloc.h" +#include "threads/switch.h" +#include "threads/synch.h" +#ifdef USERPROG +#include "userprog/addrspace.h" +#include "userprog/gdt.h" +#endif + +/* Random value for struct thread's `magic' member. + Used to detect stack overflow. See the big comment at the top + of thread.h for details. */ +#define THREAD_MAGIC 0xcd6abf4b /* List of processes in THREAD_READY state, that is, processes that are ready to run but not actually running. */ -static struct list run_queue; +static struct list ready_list; /* Idle thread. */ -static struct thread *idle_thread; /* Thread. */ -static void idle (void *aux UNUSED); /* Thread function. */ +static struct thread *idle_thread; + +/* Initial thread, the thread running init.c:main(). */ +static struct thread *initial_thread; + +/* Lock used by allocate_tid(). */ +static struct lock tid_lock; /* Stack frame for kernel_thread(). */ struct kernel_thread_frame @@ -30,62 +44,82 @@ struct kernel_thread_frame static void kernel_thread (thread_func *, void *aux); +static void idle (void *aux UNUSED); +static struct thread *running_thread (void); static struct thread *next_thread_to_run (void); -static struct thread *new_thread (const char *name); -static bool is_thread (struct thread *t); -static void *alloc_frame (struct thread *t, size_t size); -static void destroy_thread (struct thread *t); +static struct thread *new_thread (const char *name, int priority); +static void init_thread (struct thread *, const char *name, int priority); +static bool is_thread (struct thread *); +static void *alloc_frame (struct thread *, size_t size); +static void destroy_thread (struct thread *); static void schedule (void); void schedule_tail (struct thread *prev); +static tid_t allocate_tid (void); + +/* Initializes the threading system by transforming the code + that's currently running into a thread. Note that this is + possible only because the loader was careful to put the bottom + of the stack at a page boundary; it won't work in general. + Also initializes the run queue. -/* Initializes the threading system. After calling, create some - threads with thread_create() or thread_execute(), then start - the scheduler with thread_start(). */ + After calling this function, be sure to initialize the page + allocator before trying to create any threads with + thread_create(). */ void thread_init (void) { ASSERT (intr_get_level () == INTR_OFF); - /* Initialize run queue. */ - list_init (&run_queue); + lock_init (&tid_lock, "tid"); - /* Create idle thread. */ - idle_thread = thread_create ("idle", idle, NULL); - idle_thread->status = THREAD_BLOCKED; + /* Set up a thread structure for the running thread. */ + initial_thread = running_thread (); + init_thread (initial_thread, "main", PRI_DEFAULT); + initial_thread->status = THREAD_RUNNING; + initial_thread->tid = allocate_tid (); + + /* Initialize run queue. */ + list_init (&ready_list); } -/* Starts the thread scheduler. The caller should have created - some threads with thread_create() or thread_execute(). Never - returns to the caller. */ +/* Starts preemptive thread scheduling by enabling interrupts. + Also creates the idle thread. */ void thread_start (void) { - struct thread *t = next_thread_to_run (); - if (t->status == THREAD_READY) - list_remove (&t->rq_elem); - t->status = THREAD_RUNNING; - switch_threads (NULL, t); - - NOT_REACHED (); + thread_create ("idle", PRI_DEFAULT, idle, NULL); + intr_enable (); } -/* Creates a new kernel thread named NAME, which executes - FUNCTION passing AUX as the argument, and adds it to the ready - queue. If thread_start() has been called, then the new thread - may be scheduled before thread_create() returns. Use a - semaphore or some other form of synchronization if you need to - ensure ordering. */ -struct thread * -thread_create (const char *name, thread_func *function, void *aux) +/* Creates a new kernel thread named NAME with the given initial + PRIORITY, which executes FUNCTION passing AUX as the argument, + and adds it to the ready queue. If thread_start() has been + called, then the new thread may be scheduled before + thread_create() returns. It could even exit before + thread_create() returns. Use a semaphore or some other form + of synchronization if you need to ensure ordering. Returns + the thread identifier for the new thread, or TID_ERROR if + creation fails. + + The code provided sets the new thread's `priority' member to + PRIORITY, but no actual priority scheduling is implemented. + Priority scheduling is the goal of Problem 1-3. */ +tid_t +thread_create (const char *name, int priority, + thread_func *function, void *aux) { struct thread *t; struct kernel_thread_frame *kf; struct switch_entry_frame *ef; struct switch_threads_frame *sf; + tid_t tid; ASSERT (function != NULL); - t = new_thread (name); + t = new_thread (name, priority); + if (t == NULL) + return TID_ERROR; + tid = t->tid; /* Stack frame for kernel_thread(). */ kf = alloc_frame (t, sizeof *kf); @@ -102,106 +136,61 @@ thread_create (const char *name, thread_func *function, void *aux) sf->eip = switch_entry; /* Add to run queue. */ - thread_wake (t); - - return t; -} - -#ifdef USERPROG -/* Starts a new thread running a user program loaded from - FILENAME, and adds it to the ready queue. If thread_start() - has been called, then new thread may be scheduled before - thread_execute() returns.*/ -bool -thread_execute (const char *filename) -{ - struct thread *t; - struct intr_frame *if_; - struct switch_entry_frame *ef; - struct switch_threads_frame *sf; - void (*start) (void); - - ASSERT (filename != NULL); - - t = new_thread (filename); - if (t == NULL) - return false; - - if (!addrspace_load (t, filename, &start)) - PANIC ("%s: program load failed", filename); - - /* Interrupt frame. */ - if_ = alloc_frame (t, sizeof *if_); - if_->es = SEL_UDSEG; - if_->ds = SEL_UDSEG; - if_->eip = start; - if_->cs = SEL_UCSEG; - if_->eflags = FLAG_IF | FLAG_MBS; - if_->esp = PHYS_BASE; - if_->ss = SEL_UDSEG; - - /* Stack frame for switch_entry(). */ - ef = alloc_frame (t, sizeof *ef); - ef->eip = intr_exit; - - /* Stack frame for switch_threads(). */ - sf = alloc_frame (t, sizeof *sf); - sf->eip = switch_entry; - - /* Add to run queue. */ - thread_wake (t); + thread_unblock (t); - return true; + return tid; } -#endif -/* Transitions T from its current state to THREAD_READY, the - ready-to-run state. On entry, T must be ready or blocked. +/* Transitions a blocked thread T from its current state to the + ready-to-run state. This is an error if T is not blocked. (Use thread_yield() to make the running thread ready.) */ void -thread_wake (struct thread *t) +thread_unblock (struct thread *t) { + enum intr_level old_level; + ASSERT (is_thread (t)); - ASSERT (t->status == THREAD_READY || t->status == THREAD_BLOCKED); - if (t->status != THREAD_READY) - { - list_push_back (&run_queue, &t->rq_elem); - t->status = THREAD_READY; - } + + old_level = intr_disable (); + ASSERT (t->status == THREAD_BLOCKED); + list_push_back (&ready_list, &t->elem); + t->status = THREAD_READY; + intr_set_level (old_level); } -/* Returns the name of thread T. */ +/* Returns the name of the running thread. */ const char * -thread_name (struct thread *t) +thread_name (void) { - ASSERT (is_thread (t)); - return t->name; + return thread_current ()->name; } -/* Returns the running thread. */ +/* Returns the running thread. + This is running_thread() plus a couple of sanity checks. + See the big comment at the top of thread.h for details. */ struct thread * thread_current (void) { - uint32_t *esp; - struct thread *t; - - /* Copy the CPU's stack pointer into `esp', and then round that - down to the start of a page. Because `struct thread' is - always at the beginning of a page and the stack pointer is - somewhere in the middle, this locates the curent thread. */ - asm ("movl %%esp, %0\n" : "=g" (esp)); - t = pg_round_down (esp); - + struct thread *t = running_thread (); + /* Make sure T is really a thread. - If this assertion fires, then your thread may have - overflowed its stack. Each thread has less than 4 kB of - stack, so a few big automatic arrays or moderate recursion - can cause stack overflow. */ + If either of these assertions fire, then your thread may + have overflowed its stack. Each thread has less than 4 kB + of stack, so a few big automatic arrays or moderate + recursion can cause stack overflow. */ ASSERT (is_thread (t)); + ASSERT (t->status == THREAD_RUNNING); return t; } +/* Returns the running thread's tid. */ +tid_t +thread_tid (void) +{ + return thread_current ()->tid; +} + /* Deschedules the current thread and destroys it. Never returns to the caller. */ void @@ -209,6 +198,8 @@ thread_exit (void) { ASSERT (!intr_context ()); + /* Just set our status to dying and schedule another process. + We will be destroyed during the call to schedule_tail(). */ intr_disable (); thread_current ()->status = THREAD_DYING; schedule (); @@ -226,16 +217,20 @@ thread_yield (void) ASSERT (!intr_context ()); old_level = intr_disable (); - list_push_back (&run_queue, &cur->rq_elem); + list_push_back (&ready_list, &cur->elem); cur->status = THREAD_READY; schedule (); intr_set_level (old_level); } /* Puts the current thread to sleep. It will not be scheduled - again until awoken by thread_wake(). */ + again until awoken by thread_unblock(). + + This function must be called with interrupts turned off. It + is usually a better idea to use one of the synchronization + primitives in synch.h. */ void -thread_sleep (void) +thread_block (void) { ASSERT (!intr_context ()); ASSERT (intr_get_level () == INTR_OFF); @@ -248,16 +243,17 @@ thread_sleep (void) static void idle (void *aux UNUSED) { + idle_thread = thread_current (); + for (;;) { - /* Wait for an interrupt. */ - DEBUG (idle, "idle"); - asm ("hlt"); - /* Let someone else run. */ intr_disable (); - thread_sleep (); + thread_block (); intr_enable (); + + /* Use CPU `hlt' instruction to wait for interrupt. */ + asm ("hlt"); } } @@ -272,6 +268,20 @@ kernel_thread (thread_func *function, void *aux) thread_exit (); /* If function() returns, kill the thread. */ } +/* Returns the running thread. */ +struct thread * +running_thread (void) +{ + uint32_t *esp; + + /* Copy the CPU's stack pointer into `esp', and then round that + down to the start of a page. Because `struct thread' is + always at the beginning of a page and the stack pointer is + somewhere in the middle, this locates the curent thread. */ + asm ("movl %%esp, %0\n" : "=g" (esp)); + return pg_round_down (esp); +} + /* Returns true if T appears to point to a valid thread. */ static bool is_thread (struct thread *t) @@ -279,28 +289,39 @@ is_thread (struct thread *t) return t != NULL && t->magic == THREAD_MAGIC; } -/* Creates a new thread named NAME and initializes its fields. - Returns the new thread if successful or a null pointer on - failure. */ +/* Creates a new thread named NAME as a child of the running + thread. Returns the new thread if successful or a null + pointer on failure. */ static struct thread * -new_thread (const char *name) +new_thread (const char *name, int priority) { - struct thread *t; - - ASSERT (name != NULL); - - t = palloc_get (PAL_ZERO); - if (t != NULL) + struct thread *t = palloc_get (PAL_ZERO); + if (t != NULL) { - strlcpy (t->name, name, sizeof t->name); - t->stack = (uint8_t *) t + PGSIZE; - t->status = THREAD_BLOCKED; - t->magic = THREAD_MAGIC; + init_thread (t, name, priority); + t->tid = allocate_tid (); } - + return t; } +/* Does basic initialization of T as a blocked thread named + NAME. */ +static void +init_thread (struct thread *t, const char *name, int priority) +{ + ASSERT (t != NULL); + ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); + ASSERT (name != NULL); + + memset (t, 0, sizeof *t); + t->status = THREAD_BLOCKED; + strlcpy (t->name, name, sizeof t->name); + t->stack = (uint8_t *) t + PGSIZE; + t->priority = priority; + t->magic = THREAD_MAGIC; +} + /* Allocates a SIZE-byte frame at the top of thread T's stack and returns a pointer to the frame's base. */ static void * @@ -322,25 +343,24 @@ alloc_frame (struct thread *t, size_t size) static struct thread * next_thread_to_run (void) { - if (list_empty (&run_queue)) + if (list_empty (&ready_list)) return idle_thread; else - return list_entry (list_pop_front (&run_queue), struct thread, rq_elem); + return list_entry (list_pop_front (&ready_list), struct thread, elem); } -/* Destroys T, which must be in the dying state and must not be - the running thread. */ +/* Destroys T, which must not be the running thread. */ static void destroy_thread (struct thread *t) { ASSERT (is_thread (t)); - ASSERT (t->status == THREAD_DYING); ASSERT (t != thread_current ()); #ifdef USERPROG addrspace_destroy (t); #endif - palloc_free (t); + if (t != initial_thread) + palloc_free (t); } /* Completes a thread switch by activating the new thread's page @@ -358,17 +378,23 @@ destroy_thread (struct thread *t) void schedule_tail (struct thread *prev) { - struct thread *cur = thread_current (); + struct thread *cur = running_thread (); ASSERT (intr_get_level () == INTR_OFF); + /* Mark us as running. */ cur->status = THREAD_RUNNING; - if (prev != NULL && prev->status == THREAD_DYING) - destroy_thread (prev); #ifdef USERPROG - addrspace_activate (cur); + /* Activate the new address space. */ + addrspace_activate (); #endif + + /* If the thread we switched from is dying, destroy it. + This must happen late because it's not a good idea to + e.g. destroy the page table you're currently using. */ + if (prev != NULL && prev->status == THREAD_DYING) + destroy_thread (prev); } /* Schedules a new process. At entry, interrupts must be off and @@ -378,18 +404,31 @@ schedule_tail (struct thread *prev) static void schedule (void) { - struct thread *cur = thread_current (); + struct thread *cur = running_thread (); struct thread *next = next_thread_to_run (); + struct thread *prev = NULL; ASSERT (intr_get_level () == INTR_OFF); ASSERT (cur->status != THREAD_RUNNING); ASSERT (is_thread (next)); if (cur != next) - { - struct thread *prev = switch_threads (cur, next); - schedule_tail (prev); - } + prev = switch_threads (cur, next); + schedule_tail (prev); +} + +/* Returns a tid to use for a new thread. */ +static tid_t +allocate_tid (void) +{ + static tid_t next_tid = 1; + tid_t tid; + + lock_acquire (&tid_lock); + tid = next_tid++; + lock_release (&tid_lock); + + return tid; } /* Offset of `stack' member within `struct thread'.