munotes®

Directory Implementation

Get access to whole semester resourcesSemester Pass

Chapter One Hundred Six

Syllabus topic Module 2, "File System Implementation - Directory Implementation"

Pages 429 to 432 of 452

In one line

The names in a directory are kept either in a list, which is simple and slow to search, or in a hash table, which is fast and fixed in size.

Why the choice matters

Every path resolution is a directory search. Opening /home/student/notes/os.txt searches four directories, and that happens on every open, every stat, every ls of a path. The directory search is the most frequent operation in the file system, which is why the choice of structure is worth a chapter.

A linear list

A list of entries, each holding a name and the inode number. It is what the ext4 image of Chapter one hundred five had: the debugfs listing printed a name and a number for each entry and nothing else.

OperationWhat it costs
search for a namea linear scan: on average half the entries, and all of them if the name is absent
create a filea full search first, to be sure the name is not already there, then add an entry
delete a filea search, then release the entry
list the directoryread it through: this one is ideal

Creating a file costs a search of the whole directory, not half. The system must prove the name is absent, and proving absence means looking everywhere. That is the answer to why creating files in a huge directory is slow.

What to do with a deleted entry

MethodWhat happens
mark it unused, with a special name or a zero inode numbersimple; the directory never shrinks and the holes are searched over
move the last entry into the holekeeps the list packed; loses any ordering
keep a free list of the holesa new entry goes into the first hole that fits

A sorted list allows a binary search, which turns 500 comparisons into about 10, but every insertion has to move the entries after it. Keeping the list as a linked list in sorted order, or as a B tree, is how real systems get the search without the insertion cost.

The cost as a number

A directory of 1,000 entries in a file system with 1024 byte blocks, each entry taking about 16 bytes:

entries per block = 1024 / 16 = 64

blocks in the directory = 1000 / 64, about 16

OperationComparisonsBlock reads
a successful search, on average500about 8
an unsuccessful search, or a create1000about 16
a binary search of a sorted listabout 10about 4, one per probe

The comparisons are cheap and the block reads are not: Chapter ninety two priced a block read at about nine milliseconds. A linear directory search of a large directory is a disk operation, and that is the real cost.

munotes.in429

Directory Implementation

A hash table

Hash the name to a number, and keep the entry in the bucket with that number. The search is then one hash and a short chain instead of a scan.

PartWhat it does
the hash functionturns a name into a bucket number
the table of bucketseach bucket holds the entries whose names hashed to it
a chained overflow listfor names that collide, which they will
OperationWhat it costs
searchone hash, then the length of one chain
createone hash, a scan of that chain to check the name, then an insertion
list in orderbadly: the entries are in hash order, so listing sorted means reading everything and sorting it

The weakness is the fixed size of the table. The number of buckets is chosen when the directory is made; as the directory grows, the chains grow with it, and the search becomes linear again. The answer is to rehash into a larger table, which means recomputing every entry's bucket, so it is done rarely and it is expensive when it is.

The same directory, with 64 buckets

average chain = 1000 / 64 = 15.625

About 16 comparisons instead of 500, and usually one block read instead of eight, because a bucket and its chain are small enough to sit together.

Real systems do better again: ext4's directories are hashed B trees, which keep the fast lookup and also grow gracefully. The feature is recorded in the superblock, and it is in the image built in Chapter one hundred five:

$ mkfs.ext4 -q -F -b 1024 -I 128 -O ^has_journal dir.fs 4096 2>&1 | grep -v deprecated
Creating regular file dir.fs
$ dumpe2fs -h dir.fs 2>/dev/null | grep '^Filesystem features' | tr -s ' ' '\n' | grep -c dir_index
1

The 1 is the dir_index feature being present: this file system indexes its directories rather than scanning them.

A directory is a file that grows

The measurement that makes the whole chapter concrete. 120 files are created in the root directory of the image, and the directory's own size is read from its inode before and after.

