4.2.34 · D4Operating Systems

Exercises — File allocation — contiguous, linked, indexed (inode)

3,083 words14 min readBack to topic

Throughout, we reuse three symbols. Let us define them once, in plain words, before any formula uses them:

Unless a problem says otherwise, take bytes, bytes, so .


L1 — Recognition

Exercise 1.1 (L1)

A directory entry for a file stores exactly two numbers: start = 200, length = 8. Which allocation scheme is this, and which physical blocks does the file occupy?

Recall Solution 1.1

WHAT the clue tells us: only two numbers, a start and a count. That is the fingerprint of contiguous allocation — the whole point of contiguous is that arithmetic replaces stored pointers, so the metadata is tiny: just . WHY: in linked you'd store a first (and maybe last) block; in indexed you'd point at an index block. Neither reduces to "start + length." Blocks occupied: consecutive run from of length : The last block is (we subtract 1 because block 200 is itself the first of the 8).

Exercise 1.2 (L1)

You corrupt one pointer somewhere in the middle of a file's block chain. In which scheme does this potentially lose the entire rest of the file, and why?

Recall Solution 1.2

Answer: linked allocation (the plain, in-block-pointer version). WHY: in linked allocation each block stores the address of the next block inside itself. The only way to reach block is to read block 's pointer. If that pointer is garbage, you can no longer find block — or block , , … since each depends on the previous. One broken link orphans the whole tail. Contrast: in indexed allocation every data-block address sits independently in the index array, so a single corrupted entry loses only that one block, not the tail. This is the reliability advantage of indexing.


L2 — Application

Exercise 2.1 (L2)

A contiguous file starts at block , block size bytes. Which physical block and byte-offset hold byte 30000?

Recall Solution 2.1

Step 1 — WHAT: find the logical block index . Bytes 0–4095 live in logical block 0, bytes 4096–8191 in block 1, etc. So WHY the floor: we want how many whole blocks of 4096 fit before byte 30000; the fractional part is the leftover inside the current block. Step 2 — offset inside the block: Step 3 — physical block via the contiguous formula : Answer: read physical block 57, byte offset 1328.

Exercise 2.2 (L2)

Plain linked allocation, , pointer . A file's blocks (in order) are . (a) How many disk reads to locate & read logical block 3? (b) How many usable data bytes does each block hold?

Recall Solution 2.2

(a) Logical blocks are numbered from 0: block 0 = physical 7, block 1 = 3, block 2 = 90, block 3 = 12. To reach logical block 3 there is no formula — you must chain-walk, reading each pointer to learn the next address: That is disk reads. This is the cost of linked allocation. (b) The next-pointer lives inside the block, stealing bytes:


L3 — Analysis

Exercise 3.1 (L3)

Unix inode, , , so . For each of these logical block numbers, say which pointer region holds it and how many disk reads (inode already in memory) are needed to fetch the data block: (a) , (b) , (c) .

Recall Solution 3.1

First define the region boundaries (see figure below):

  • Direct covers logical blocks through (12 pointers).
  • Single indirect covers the next blocks: through .
  • Double indirect covers the next blocks: through .
Figure — File allocation — contiguous, linked, indexed (inode)

What to observe in the figure: the coloured bars are the four pointer regions laid on the logical-block number line, each labelled with its start and end block. Notice how each region begins exactly where the previous one ended + 1 — the boundaries are not style round numbers but are offset by everything before them. The three arrows drop the exercise blocks , , into their regions, and each arrow's caption states the read count = (pointer levels crossed) + 1. Read the figure left-to-right and you can classify any block by seeing which bar it lands in.

(a) : direct. The inode already holds the direct pointers, so we read straight to data = 1 read. (b) : single indirect. We (1) read the single-indirect block to get the address, then (2) read the data block = 2 reads. (c) : since double indirect. We (1) read the double-indirect block, (2) read the single-indirect block it points to, then (3) read the data block = 3 reads. WHY the read count = number of pointer levels + 1: each level of indirection is one pointer block that must be fetched before we know the next address; the final +1 is the data block itself.

Exercise 3.2 (L3)

Same inode parameters. Compute the maximum file size and identify which term dominates.

Recall Solution 3.2

Reach per region = (number of data blocks) : Plug , :

  • Direct: B KB
  • Single: B MB
  • Double: B GB
  • Triple: B TB

Total bytes. Dominant term: the triple indirect ( TB) — each extra level multiplies reach by , so the top level is ~1024× the one below it. The geometric growth is why three levels suffice for enormous files.


L4 — Synthesis

Exercise 4.1 (L4)

Design question. You must store million tiny log files, each exactly one 4 KB block, that are frequently appended to (so they occasionally grow by a block). Rank the three schemes for this workload and justify, then name the External vs Internal Fragmentation type each suffers.

Recall Solution 4.1

