Chapter 2: Project 1: Threads 9
2 Project 1: Threads
In this assignment, we give you a minimally functional thread system. Your job is to extend
the functionality of this system to gain a better understanding of synchronization problems.
You will be working primarily in the ‘threads’ directory for this assignment, with some
work in the ‘devices’ directory on the side. Compilation should be done in the ‘threads’
directory.
Before you read the description of this project, you should read all of the following
sections: Chapter 1 [Introduction], page 1, Appendix C [Coding Standards], page 94, Ap-
pendix E [Debugging Tools], page 100, and Appendix F [Development Tools], page 111. You
should at least skim the material from Section A.1 [Pintos Loading], page 57 through Sec-
tion A.5 [Memory Allocation], page 74
, especially Section A.3 [Synchronization], page 64.
To complete this project you will also need to read Appendix B [4.4BSD Scheduler], page 89.
2.1 Background
2.1.1 Underst anding Thre ads
The first step is to read and understand the code for the initial thread system. Pintos
already implements thread creation and thread completion, a simple scheduler to switch
between threads, and synchronization primitives (semaphores, locks, condition variables,
and optimization barriers).
Some of this code might seem slightly mysterious. If you haven’t already compiled and
run the base system, as described in the introduction (see
Chapter 1 [Introduction], page 1),
you should do so now. You can read through parts of the source code to see what’s going
on. If you like, you can add calls to printf() almost anywhere, then rec ompile and run to
see what happens and in what order. You can also run the kernel in a debugger and set
breakpoints at interesting spots, single-step through code and examine data, and so on.
When a thread is created, you are creating a new context to be scheduled. You provide
a function to be run in this context as an argument to thread_create(). The first time
the thread is scheduled and runs, it starts from the beginning of that function and executes
in that context. When the function returns, the thread terminates. Each thread, there-
fore, acts like a mini-program running inside Pintos, with the function passed to thread_
create() acting like main().
At any given time, exactly one thread runs and the rest, if any, b e com e inactive. The
scheduler decides which thread to run next. (If no thread is ready to run at any given time,
then the special “idle” thread, implemented in idle(), runs.) Synchronization primitives
can force context switches when one thread needs to wait for another thread to do something.
The mechanics of a context switch are in ‘threads/switch.S’, which is 80x86 as sembly
code. (You don’t have to understand it.) It saves the state of the currently running thread
and restores the state of the thread we’re switching to.
Using the GDB debugger, slowly trace through a context switch to see what happens
(see Section E.5 [GDB], page 103). You can set a breakpoint on schedule() to start out,