7.3 Code: Using locks
Xv6 uses locks in many places to avoid races.
As described above,
kalloc
(3027)
and
kfree
(3005)
form a good example.
Try Exercises 1 and 2 to see what happens if those
functions omit the locks.
You’ll likely find that it’s difficult to trigger incorrect
behavior, suggesting that it’s hard to reliably test whether code
is free from locking errors and races.
Xv6 may well have as-yet-undiscovered races.
A hard part about using locks is deciding how many locks to use and which data and invariants each lock should protect. There are a few basic principles. First, any time a variable can be written by one CPU at the same time that another CPU can read or write it, a lock should be used to keep the two operations from overlapping. Second, remember that locks protect invariants: if an invariant involves multiple memory locations, typically all of them need to be protected by a single lock to ensure the invariant is maintained.
The rules above say when locks are necessary but say nothing about when locks are unnecessary, and it is important for efficiency not to lock too much, because locks reduce parallelism. If parallelism isn’t important, then one could arrange to have only a single thread and not worry about locks. A simple kernel can do this on a multiprocessor by having a single lock; the kernel acquires the lock every time the kernel is entered from user space, for a system call or interrupt; the kernel releases the lock when it returns to user space. Many uniprocessor operating systems have been converted to run on multiprocessors using this approach, sometimes called a “big kernel lock,” but the approach sacrifices parallelism: only one CPU can execute in the kernel at a time. If the kernel consumes significant CPU time, more parallelism could be obtained by protecting different objects or modules with different locks, so that different CPUs could be executing in different parts of the kernel at the same time.
As an example of coarse-grained locking, xv6’s kalloc.c
allocator has a single free list protected by a single lock. If
multiple processes on different CPUs try to allocate pages at the same
time, each will have to wait for its turn by spinning in acquire. Spinning wastes CPU time, since it’s not useful work.
If contention for the lock wasted a significant fraction of CPU time,
perhaps performance could be improved by changing the allocator
to have a separate free list per CPU, each with its own lock, to allow truly
parallel allocation.
As an example of fine-grained locking, xv6 has a separate lock for each file, so that processes that manipulate different files can often proceed without waiting for each other’s locks. The file locking scheme could be made even more fine-grained if one wanted to allow processes to simultaneously write different areas of the same file. Ultimately lock granularity decisions need to be driven by performance measurements as well as complexity considerations.
As subsequent chapters explain each part of xv6, they will mention examples of xv6’s use of locks to deal with concurrency. As a preview, Figure 7.3 lists all of the locks in xv6.
| Lock | Description |
| bcache.lock | Protects allocation of block buffer cache entries |
| cons.lock | Serializes read processing of console input |
| tx_lock | Serializes access to console (uart) output hardware |
| ftable.lock | Serializes allocation of a struct file in file table |
| itable.lock | Protects allocation of in-memory inode entries |
| vdisk_lock | Serializes access to disk hardware and queue of DMA descriptors |
| kmem.lock | Serializes allocation of memory |
| log.lock | Serializes operations on the transaction log |
| pipe’s pi->lock | Serializes operations on each pipe |
| pid_lock | Serializes increments of next_pid |
| proc’s p->lock | Serializes changes to process’s state |
| wait_lock | Helps wait avoid lost wakeups |
| tickslock | Serializes operations on the ticks counter |
| inode’s ip->lock | Serializes operations on each inode and its content |
| buf’s b->lock | Serializes operations on each block buffer |