Technical Women

036
Figure 1. Shafrira Goldwasser

Today

  • Finish wait()/exit().

  • Resource multiplexing—specifically, the CPU.

  • Operating system privilege.

  • Kernel-user boundary crossing via:

    • hardware interrupts,

    • software interrupts,

    • and software exceptions.

Our Simple Shell

Disclaimer: this is C-like pseudo-code. It will not compile or run! (But it’s not far off.)

while (1) {
  input = readLine();
  returnCode = fork();
  if (returnCode == 0) {
    exec(input);
  }
}

exec() Challenges

  • The most challenging part of exec() is making sure that, on failure, exec() can return to the calling process!

    • Can’t make destructive changes to the parent’s address space until we are sure that things will success.

    • Of course, the process is just an abstraction anyway and that provides a lot of flexibility: can prepare a separate address space and just swap it in when we’re done.

exit() # End of Life Issues

  • What’s missing here? Death!

  • Processes choose the moment of their own end by calling exit().

  • As we discussed earlier a processes passes an exit code to the exit() function.

  • What happens to this exit code?

wait() # The Afterlife

  • When a process calls exit() the kernel holds the exit code, which can be retrieved by the exiting child’s parent.

  • The parent retrieves this exit code by calling wait(), the last of the primary process-related system calls.

    • And the one that stubbornly refuses to fit into my lifecycle metaphor.

wait()/exit()

  • We often consider wait() and exit() together, since they combine to remove any trace of a process from the system.

  • Until a process both calls exit() and has its exit code collected via wait() traces of it remain on the system:

    • Its return code is retained by the kernel.

    • Its process ID (or PID) is also retained. Why?

  • Processes that have exit()ed but not had their exit code collected are called zombies. (Ooh, scary!)

wait()/exit()

  • wait()/exit() also present an interesting synchronization problem you will solve for ASST2.

    • Calls to wait() (by the parent) and exit() (by the child) may interleave in the kernel.

    • You must guarantee that the parent can retrieve the exit code successfully.

wait()/exit() Issues

  • What happens if a process’s parent exits before it does?

    • The "orphaned" process is assigned the init process as a parent, which will collect its exit code when it exits. Referred to as reparenting.

  • How do we prevent zombies from taking over the machine?

    • A processes parent receives the SIGCHLD signal when a child calls exit(), alerting it to the chance to retrieve the child’s exit status.

    • On some systems a process can choose to have its children automatically reaped by ignoring this signal.

    • On bash the relevant command is the appropriately-named disown. This allows children to continue running as daemons even after bash exits.

What If I Don’t Want to wait()?

  • Parent may want to peek at the exit status of its child, just to check on it. (Are you dead yet? Are you dead yet?)

  • Systems support a non-blocking wait() for this purpose:

    • Blocking wait() will block until the child exits, unless it has already exited in which case it returns immediately.

    • Non-Blocking wait() will not block. Instead, its return status indicates if the child has exited and, if so, what the exit code was.

Our Simple Shell

  • Disclaimer: this is C-like pseudo-code. It will not compile or run! (But it’s not far off.)

while (1) {
  input = readLine();
  returnCode = fork();
  if (returnCode == 0) {
    exec(input);
  } else {
    wait(returnCode);
  }
}

Aside: errno

Where’s exit()?
  • There is potential confusion between kernel system calls and wrappers implemented by libc:

    • _exit() (system call) v. exit() (C library function call).

  • The C library wraps system calls and changes their return codes.

  • The C library is what sets errno, not the kernel.

Questions About Processes?

Multiplexing and Abstracting the CPU

For the next several weeks we’ll be looking at how the operating system manages the processor:

  • What are the limitations or problems with the hardware resource that the operating system is trying to address? There is only one (or at least, no that many) processor(s)!

  • What are the mechanisms necessary to allow the processor to be shared? Interrupts and context switching.

  • What are the consequences for programmers of processor multiplexing? Concurrency and synchronization.

  • How do we design good policies ensuring that processor sharing meets the needs of the user? Processor scheduling.

