Threads
Struct thread
The main Pintos data structure for threads is struct thread
, declared in threads/thread.h
.
Structure: 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.
Notes
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 |
| : |
| : |
| 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 basestruct 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()
orpalloc_get_page()
instead (see section Memory Allocation).
Thread Functions
threads/thread.c
implements several public functions for thread support. Let's take a look at the most useful:
Thread Switching
schedule()
is responsible for switching threads.
It is internal to
threads/thread.c
and called only by the three public thread functions that need to switch threads:thread_block()
,thread_exit()
, andthread_yield()
.Before any of these functions call
schedule()
, they disable interrupts (or ensure that they are already disabled) and then change the running thread's state to something other than running.
Schedule()
schedule()
is short but tricky.
It records the current thread in local variable
cur
,determines the next thread to run as local variable next (by calling
next_thread_to_run()
),and then calls
switch_threads()
to do the actual thread switch.The thread we switched to was also running inside
switch_threads()
, as are all the threads not currently running, so the new thread now returns out ofswitch_threads()
, returning the previously running thread.switch_threads()
is an assembly language routine inthreads/switch.S
.It saves registers on the stack, saves the CPU's current stack pointer in the current
struct thread
'sstack
member.It restores the new thread's
stack
into the CPU's stack pointer, restores registers from the stack, and returns.
The rest of the scheduler is implemented in
thread_schedule_tail()
.It marks the new thread as running.
If the thread we just switched from is in the dying state, then it also frees the page that contained the dying thread's
struct thread
and stack.These couldn't be freed prior to the thread switch because the switch needed to use it.
Run a Thread for the First Time
Running a thread for the first time is a special case.
When thread_create()
creates a new thread, it goes through a fair amount of trouble to get it started properly. In particular, the new thread hasn't started running yet, so there's no way for it to be running inside switch_threads()
as the scheduler expects. To solve the problem, thread_create()
creates some _fake stack frames_ in the new thread's stack:
The topmost fake stack frame is for
switch_threads()
, represented bystruct switch_threads_frame
. The important part of this frame is itseip
member, the return address. We pointeip
toswitch_entry()
, indicating it to be the function that calledswitch_entry()
.The next fake stack frame is for
switch_entry()
, an assembly language routine in threads/switch.S that adjusts the stack pointer, callsthread_schedule_tail()
(this special case is whythread_schedule_tail()
is separate fromschedule()
), and returns. We fill in its stack frame so that it returns intokernel_thread()
, a function inthreads/thread.c
.The final stack frame is for
kernel_thread()
, which enables interrupts and calls the thread's function (the function passed tothread_create()
). If the thread's function returns, it callsthread_exit()
to terminate the thread.
The following is the stack page layout of a thread created by thread_create()
and scheduled for the first time. If you find some problems, feel free to contact us.
4 kB +---------------------------------+
| aux |
| function |
| eip (NULL) | kernel_thread_frame
+---------------------------------+
| eip (to kernel_thread) | switch_entry_frame
+---------------------------------+
| next |
| cur |
| eip (to switch_entry) |
| ebx |
| ebp |
| esi |
| edi | switch_threads_frame
+---------------------------------+
| kernel stack |
| | |
| | |
| V |
| grows downward |
sizeof (struct thread) +---------------------------------+
| magic |
| : |
| : |
| name |
| status |
0 kB +---------------------------------+
Last updated