7.9 Process Locking

The lock associated with each process (p->lock) is the most complex lock in xv6. A simple way to think about p->lock is that it must be held while reading or writing any of the following struct proc fields: p->state, p->chan, p->killed, p->xstate, and p->pid. These fields can be used by other processes, or by scheduler threads on other CPUs, so it’s natural that they must be protected by a lock.

However, most uses of p->lock are protecting higher-level aspects of xv6’s process data structures and algorithms. Here’s the full set of things that p->lock does:

  • Along with p->state, it prevents races in allocating proc[] slots for new processes.

  • It conceals a process from view while it is being created or destroyed.

  • It prevents a parent’s wait from collecting a process that has set its state to ZOMBIE but has not yet yielded the CPU.

  • It prevents another CPU’s scheduler from deciding to run a yielding process after it sets its state to RUNNABLE but before it finishes swtch.

  • It ensures that only one CPU’s scheduler decides to run a RUNNABLE processes.

  • It prevents a timer interrupt from causing a process to yield while it is in swtch.

  • Along with the condition lock, it helps prevent wakeup from overlooking a process that is calling sleep but has not finished yielding the CPU.

  • It prevents the victim process of kill from exiting and perhaps being re-allocated between kill’s check of p->pid and setting p->killed.

  • It makes kill’s check and write of p->state atomic.

The p->parent field is protected by the global lock wait_lock rather than by p->lock. Only a process’s parent modifies p->parent, though the field is read both by the process itself and by other processes searching for their children. The purpose of wait_lock is to act as the condition lock when wait sleeps waiting for any child to exit. An exiting child holds either wait_lock or p->lock until after it has set its state to ZOMBIE, woken up its parent, and yielded the CPU. wait_lock also serializes concurrent exits by a parent and child, so that the init process (which inherits the child) is guaranteed to be woken up from its wait. wait_lock is a global lock rather than a per-process lock in each parent, because, until a process acquires it, it cannot know who its parent is.