Today: Operating System Privilege

  • Earlier we alluded to the fact that the operating system is like a normal program with some special privileges.

  • In fact, implementing most of the process-related system calls we discussed last week does not require these special privileges!

    • If you don’t believe me, look at user-space threading libraries. They provide functionality very similar to the fork(), exec(), exit() and wait() system calls we discussed.

  • So why does the operating system need special privileges?

Multiplexing Requires Privilege

  • In many cases implementing abstractions does not require special privileges.

  • However, the operating systems other task—multiplexing resources—does.

Multiplexing Requires Privilege

  • In order to divide resources between processes the system needs a trusted and privileged entity that can:

    • divide the resources, and

    • enforce the division.

No Trusto Processo

  • Why can’t processes share resources without a privileged arbiter?

  • Some processes are:

    • malicious—"Hey, I’d like some more memory, so I’ll use yours!"

    • buggy—"Um, is this my memory or your memory? I’m not sure but I’ll just use it and hope things turn out OK…​"

Privileged Execution

  • CPUs implement a mechanism allowing the operating system to manage resources: kernel (or privileged) mode.

  • Being in kernel mode may mean that the executing code

    • has access to special instructions, and

    • has a different view of memory.

Special Instructions

  • When the CPU is in kernel mode there are special instructions that can be executed.

    • These instructions usually modify important global state controlling how resources are shared.

  • When the CPU is not in kernel mode it does not allow these instructions to be executed.

    • We will see what happens when an unprivileged process tries to execute a privileged instruction in a minute.

Protection Boundaries

  • The goal:

    • only trusted kernel code runs in kernel mode;

    • untrusted user code always runs in user mode.

  • The CPU implements mechanisms to transition between user and kernel mode which we will discuss during the rest of today’s class.

Aside: Fine-Grained Protection

  • Many modern CPUs implement more than two protection modes.

  • x86 processors actually have four protection "rings" from Ring 0 (most privileged) to Ring 3 (least privileged).

  • For many years operating systems running on x86 architectures only used Ring 0 (kernel mode) and Ring 3 (user mode).

  • Recently this has become more interesting because of operating system virtualization, so we will return to this.

    • But for now, you can think of processors as having two privilege modes: kernel mode and user mode.

Terminology

  • When we say "application" we refer to code running without privileges or in unprivileged or "user" mode.

  • When we say "kernel" we mean code running in privileged or kernel mode.

  • What makes the kernel special? It is the one application allowed to executed code in kernel mode!

Bootstrapping Privilege

Why is the operating system allowed to run in kernel mode?
  • You installed your machine that way! This is what it means to install an operating system: choose a particular application to grant special privileges to.

  • On boot the CPU starts out executing the kernel code in privileged mode, which is how privilege is bootstrapped.

  • The kernel is responsible for lowering the privilege level before executing user code.

More Terminology: Traps

  • When a normal application does something that causes the system to enter kernel mode we sometimes refer to this as trapping into the kernel.

  • I frequently think about the thread that trapped into the kernel as running in the kernel after the trap occurs.

    • On some level this is accurate: it is the same stream of instructions.

    • On some level this is not accurate: the kernel thread has its own stack and has saved the state of the trapping user thread, so in a way the user thread has been paused while the kernel performs some task on its behalf.

  • Decide the way to think about this that is the most effective for you.

Privilege Transitions

  • The transition into the kernel or into privileged mode typically occurs for one of three reasons:

    • a hardware device requests attention—hardware interrupt

    • software requests attention—software interrupt or system call

    • software needs attention—software exception

  • What is the difference between requesting and needing attention?

Hardware Interrupts

  • Hardware interrupts are used to signal that a particular device needs attention:

    • a disk read completed, or

    • a network packet was received, or

    • a timer fired.

  • Processors implement multiple interrupt lines, input wires on which a logic transition (or level) will trigger an interrupt.

Interrupt Handling

When an interrupt is triggered (interrupt request, or IRQ), the processor:
  1. enters privileged mode,

  2. records state necessary to process the interrupt,

  3. jumps to a pre-determined memory location and begins executing instructions.

The instructions that the processor executes when an interrupt fires are called the interrupt service routine (ISR).

Next Time

  • Software interrupts and exceptions.