Threads, Processes, and Dispatching
Lecture Notes for CS 140
Spring 2020
John Ousterhout
- Readings for this topic from Operating Systems: Principles and Practice:
Chapter 4.
Threads and Processes
- Thread: a sequential execution stream
- Executes a series of instructions in order (only one
thing happens at a time).
- Execution state: everything that can affect, or be affected by,
a thread:
- Code, data, registers, call stack, open files,
network connections, time of day, etc.
- Process: one or more threads, along with their execution state.
- Part is shared among all threads in the process
- Part of the process state is private to a thread
- Evolution of operating system process model:
- Early operating systems supported a single process with a
single thread at a time (single tasking). They ran batch
jobs (one user at a time).
- By late 1970's most operating systems were multitasking
systems: they supported multiple processes, but each process
had only a single thread.
- Some early personal computer operating systems used
single-tasking (e.g. MS-DOS), but these systems are almost
unheard of today.
- In the 1990's systems converted to multithreading:
multiple threads within each process.
- Is a process the same as a program?
Dispatching
- Almost all computers today can execute multiple threads simultaneously:
- Each processor chip typically contains multiple cores
- Each core contains a complete CPU capable of executing a thread
- Many modern processors support hyperthreading: each physical
core behaves as if it is actually two cores, so it can run two threads
simultaneously (e.g. execute one thread while the other
is waiting on a cache miss).
- For example, a server might contain 2 Intel processor chips,
each with 12 cores, where each core supports 2-way hyperthreading.
Overall, this server can run 48 threads simultaneously.
- Typically have more threads than cores
- At any given time, most threads do not need to execute (they are
waiting for something).
- OS uses a process control block to keep track
of each process:
- Saved execution state for each thread (saved registers, etc.)
- Scheduling information
- Information about memory used by this process
- Information about open files
- Accounting and other miscellaneous information
- At any given time a thread is in one of 3 states:
- Running
- Blocked: waiting for an event (disk I/O, incoming
network packet, etc.)
- Ready: waiting for CPU time
- Dispatcher: innermost portion of the OS that runs
on each core:
- Let a thread run for a while
- Save its execution state
- Load state of another thread
- Let it run ...
- Context switch: changing the thread currently running
on a core by first saving the state of the old thread, then
loading the state of the new thread.
- What causes the dispatcher to run?
- Traps (events occurring in current thread that cause a
change of control into the operating system):
- System call.
- Error (illegal instruction, addressing violation, etc.).
- Page fault.
- Interrupts (events occurring outside the current thread
that cause a state switch into the operating system):
- Character typed at keyboard.
- Completion of disk operation.
- Timer: to make sure OS eventually gets control.
- The dispatcher is not itself a thread
- It is just code that is invoked to perform the dispatching
function
- How does dispatcher decide which thread to run next (assuming just
one core)?
- Simplest approach: Link together the ready threads into a queue.
Dispatcher grabs first thread from the queue. When
threads become ready, insert at back of queue.
- More complex/powerful: give each thread a priority, organize the queue
according to priority. Or, perhaps have multiple queues,
one for each priority class.
Process Creation
- Basic steps in creating a new process:
- Allocate and initialize process control block.
- Load code and data into memory.
- Create structures for first thread, such as call stack.
- Provide initial values for "saved state" for the thread
- Make thread known to dispatcher; dispatcher "resumes"
to start of new program.
- System calls for process creation in UNIX:
- fork makes copy of current process, with one
thread.
- exec replaces memory with code and data from a
given executable file. Doesn't return ("returns"
to starting point of new program).
- waitpid waits for a given process to exit.
- Example:
int pid = fork();
if (pid == 0) {
/* Child process */
exec("foo");
} else {
/* Parent process */
waitpid(pid, &status, options);
}
- Advantage: can modify process state before calling
exec (e.g. change environment, open files).
- Disadvantage: wasted work (most of forked state gets
thrown away).
- System calls for process creation in Windows:
- CreateProcess combines fork and exec:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
PVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
- Must pass arguments for any state changes between parent
and child.
- WaitForSingleObject waits for a child to complete:
WaitForSingleObject(lpProcessInformation->hProcess,
INFINITE);
- Process creation in Pintos: exec combines UNIX
fork and exec.