Β§26.5–26.7The Wish For Atomicity … Summary: Why in OS Class?

Part II OSTEP pp. 296–299 Β· ~5 min read

  • atomicity
  • synchronization primitives

The explorer made the wish obvious: if only the increment couldn’t be interrupted in the middle.

26.5 The Wish For Atomicity

Imagine a super-instruction:

memory-add 0x8049a1c, $0x1

If the hardware guaranteed it executed atomically, the race would vanish: when an interrupt occurs, either the instruction has not run at all, or it has run to completion β€” no in-between state visible. Hardware can be a beautiful thing, no? Atomicity means β€œas a unit” β€” all or none.

But we can’t have a super-instruction for everything. Building a concurrent B-tree? No sane instruction set offers atomic-update-of-B-tree. Instead, the hardware provides a few useful atomic instructions, and from them β€” plus help from the OS β€” we build a general set of synchronization primitives : machinery that lets multi-threaded code enter critical sections in a controlled way and reliably produce correct results. Pretty awesome, right?

Tip: Use Atomic Operations

All-or-nothing grouping is one of computing’s most powerful ideas β€” from architecture to concurrent code to file systems (journaling, copy-on-write) to databases and distributed systems, where grouped atomic actions go by the name transactions. Here we’ll use synchronization primitives to make short instruction sequences atomic; the idea, as Part III will show, is much bigger than that.

The Crux: How To Support Synchronization

What support do we need from the hardware in order to build useful synchronization primitives? What support from the OS? How can we build these primitives correctly and efficiently? How can programs use them to get the desired results?

This is the problem this whole Part studies. It is a wonderful and hard problem, and should make your mind hurt (a bit). If it doesn’t β€” keep working until your head hurts; then you know you’re headed in the right direction. (Then take a break. We don’t want your head hurting too much.)

26.6 One More Problem: Waiting For Another

Atomicity isn’t the only interaction between threads. Just as often, one thread must wait for another to complete some action β€” a thread sleeping through a disk I/O must be roused from its slumber when the I/O completes. So the coming chapters build two kinds of machinery: synchronization primitives for atomicity, and mechanisms for sleeping/waking. If that doesn’t fully make sense yet, that’s OK! It will when you read the condition-variables chapter. (If it doesn’t by then, well, that’s less OK β€” read it again, and again, until it does.)

The four key concurrency terms β€” the code, the failure, the symptom, the cure (all Dijkstra's)
definition
critical sectiona piece of code that accesses a shared resource, usually a variable or data structure
race conditionmultiple threads enter the critical section at roughly the same time, both updating the shared structure β€” surprising outcomes follow
indeterminatea program containing one or more races: its output varies run to run, depending on which threads ran when
mutual exclusionthe guarantee that only a single thread ever enters a critical section β€” races avoided, deterministic output restored
Dotted-underlined cells have explanations β€” click one.

26.7 Summary: Why In OS Class?

Why study this here and not in a programming course? β€œHistory” is the one-word answer: the OS was the first concurrent program, and the techniques were created for its own use. Consider two processes appending to the same file:

Why the OS cared first: two processes both append to one file via write()
each write() must…
step 1allocate a new disk block
step 2record the block in the file's inode
step 3grow the file's recorded size
Dotted-underlined cells have explanations β€” click one.

From the moment interrupts existed, kernel designers had to protect every shared structure β€” page tables, process lists, file-system metadata, virtually all of it β€” with proper synchronization. When multi-threaded processes arrived, application programmers inherited the same problems, and the same toolbox.

Homework: x86.py

Interleave real (tiny) assembly yourself: trace loop.s with one thread, then two; shrink and randomize the interrupt interval on looping-race-nolock.s and predict the final shared value; find which interrupt intervals give the β€œcorrect” answer and which are surprising β€” in other words, locate the critical section exactly. Finish with wait-for-me.s and watch a thread busy-wait for a flag: correct, and painfully wasteful. Get it at ostep-homework.

Check yourself

1.A hypothetical memory-add instruction would fix the broken counter in one stroke. What does 'atomically' guarantee, exactly?

2.Why don't hardware designers just give us atomic instructions for everything β€” say, 'atomic update of B-tree'?

3.Besides atomicity for critical sections, the chapter names a SECOND fundamental thread interaction. What is it?

4.'Why in OS class?' has a one-word answer. Give it β€” and the illustration.

5.Match the four key terms: the CODE, the FAILURE, the SYMPTOM, the CURE.

5 questions