Threads, Processes, and Dispatching

Lecture Notes for CS 140
Spring 2019
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 threads
    • 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.
  • May 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.
  • Note: the dispatcher is not itself a 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 peform the dispatching function
    • Some at interrupt level
    • Some at thread level
  • 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.