Workload facts that matter: files are tiny and grow. Contiguous: growing a file means the next physical block may already be taken → you'd have to relocate the whole file to a larger free run. With 10 M files churning, that is catastrophic. Also suffers external fragmentation (free space shatters into holes too small to reuse). Worst here. Linked: any free block joins any file, so growth is trivial (append a block, patch the last pointer) and there is no external fragmentation. Downside: each block loses bytes to the next-pointer — this is metadata overhead (a fixed tax paid inside every allocated block), not true internal fragmentation. Random access is — but these files are 1 block, so in practice. Good. Indexed (inode): growth is easy (add a pointer to the index). For a 1-block file the address sits in a direct pointer → 1-read access, no external fragmentation. The cost is the inode/index metadata (again metadata overhead, not fragmentation). Best overall for real filesystems because it also keeps random access if files later grow large. Ranking: Indexed Linked Contiguous. Fragmentation vs overhead map: Contiguous → true external fragmentation (unusable gaps between files). Linked → metadata overhead (pointer bytes taxed inside each data block) plus internal fragmentation only in the file's last partly-filled block. Indexed → metadata overhead (index/inode blocks) plus last-block internal fragmentation. See Free Space Management (bitmap, free list) for how the OS finds those free blocks.

Exercise 4.2 (L4)

An engineer proposes: "Drop the triple-indirect pointer to save inode space — nobody has files that big." With , , what maximum file size does the truncated inode now support, and what fraction of the original capacity is that?

Recall Solution 4.2

Remove only the triple term: Original total (from Ex 3.2) bytes. Fraction retained: Interpretation: dropping one 4-byte pointer per inode collapses the maximum file size by ~1000×, from TB to GB. Saving 4 bytes per inode to lose 3 orders of magnitude of reach is a terrible trade — this is exactly why the triple pointer exists despite being rarely used.


L5 — Mastery

Exercise 5.1 (L5)

Generalize. A filesystem uses bytes, bytes, and an inode with direct pointers, one single-, one double-, and one triple-indirect pointer. (a) Find . (b) Write the general max-size formula. (c) Compute the max size for . (d) How many disk reads to fetch logical block ?

Recall Solution 5.1

(a) WHY first: every later count is measured in "pointers per block," so we compute the fan-out before anything else. (Coincidentally still 1024 — both block and pointer doubled, so their ratio is unchanged.)

(b) WHY this formula: direct pointers reach blocks with no indirection; each indirection level multiplies reach by the fan-out (single = , double = , triple = ). Multiply every block-count by bytes/block and sum: Factoring out keeps the arithmetic clean — do the integer sum inside the parentheses first, multiply once at the end.

(c) With , : build the sum term-by-term (WHY: this exposes which level dominates). Now multiply once by : The triple term () is ~99.99% of the sum — same geometric domination as Ex 3.2.

(d) WHY we classify by region first: the read count equals (number of pointer-block levels we must dereference) for the final data block, so we only need to know which region block lands in. Compute the boundaries, offsetting each region by all earlier ones:

  • Direct: blocks .
  • Single indirect: next blocks .
  • Double indirect: next blocks .
  • Triple indirect: next blocks, starting at .

Since , block falls in the triple-indirect region. WHY the count is 4: we must fetch the triple-indirect block, then the double-indirect block it points to, then the single-indirect block that points to (3 pointer levels), then the data block itself:

Exercise 5.2 (L5)

Prove that for a fixed disk of blocks (let = total number of blocks on the whole disk), plain linked allocation can always satisfy an allocation request of any size (free blocks), but contiguous allocation may fail even when (free blocks). Give the smallest concrete counterexample for contiguous.

Recall Solution 5.2

Linked always succeeds: a linked file needs free blocks in any positions, because each block only needs to point to the next — position is irrelevant. So if the number of free blocks , we can always pick of them and chain them. Formally: the free blocks form a set; any -subset works. No positional constraint ⇒ no failure. ∎ Contiguous can fail: it needs consecutive free blocks — a positional constraint linked lacks. This is external fragmentation. Smallest concrete counterexample. Take the tiniest disk on which the failure can appear: blocks with the pattern free, used, free — i.e. block 0 free, block 1 used, block 2 free.

  • Free blocks , so a request of size satisfies (free blocks).
  • Linked on this request: pick blocks 0 and 2, chain . ✔ succeeds (positions don't matter).
  • Contiguous on this request: needs 2 adjacent free blocks, but the only free blocks (0 and 2) are separated by used block 1 — no run of length 2 exists. ✘ fails despite free blocks.

Why is smallest: with or you cannot build a "free–used–free" split (you need at least one used block between two free ones, so at least 3 blocks). Hence is the minimal disk that proves "enough free space" "enough contiguous free space." ∎


Recall One-line self-checks (cover the answer)

Contiguous file (start=200, len=8) last block ::: 207 Reads to reach logical block 3 in a linked file ::: 4 Region of logical block 500 in a 4096/4 inode ::: single indirect (2 reads) Region of logical block 100000 ::: double indirect (3 reads) Max file size, 4096/4 inode, in bytes ::: 4,402,345,721,856 ( 4 TB) Dropping triple-indirect max (4096/4) ::: 4 GB (about 0.098% of original) when , ::: 1024