$ printf 'x\n' > one.txt
$ debugfs -R "stat <2>" dir.fs 2>/dev/null | awk '/^User/ {print "the root directory is", $6, "bytes"}'
the root directory is 1024 bytes
$ { for i in $(seq 1 120); do printf 'write one.txt file-%03d\n' "$i"; done; } > script.debugfs
$ debugfs -w -f script.debugfs dir.fs > /dev/null 2>&1; echo done
done
$ debugfs -R "stat <2>" dir.fs 2>/dev/null | awk '/^User/ {print "after 120 files the root directory is", $6, "bytes"}'
after 120 files the root directory is 2048 bytes
$ debugfs -R "ls /" dir.fs 2>/dev/null | tr -s ' ' '\n' | grep -c '^file-'
120
munotes.in430

Directory Implementation

One block became two. The root directory is inode 2, as Chapter one hundred five showed, and it is an ordinary file: it was 1024 bytes, one block; 120 more names did not fit, so the file system gave it a second block and it is now 2048.

entries = 120 + 3 = 123

2048 / 123, about 17 bytes an entry

123 entries, counting ., .. and lost+found, in 2,048 bytes: about 17 bytes each, which is a name of a few characters, an inode number, and the lengths that go with them. The arithmetic of the earlier section was not a model of something else: it is this.

Distinctions that carry marks

Linear listHash table
Searchlinear: half the entries on averageone hash and a chain
Createa full search, then insertone hash, one chain, then insert
1,000 entries, 64 buckets500 comparisonsabout 16
Listing in ordereasyhard: hash order is not name order
Fixed sizenoyes, until it is rehashed
Programming difficultytrivialsome, and the hash function matters
Successful searchUnsuccessful search
Comparisons in a list of nn / 2 on averagen, always
Happens whenopening a filecreating one

What it does not mean

A directory is not a special kind of object. It is a file, and it grows in blocks like any other, as the measurement shows.

A hash table does not remove the search. It makes it short.

A linear list is not always wrong. Most directories hold a few dozen entries, where a scan of one block is cheaper than a hash.

Rehashing is not something the user sees. It is the file system's own work, and it is why the fixed table size is tolerable.

The 16 bytes an entry is not a rule. It depends on the length of the names, and the image measured about 17.

Quick revision

  • Every path resolution is a directory search, so the structure matters more than any other

choice in the file system.

  • A linear list of name and inode number: a search costs n / 2 on average and n when

the name is absent, so creating a file costs a full search.

  • Deleted entries are marked unused, filled by the last entry, or kept on a

free list. A sorted list allows a binary search but costs on insertion; real systems use a B tree.

munotes.in431

Directory Implementation

  • Worked: 1,000 entries of 16 bytes in 1024 byte blocks is 64 entries a block, about

16 blocks; a search averages 500 comparisons and about 8 block reads.

  • A hash table costs one hash and one chain: with 64 buckets, 1000 / 64 = 15.625,

about 16 comparisons. It lists badly in order, and its fixed size means rehashing as it grows.

  • ext4 records dir_index in the superblock: its directories are hashed B trees.
  • Measured: the root directory was 1024 bytes, and after 120 files it is 2048. With

., .. and lost+found that is 123 entries in two blocks, about 17 bytes each.

Test yourself

  1. Why does the directory structure matter so much? Because every path resolution searches

one directory per component, which makes it the most frequent operation in the file system.

  1. What does a linear list cost to search, and why is creating a file worse? Half the entries

on average for a successful search; creating a file must prove the name is absent, which means searching all of them.

  1. Give the three ways of handling a deleted entry in a linear list. Mark it unused, move the

last entry into the hole, or keep a free list of holes. 4. A directory holds 1,000 entries of about 16 bytes in 1024 byte blocks. How many blocks is it, and what does a search cost? 64 entries a block, so about 16 blocks; a successful search averages 500 comparisons and about 8 block reads.

  1. How does a hash table change the cost, with 64 buckets and 1,000 entries? The search is

one hash and one chain of 1000 / 64 = 15.625 entries, about 16 comparisons and usually one block read.

  1. Give two disadvantages of a hash table for a directory. Listing the entries in name order

needs a sort, and the table is a fixed size, so the chains lengthen as the directory grows until it is rehashed.

  1. What does ext4 do, and how can you tell? Its directories are hashed B trees; the

dir_index feature is recorded in the superblock and printed by dumpe2fs. 8. The root directory of an image was 1024 bytes and became 2048 after 120 files were added. What does that show? That a directory is an ordinary file: the names did not fit in one block, so the file system allocated a second one.

munotes.in432

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!