Threads
struct thread
The main Pintos data structure for threads is struct thread
, declared in
threads/thread.h
.
struct thread;
Represents a thread or a user process. In the projects, you will have to add
your own members to struct thread
. You may also change or delete the
definitions of existing members. Every struct thread
occupies the beginning
of its own page of memory. The rest of the page is used for the thread's
stack, which grows downward from the end of the page. It looks like this:
4 kB +---------------------------------+
| kernel stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| |
| |
| |
| |
sizeof (struct thread) +---------------------------------+
| magic |
| intr_frame |
| : |
| : |
| status |
| tid |
0 kB +---------------------------------+
This has two consequences. First, struct thread
must not be allowed to grow
too big. If it does, then there will not be enough room for the kernel stack.
The base struct thread
is only a few bytes in size. It probably should stay
well under 1 kB. Second, kernel stacks must not be allowed to grow too large.
If a stack overflows, it will corrupt the thread state. Thus, kernel functions
should not allocate large structures or arrays as non-static local variables.
Use dynamic allocation with malloc()
or palloc_get_page()
instead
(see Memory Allocation).
tid_t tid;
The thread's thread identifier or tid. Every thread must have a tid that is unique over the entire lifetime of the kernel. By default,
tid_t
is atypedef
forint
and each new thread receives the numerically next higher tid, starting from 1 for the initial process. You can change the type and the numbering scheme if you like.
enum thread_status status;
The thread's state, one of the following:
THREAD_RUNNING
The thread is running. Exactly one thread is running at a given time.
thread_current()
returns the running thread.
THREAD_READY
The thread is ready to run, but it's not running right now. The thread could be selected to run the next time the scheduler is invoked. Ready threads are kept in a doubly linked list called
ready_list
.
THREAD_BLOCKED
The thread is waiting for something, e.g. a lock to become available, an interrupt to be invoked. The thread won't be scheduled again until it transitions to the
THREAD_READY
state with a call tothread_unblock()
. This is most conveniently done indirectly, using one of the Pintos synchronization primitives that block and unblock threads automatically (see Synchronization). There is no a priori way to tell what a blocked thread is waiting for, but a backtrace can help (see Backtraces).
THREAD_DYING
The thread will be destroyed by the scheduler after switching to the next thread.
char name[16];
The thread's name as a string, or at least the first few characters of it.
struct intr_frame tf;
Storing information for the context switching, which includes registers, and stack pointer.
int priority;
A thread priority, ranging from
PRI_MIN
(0) toPRI_MAX
(63). Lower numbers correspond to lower priorities, so that priority 0 is the lowest priority and priority 63 is the highest. Pintos as provided ignores thread priorities, but you will implement priority scheduling in project 1 (see Priority Scheduling).
struct list_elem elem;
A "list element" used to put the thread into doubly linked lists, either
ready_list
(the list of threads ready to run) or a list of threads waiting on a semaphore insema_down()
. It can do double duty because a thread waiting on a semaphore is not ready, and vice versa.
uint64_t *pml4;
Only present in project 2 and later. See Page Tables.
unsigned magic
Always set to
THREAD_MAGIC
, which is just an arbitrary number defined inthreads/thread.c
, and used to detect stack overflow.thread_current()
checks that themagic
member of the running thread'sstruct thread
is set toTHREAD_MAGIC
. Stack overflow tends to change this value, triggering the assertion. For greatest benefit, as you add members tostruct thread
, leavemagic
at the end.
Thread Functions
threads/thread.c
implements several public functions for thread support.
Let's take a look at the most useful:
void thread_init (void);
Called by
main()
to initialize the thread system. Its main purpose is to create astruct thread
for Pintos's initial thread. This is possible because the Pintos loader puts the initial thread's stack at the top of a page, in the same position as any other Pintos thread.Before
thread_init()
runs,thread_current()
will fail because the running thread'smagic
value is incorrect. Lots of functions callthread_current()
directly or indirectly, includinglock_acquire()
for locking a lock, sothread_init()
is called early in Pintos initialization.
void thread_start (void);
Called by
main()
to start the scheduler. Creates the idle thread, that is, the thread that is scheduled when no other thread is ready. Then enables interrupts, which as a side effect enables the scheduler because the scheduler runs on return from the timer interrupt, usingintr_yield_on_return()
.
void thread_tick (void);
Called by the timer interrupt at each timer tick. It keeps track of thread statistics and triggers the scheduler when a time slice expires.
void thread_print_stats (void);
Called during Pintos shutdown to print thread statistics.
tid_t thread_create (const char *name, int priority, thread func *func, void *aux);
Creates and starts a new thread named name with the given priority, returning the new thread's tid. The thread executes func, passing aux as the function's single argument.
thread_create()
allocates a page for the thread'sstruct thread
and stack and initializes its members, then it sets up a set of fake stack frames for it. The thread is initialized in the blocked state, then unblocked just before returning, which allows the new thread to be scheduled.
void thread_func (void *aux);
This is the type of the function passed to
thread_create()
, whose aux argument is passed along as the function's argument.
void thread_block (void);
Transitions the running thread from the running state to the blocked state. The thread will not run again until
thread_unblock()
is called on it, so you'd better have some way arranged for that to happen. Becausethread_block()
is so low-level, you should prefer to use one of the synchronization primitives instead (see Synchronization).
void thread_unblock (struct thread *thread);
Transitions thread, which must be in the blocked state, to the ready state, allowing it to resume running. This is called when the event that the thread is waiting for occurs, e.g. when the lock that the thread is waiting on becomes available.
struct thread *thread_current (void);
Returns the running thread.
tid_t thread_tid (void);
Returns the running thread's thread id. Equivalent to
thread_current ()->tid
.
const char *thread_name (void);
Returns the running thread's name. Equivalent to
thread_current ()->name
.
void thread_exit (void) NO_RETURN;
Causes the current thread to exit. Never returns.
void thread_yield (void);
Yields the CPU to the scheduler, which picks a new thread to run. The new threadmight be the current thread, so you can't depend on this function to keep this thread from running for any particular length of time.
int thread_get_priority (void);
void thread_set_priority (int new_priority);
Stub to set and get thread priority. See Priority Scheduling.
int thread_get_nice (void);
void thread_set_nice (int new_nice);
int thread_get_recent_cpu (void);
int thread_get_load_avg (void);
Stubs for the advanced scheduler.