Three loose ends, and each one teaches something: the stack grows the wrong way, sharing wants one more bit of hardware, and variable-sized segments leave physical memory full of holes.
16.3 What About The Stack?
In Figure 16.2 the stack lives at physical 26โ28KB โ but it grows backwards: it โstartsโ at 28KB and grows toward lower addresses, backing virtual 16KB down to 14KB. Translation must proceed differently, and the hardware needs to know which way each segment grows โ one extra bit (Figure 16.4: 1 = positive, 0 = backwards).
For a backwards segment, the hardware computes a negative offset:
the raw offset minus the maximum segment size. Virtual 15KB (binary
11 1100 0000 0000): selector 11 โ stack; raw offset 3KB; 3KB โ 4KB =
โ1KB; physical = 28KB + (โ1KB) = 27KB. The bounds check becomes
|negative offset| โค current size (1KB โค 2KB โ). Run it โ then find the
stackโs limits:
virtual address
15 KB (15360)
11110000000000
MMU (segmentation)
1. top 2 bits = 11 โ Stack
2. offset = VA & 0xFFF = 3072; grows โ: 3072 โ 4 KB (4096) = -1024
3. bounds:ย |-1024| โค 2048 โ
4. PA = 28 KB (28672) + -1024 = 27648
physical address
27 KB (27648)
| Segment | selector | Base | Size | Grows positive? | Protection |
|---|---|---|---|---|---|
| Code | 00 | 32 KB (32768) | 2 KB (2048) | 1 | Read-Execute |
| Heap | 01 | 34 KB (34816) | 3 KB (3072) | 1 | Read-Write |
| Stack โ | 11 | 28 KB (28672) | 2 KB (2048) | 0 (backwards) | Read-Write |
The tour: 15360 (15KB) โ offset 3KB, minus the 4KB max = โ1KB, so 28KB โ 1KB = 27KB. 14336 (14KB) โ โ2KB โ the deepest legal byte (|โ2KB| = the stackโs whole 2KB size) โ 26KB. 16000 โ โ384 โ 28288. 13312 (13KB) โ |โ3KB| > 2KB โ fault: below the stackโs reach. 4200 โ the heap, for contrast: positive segments just add.
(Pedantic footnote, straight from the book: the stackโs โstartโ at 28KB is really the byte just below the backwards region โ the first valid byte is 28KBโ1. Saying it this way keeps the arithmetic clean: physical = base + negative offset, always.)
16.4 Support for Sharing
Segment registers made another economy possible: sharing โ code sharing especially, common then and still in use today. The extra hardware is protection bits protection bits Per-segment permission bits (read / write / execute) checked by the hardware on every access; setting code read-only lets one physical copy be safely shared across processes. defined in ch. 16 โ open in glossary per segment: may this program read, write, or execute these bytes? Set a code segment read-execute (Figure 16.5 โ the Protection column above), and the same physical segment can be mapped into multiple address spaces: each process still believes the memory is private, while the OS secretly shares one immutable copy. The illusion holds precisely because no one can write it.
The hardware check grows accordingly: besides in bounds?, now also permissible? Writing a read-only segment, or executing a non-executable one, raises an exception and hands the offender to the OS.
16.5 Fine-grained vs. Coarse-grained Segmentation
Three big segments (code, heap, stack) is coarse-grained coarse-grained segmentation Segmentation with just a few large segments (code, heap, stack) โ the address space is chopped into big, coarse chunks. defined in ch. 16 โ open in glossary โ the space is chopped into a few large pieces. Some early systems (Multics; the Burroughs B5000 with support for thousands of segments) went fine-grained fine-grained segmentation Segmentation with a large number of small segments (Multics, Burroughs B5000), requiring a segment table in memory; compilers chopped code and data into many pieces so the OS could place memory more flexibly. defined in ch. 16 โ open in glossary : compilers chopped code and data into many small segments, and the hardware kept a segment table segment table An in-memory table of segment descriptors used when there are too many segments for dedicated registers โ the hardware support behind fine-grained segmentation. defined in ch. 16 โ open in glossary in memory to hold all those base/bounds pairs. The thinking: the more precisely the OS could see which pieces were in use, the better it could pack main memory.
16.6 OS Support
Segmentation hands the OS three new duties:
Context switches. The segment registers are per-process state now โ saved and restored with everything else. Nothing surprising, but easy to forget: run process B with Aโs segment registers and every access lands in Aโs memory.
Segment growth. A malloc() that outgrows the heap turns into a
negotiation with the OS:
Program: call malloc(size) for a new object
And the big one: managing free space. Every address space used to be the same size โ physical memory was a row of identical slots. Now each process brings several segments, each a different size, and physical memory quickly becomes full of little holes:
click a region marked โ for details
click a region marked โ for details
That failure โ a 20KB request refused while 24KB sits free โ is external fragmentation external fragmentation Free physical memory chopped into little non-contiguous holes by variable-sized allocations, so a request can fail even though enough total free space exists โ e.g. 24KB free in three chunks cannot satisfy a 20KB segment. defined in ch. 16 โ open in glossary , the sibling of chapter 15โs internal fragmentation internal fragmentation Wasted space INSIDE an allocated unit โ e.g., the unused gap between heap and stack in a fixed base/bounds slot. defined in ch. 15 โ open in glossary : that one wasted space inside an allocation; this one strands space between them. Compaction compaction Rearranging allocated segments into one contiguous region (stop the process, copy its data, update its segment registers) to cure external fragmentation โ effective but memory- and CPU-expensive. defined in ch. 16 โ open in glossary (right map) cures it by stopping processes, copying their segments together, and re-pointing their registers โ but itโs expensive, and ironically it packs memory so tightly that growing a segment now forces yet more rearranging. The gentler path: a smarter free list free list The OS structure tracking unused physical memory ranges, searched at process creation and refilled at termination. defined in ch. 15 โ open in glossary algorithm that tries to keep large extents available โ best-fit, worst-fit, first-fit, the buddy algorithm, and literally hundreds more. Theyโre the next chapterโs whole subject; no matter how smart, external fragmentation still exists. A good algorithm merely minimizes it.
Tip: If 1000 Solutions Exist, No Great One Does
That so many algorithms try to minimize external fragmentation reveals a stronger truth: there is no one best way โ we settle for something reasonable and hope itโs good enough. The only real solution is to avoid the problem altogether by never allocating memory in variable-sized chunks. Hold that thought for two chapters.16.7 Summary
Segmentation earns its keep: sparse address spaces become affordable (the unused middle costs nothing), the arithmetic is fast and hardware-friendly, and read-only segments give us code sharing for free. But two problems stick. External fragmentation is fundamental to variable-sized allocation โ manageable, never solvable. And segmentation still isnโt flexible enough: a large but sparsely-used heap in one segment must still be entirely resident. When the model of how memory is used doesnโt match the segments, segmentation stops helping. We need new solutions. Ready to find them?
Homework: segmentation.py
Translate addresses by hand in a tiny two-segment space, find the highest/lowest legal virtual addresses, then reverse-engineer: pick base/bounds values that make a given address stream produce valid, valid, violation, โฆ, violation, valid, valid โ and configure the simulator so ~90% of random addresses are valid. Get it at ostep-homework.Check yourself
1.Virtual address 15KB (0x3C00) targets the backwards-growing stack (base 28KB, size 2KB, max segment 4KB). Walk the translation.
2.How do protection bits let two processes share one physical code segment without breaking isolation?
3.A new process needs a 20KB segment. Physical memory has 24KB free โ 8KB + 8KB + 8KB, in three separate holes. What happens, and what is this problem called?
4.Compaction would cure those three holes. Why isn't it the happy ending?
5.A process calls malloc(), and the heap has no room. Trace what happens under segmentation.