Threads

Threads, Processes, and Dispatching

Optional 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 supported multitasking: multiple processes could exist at once, 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.

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?

  • Process blocks (invoked explicitly)
  • But what if a thread is executing? OS has lost control.
  • Cooperative multi-tasking
  • Interrupts (alarm clock analogy?)

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 the 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  */
        execv("/bin/ls", argv);
    } 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);