Skip to main content
Systems Programming

Files, Directories and Device Management

Published: 2026-08-20
Level: postgraduate
Audience: Postgraduate students in Systems Programming

9.1 Files and Directories — The Unix View

9.1.1 Everything Is a File and Basic Operations

Hook — why does Unix say a keyboard is a file? If every object looks different — a text file, a printer, a folder — how can one set of calls handle all of them? The answer is the Unix decision to treat everything you can read or write as a file.

In Unix and Linux everything is treated as a file. The system deals with any and every object as a file, whether it looks like plain text, a device, or a folder. That single view lets the kernel use the same set of calls for many different objects. The classic statement from the textbooks is simple: a file — a sequence of bytes with no imposed structure — is the only abstraction the kernel needs at the lowest level. Meaning comes from the program that interprets the bytes, not from the kernel.

On a file we can do a small set of core actions. We can open a file, close a file, read from a file, and write into a file. The phrasing matters: we read from a file and write into a file. Opening sets up a handle, reading and writing move bytes, closing releases the handle. Those four verbs are the base of almost all file work that follows.

Everything-is-a-file, built step by step.

  1. A byte is 8 bits — one character for our purpose.
  2. A file is a sequence of bytes stored on disk or produced by a device. The kernel attaches no structure or type to it.
  3. A file descriptor is the integer the kernel returns on open() — your ticket to later read() and write() on that sequence.
  4. A device file is a sequence too: typing on a keyboard produces bytes, a line printer consumes bytes, data flowing in a pipe is bytes. Even though the source looks different, the kernel sees a byte stream in each case.

So a regular file /etc/passwd, a character device /dev/tty, and a directory /home all pass through the same four calls. The uniformity is what keeps the interface small.

Think of a file like a named box that holds bytes, and the directory like a list that tells where each box lives. The file itself holds the content; the directory holds the map. Another everyday picture is a universal power socket: many appliances look different, but they all use the same plug shape. Here the plug is the open-read-write-close pattern, and everything — disk file, tape, terminal — fits it. Where the analogy breaks: real devices have timing and error behaviour that a plain disk file does not, so programs must still handle device-specific errors even though the call looks the same.

Visual intuition: picture a long tape of bytes with a moving finger. open puts the finger at the start, read advances it and copies bytes to memory, write inserts bytes at the finger, close lifts the finger. For a device file the tape is not stored beforehand — bytes are produced or consumed on the fly — but the finger idea still holds.

Scope — when the view helps and when it hides.

  • Applies: kernel I/O path, shell redirection, cat, ls, cp — all use the same file calls whether the argument is a regular file or a device under /dev.
  • Does not hide: permissions, file type, and blocking behaviour still differ. A directory cannot be written with a plain write() in the same way as a text file; a terminal may block until a line is ready. The unified call does not mean identical semantics.
  • Assumes: byte-stream view. Modern systems add richer interfaces (memory-mapped I/O, ioctl) for cases where byte-stream alone is not enough.

9.1.2 What a Directory Holds

A directory — a folder that lists what is inside it — holds information about what is present inside that particular directory. What can be inside? Either files or other subfolders, often called subdirectories, can be present inside that directory. In implementation terms a directory is itself a file whose bytes are a table of entries, not free-form text.

File names inside a directory may be handled in two broad ways: fixed length or variable length. Fixed length means every name slot reserves the same number of bytes even if the real name is shorter. Variable length means each slot grows or shrinks with the name. Unix in particular follows the fixed length structure for directory entries, and that choice has direct limits on how long a name can be.

Everyday analogy — fixed pigeonholes vs elastic folders. Fixed length is like a wall of mailboxes each with the same sized label slot: short names waste space, long names spill over. Variable length is like hanging folders that expand to the label: no waste, but you need extra bookkeeping to find the next folder. Classic Unix chose the fixed mailbox wall to make offset math trivial.

Real-world: This same idea of treating everything as a file is why you can list devices under /dev with the same tool you use to list normal files. Running ls /dev and ls /home goes through the same directory-read logic because both /dev and /home are directories whose entries the kernel reads as tables.

Pitfalls for this idea.

  • Do not think a directory contains the file bytes. It contains only the name-to-inode map; the bytes live in data blocks pointed to by the inode.
  • Do not mix up fixed-length with “always 14 characters today.” The 14-character limit is the classic Version 7 layout; modern ext4, xfs and others use variable names and allow 255 characters.
  • Remember the spelling: read from, write into — examiners notice the prepositions.

9.1.3 How This Connects to What Came Before

This session continues directly from the previous one that introduced what a file is and what a directory is. The plan here is to summarize those ideas quickly and then move forward into how the system turns a path name into the bytes on disk, and how to inspect disks and devices from the command line.

The lecture flow from here is intentional: first the static layout — how a directory entry maps name to number (section 9.2) — then the bridge from name to content via inodes (section 9.3), then the dynamic search that turns a path string into an inode (section 9.4), then the cost of that search in disk fetches (section 9.5), and finally the user-visible tools df, lsblk and fdisk that let you see the same structures from the shell (sections 9.6–9.8).

Recap — 9.1 in one line: Unix treats every I/O object as a byte-sequence file accessed by open-read-write-close, and a directory is itself a file that maps names to inode numbers — the fixed-length table idea sets up the 16-byte entry you see next.

Bridge: With that map idea in mind, the next step is to open the table itself and measure its rows: 16 bytes, split 2 plus 14.

9.2 Unix Directory Entry Structure

9.2.1 The 16-Byte Entry and Its Two Parts

Hook — how does the kernel jump straight to the 5th file in a folder without scanning names? Because every row in the directory table is the same size, so position is arithmetic, not search.

Unix stores directory entries in a small table with three columns visible in the teaching slide. The first column gives the byte offset in the directory — that is, where the next item sits inside the directory file itself. The source phrasing for the reconstruction is: "the difference between various row entries in that byte offset ... is 16 bytes ... every row is of size 16 bytes" and "every row ... consists of 16 bytes ... divided into two parts ... I node number ... 2 bytes ... 14 bytes ... file name."

Formalize — the 16-byte entry, step by step.

An offset is the distance in bytes from the start of the directory file. An I-node number is a small integer that identifies the file's metadata structure. A file name field holds the characters of the name.

  1. Look at successive rows in the slide's offset column: offsets read . The gap is constant .
  2. So each row — one directory entry — must be bytes.
  3. The row is split into two fields: the first bytes hold the I-node number, the remaining bytes hold the name characters.

Reconstructed size relation:

where

  • is the size of one directory entry in bytes,
  • bytes bits hold the I-node number — a small integer that points to the file's metadata structure,
  • bytes hold the file name characters under this old fixed length layout.

Variable view inline: an entry is bytes of inode number bytes of name, and the byte offset steps by each row, so offsets read .

The geometry check is simple: if each entry is bytes, then the -th entry starts at . That matches the observation that successive row offsets differ by .

The verbal audit trail for this math is kept as quoted above so the rebuild can be checked. The symbol rule here is that byte counts are in bytes, the inode number is a 16-bit unsigned integer in this classic layout, and offsets are directory byte positions.

Visual intuition: picture a ruler marked every 16 bytes. At sits entry , at entry , at entry . Each 16-byte block is cut after 2 bytes: left slice is the number, right slice is the name. Because the cut is at the same place every time, the kernel computes the address of entry without reading names — a direct calculation.

Worked example — 16-byte entry table, step by step.

Take a tiny directory that contains three files. The slide's table looks like this conceptually:

Byte offset I-node number (2 bytes) File name (14 bytes, padded)
file1.txt
notes
a.out
  1. Entry size confirmed: bytes per row.
  2. Offset of entry : . So entry at , entry at , entry at .
  3. If you want entry 's inode, seek to byte , read 2 bytes → . Those 2 bytes are the only link to the file's content.
  4. Sense-check: bytes bits can represent distinct inode numbers (0 to 65535), which was the limit in this classic Version 7 design.

This fixed wall of 16-byte slots is why the byte offset column steps by 16 — it is counting slots, not characters.

9.2.2 The 14-Character Name Limit and What Happens on Overflow

Because bytes are fixed for the name, Unix under this layout restricts a file name to a maximum of characters. Anything longer will be truncated. The practical warning given is that if two files share the same first characters, the system may treat them as colliding and the older file might be replaced by the newer one, with a prompt in interactive use. In short, a truncated name is not a safe name.

Concretely, a name like my_very_long_filename.txt (24 chars) would be cut to my_very_long_f — only the first 14 characters kept. A second file my_very_long_fileB.txt also becomes my_very_long_f in the table, so the two entries collide.

Real-world: Modern file systems like ext4 now allow much longer variable names — typically 255 characters — but the 14-character fixed slot is the classic Unix Version 7 layout that explains the design trade-off between simple table math and name flexibility. The limit also explains old stories of name-collision bugs on legacy installations.

Assumptions & scope — when the 14-character rule applies.

  • Applies: classic Unix Version 7 and the teaching slide's 16-byte layout. The arithmetic and the -character truncation follow directly from that fixed design.
  • Does not apply: modern ext4, xfs, btrfs, ntfs — all use variable-length entries and longer limits. Do not quote 14 characters as a current Linux limit in an exam unless the question says “classic/Version 7” or “16-byte entry.”
  • Assumes: one byte per character (ASCII). With multi-byte encodings the byte vs character distinction would matter, but the vintage layout predates that concern.

Visual — truncation collision: imagine two mailboxes both labelled my_very_long_f because the label cutter chops after 14 letters. From the outside they look identical, so mail for one can overwrite the other. Modern variable-length systems print the full label, so no collision.

Pitfall — the “first-14 match” trap. Students often think a long name is simply stored longer. Under the old fixed slot it is silently cut, and two files whose first characters match may appear as one entry. The older file might be replaced by the newer one, with a prompt in interactive use. If an exam asks why a long file name appears truncated, answer: the 14-byte fixed name field was exceeded, so the system keeps only the first 14 characters and risks collision.

Exam note: Expect a short theory or output-justification question on why a long file name is stored in truncated form and what the risk is when first characters match.

9.2.3 Special Entries: Dot, Dot-Dot and Root

The last column of the table shows example file names such as file1.txt, along with two special entries . and ...

  • . indicates the current directory itself.
  • .. indicates its parent directory.
  • / indicates the root — the top of the whole hierarchy.

Those three symbols are not ordinary files; they are entries that let the system walk up or stay in place without extra bookkeeping. In the 16-byte table they look like normal rows — for example offset might be . with the inode of the current directory, offset might be .. with the parent's inode — but their meaning is fixed: . loops to the same inode, .. moves one level up, and / is the top.

Q: What is this . and what is this .. in the directory listing?

A: . is the current directory and .. is its parent directory. The root itself is written as /. The entries behave like any other directory entry in the table, but their meaning is fixed: . loops to the same inode, .. moves one level up, and / is the top. The question came up as a quick recall check, and the follow-up clarified that root is a single forward slash, not dot notation.

A helpful memory picture: inside any folder you always find two hidden signposts — one that says “you are here” (.) and one that says “go back one level” (..). Without them the kernel would need separate logic to stay put or go up; with them the same lookup loop works for every component, including “stay” and “up.”

Recap — 9.2 in one line: A classic Unix directory is a table of -byte rows ( bytes inode number bytes name) at offsets with formula and , names beyond characters are truncated and can collide, and . / .. / / are the fixed signposts for current, parent and root.

Bridge: The 2-byte number in each row is only a pointer — next we see what it points to and why the name itself is called a link.

9.4 Path Name to Inode Translation — The namei Algorithm

9.4.1 Absolute versus Relative Paths and a Motivating Example

Hook — why does the kernel care how you type the path? You type characters; the kernel must end with a number. The whole job of this section is that translation: path string → inode number.

Initial access to a file from a user point of view is through its path name. You must know where the file lives to name it. The kernel also starts from that path name, but internally it must work with I-nodes. It converts the path plus file name into the file's I-node.

A running example carries through the whole discussion:

  • cat /home/ubuntu/sp/file_name.txt where cat displays the contents of a file.
  • Equivalent form cat /home/spiderman/hello.txt used when counting disk transfers.

A path can be of two types:

  • An absolute path always starts from the root /, for example /home/ubuntu/sp/file_name.txt.
  • A relative path is relative to the current directory and does not start from /, for example exercises/sys_pgm1/test1/mydetails.pxt when the current directory is /home/sys_pgm.

The kernel passes the path name one component at a time, where a component — the text between slashes — is the unit of lookup. Even the first / itself counts as a component that means root. The next component is the first directory name, then the next directory name, and so on, with the last component being the file name itself including its extension. The distinction that the extension is part of the name, not a separate component, was made explicit.

A path component example for /home/ubuntu/sp/file_name.txt:

  • component 1: / (root)
  • component 2: home
  • component 3: ubuntu
  • component 4: sp
  • component 5: file_name.txt (name plus extension together)

Q: What will be the last component of the path?

A: A student offered "file extension." The clarification is that the file extension is part of the file, so the last component is the whole file name with name plus extension together. For /home/ubuntu/sp/file_name.txt the last component is file_name.txt, not just txt. The extension is not a separate hop; the kernel looks up the full string file_name.txt as one entry in the sp directory.

Why the confusion feels real: in everyday talk we say “the txt file,” so it sounds like txt should be its own level. In the kernel table there is no row for txt — only a row whose 14-byte name field holds file_name.txt.

Exam note: The split name + extension is one component is a common point for a short question.

9.4.2 Where the Search Starts: Root and Current Directory Inodes

All path name searches start from the current directory of the process unless the path name starts with /, which indicates start from root. The kernel therefore keeps two inode numbers handy:

  • the root inode number, always available,
  • the inode number of the current working directory, updated whenever a cd is issued.

An extra piece of context stored in the U area (user area) is the per-process information: what files are open for that process, what the current directory is, and what signal action to take when signals such as control-D, control-X, or control-Z arrive.

Why the U area matters here. The U area is the kernel's per-process scratch pad. For pathname work it remembers the current directory inode so a relative path can start without mentioning /. For shell life it also remembers open descriptors and disposition for terminal signals — control-D (end-of-file), control-X or control-Z patterns the professor mentioned as examples of per-process signal choices. The point is not the signal details but that “current directory” is per-process, not global: two shells can cd differently because each has its own U area entry.

A small detail that helps intuition: inside the kernel, you think in paths, but the kernel always works with inodes. The path is the user view; the inode chain is the kernel view. The path is what you type; the inode number is what the kernel caches and locks.

9.4.3 Working Inodes and the Left-to-Right Walk

Components of the path are processed from left to right — that is the order the path is written and the order the kernel follows. A light aside noted that if a file system supported a language written right to left, the order might feel different, but on the systems in use the walk is left to right.

Every component except the last one should be either a directory or a symbolic link:

  • Those intermediate inodes are called working inodes. They are the stepping stones that let the kernel reach the final file inode.
  • If the working inode is a directory, the kernel looks for the current path component inside the directory entry list for that working inode. If it is not found, it returns an error. If it is found, the inode number tied to that matched name becomes the new working inode.
  • If the working inode corresponds to a symbolic link, the path name up to and including the current component is replaced by the contents of the symbolic link and the pathname is reprocessed from the start, because a symlink is a shortcut that points elsewhere.

Intuition — stepping stones. The teaching line that was restated several times is: home is the working inode, search for ubuntu inside it, then ubuntu becomes the working inode, then search for sp inside ubuntu, then sp becomes the working inode, and so on.

Picture a river crossing: each stone is a directory inode. You stand on home, you look inside home's table for the name ubuntu, you hop to ubuntu's inode-stone, then look for sp, hop again. The last hop lands on the file's inode. You never jump over a stone; you always move one component at a time, left to right.

The walk is exactly the left-to-right scan of the string you typed. For /home/ubuntu/sp/file_name.txt the kernel sees the stream /homeubuntuspfile_name.txt and processes them in that string order. The Arabic aside — whether a right-to-left language would flip the order — was answered as left-to-right for almost all systems in use because the path string itself is built left-to-right.

9.4.4 The namei Algorithm Laid Out Step by Step

The algorithm that does this conversion is called the namei algorithm — spoken as "name I" — and every cat, open, or search for a file goes through this loop. “namei” literally means “name to inode.”

Pseudo-steps preserved verbatim alongside the reconstruction:

"if path name starts with the root meaning that it is absolute path ... working I node is root I node ... if not ... working I node will be the current working I node ... while there are more in the path name ... read the next component ... read the directory content ... if component matches entry ... get I node number ... release working I node ... I node of match component becomes working I node ... return working I node."

Reconstructed as structured prose with symbols:

  • Let be the path string, let be the inode number of the current working directory, let be the inode number of /, and let be the working inode.

Then

When the loop ends, is the inode number of the file name itself. The algorithm returns that working inode. That inode then leads via its block pointers to the data blocks that hold the actual bytes.

Reading the loop line by line.

  • Initialization: The conditional on chooses the start stone: root inode if the path is absolute, otherwise the process's current directory inode from the U area.
  • Read directory content of : fetch the 16-byte entry table for the current directory (via its inode structure and data blocks).
  • Match: linear search for the string in the 14-byte name fields. On hit, pull the 2-byte number .
  • Release: drop the in-memory hold on the old — “release the working inode” means the kernel no longer pins that directory's inode as the focus.
  • Update: — the matched entry's inode becomes the new stepping stone.
  • Error: if no row has name , the path is bad (No such file or directory).

Symbols in one place: is a string, and are inode numbers (16-bit in the classic layout, larger in modern systems), and are inode numbers, is a component string like home or file_name.txt.

The verbal audit trail above is kept intact so the step "release the working inode" is not lost — it means the old working inode's in-memory copy is no longer held as the focus moves forward. The step matters for locking: without release, the kernel would pin every directory on the path at once.

Real-world: This same loop explains why a broken intermediate directory name fails fast, while a dangling symlink causes reprocessing rather than a simple miss. Example of symlink: if sp is a symlink to /srv/data, the kernel replaces the path up to sp with /srv/data and restarts the walk from root — it does not just look for the next component inside sp.

Worked example — namei walk for /home/ubuntu/sp/file_name.txt.

Start: /home/ubuntu/sp/file_name.txt, so .

  1. / is implicit root — already root.
  2. home — read directory content of (root's table), find entry home, release old , (home is now working inode).
  3. ubuntu — read home's table, find ubuntu → new , release home, ubuntu's inode.
  4. sp — read ubuntu's table, find sp → new , release ubuntu, sp's inode.
  5. file_name.txt — read sp's table, find file_name.txt → new , release sp, file's inode. Loop ends. Return .

At each line the pattern is “home is the working inode, search for ubuntu inside it, then ubuntu becomes the working inode, then search for sp inside ubuntu, then sp becomes the working inode” — exactly the professor's stepping-stones phrasing. If any lookup fails, the kernel returns an error before reaching the file. If an intermediate inode is a symlink, the prefix up to that component is replaced by the link content and the whole walk restarts.

For a relative path exercises/sys_pgm1/test1/mydetails.pxt with current directory /home/sys_pgm, the same loop runs but starts with (the inode of /home/sys_pgm) and components exercises, sys_pgm1, test1, mydetails.pxt in order.

Visual intuition: plot the path horizontally. Put a token on the start stone (root or wd). Each iteration moves the token one notch right to the next stone, after peeking inside the current stone's 16-byte table. The token never leaps; symlink is the only case that teleports the token back to the start with a new path string.

Assumptions & scope — what namei assumes.

  • Assumes: every intermediate component except the last resolves to a directory or a symlink. If an intermediate component is a regular file, the walk errors — you cannot look up a name inside a file.
  • Assumes: directory tables are readable and follow the 16-byte / 2-plus-14 layout in this teaching model. Modern systems generalize the entry size, but the lookup logic is the same.
  • Scope: namei finds the inode; it does not check permissions or open the file — later steps do that. Caching (inode and buffer cache) can avoid disk fetches for repeated components, but the logical walk stays left-to-right.

Common pitfalls.

  • Thinking the extension is a separate component — it is not; file_name.txt is one lookup.
  • Thinking the walk is right-to-left for some locales — it is left-to-right because the string is built that way; the Arabic joke was only a hook.
  • Forgetting the release: “get inode number, release working inode, inode of matched component becomes working inode” — order matters; the new inode is not valid until the old one is released and the next table is read.

Exam note: Expect to apply the same left-to-right working-inode walk to a relative path example and to state how many inode and data block fetches occur.

9.4.5 Student Questions During the Walk

Q: If Arabic were used for file systems, would the walk go right to left?

A: The note was that the professor was not sure for such a system, but for almost all systems in use the walk is left to right because that is how the path string is built. The question worked as a memory hook: follow the string order you see on screen — /home/... is read from the slash on the left toward the file name on the right — and the kernel follows that same left-to-right token motion.

Why it is useful anyway: it reminds you that namei is not scanning the disk in some optimized order; it is literally consuming the characters you typed from first to last, one slash-separated piece at a time.

The aside serves as a memory hook rather than a technical rule: follow the string order. The same hook helps with the exam: when asked to “show the working inode at each step,” list components left-to-right and update after each match — exactly the loop invariant above.

Recap — 9.4 in one line: The namei (“name I”) algorithm turns a path into an inode by initializing working inode to for absolute paths or for relative, then looping left-to-right — read directory of , match component to a 14-byte name, grab its 2-byte inode , release , set — with symlink prefix replacement as the only detour, finally returning for the file.

Bridge: Once the final is known, the next cost appears — each hop inside that loop can touch disk, so even a simple cat pays an inode-plus-data-block price at every level.

9.5 Disk Access Walkthrough — From cat Command to Data Blocks

9.5.1 The Example Path Used for Counting

Hook — why does cat pause before printing? The bytes you asked for are not in memory yet. The kernel must first fetch a chain of tables and records, one per directory on the path, before the file's own bytes arrive.

The walkthrough example uses cat /home/spiderman/hello.txt and by analogy cat /home/ubuntu/sp/file_name.txt. The path is absolute, so the start is the root. Along the way concrete inode numbers were given: root is , home is , and hello.txt is . Those numbers are the short handles that move while the full inode structures stay on disk until fetched.

What those numbers mean. In this teaching demo the kernel's root inode number is — the well-known fixed root used by ext-family file systems (inode is often reserved). home and hello.txt are the 2-byte values you would read from the 14-byte name fields' companion numbers in the directory tables. The numbers themselves are small; the structures they point to are larger records with mode, size and block pointers.

For /home/spiderman/hello.txt the components are /homespidermanhello.txt — three directories before the file, matching the four-component shape used for counting. For /home/ubuntu/sp/file_name.txt the shape is /homeubuntuspfile_name.txt — four directories before the file. The counting pattern scales with depth: each extra directory adds another inode-plus-data-block pair.

9.5.2 Step-by-Step Transfer Sequence

Each level does two fetches if not already cached: the inode structure itself and then the data block(s) that hold the directory entries. A data block — the unit the kernel moves between disk and main memory — is typically , that is bytes.

Worked trace — cat /home/spiderman/hello.txt with disk transfers.

Path is absolute, so working inode starts at root .

  1. The inode number of / is always with the kernel. That inode structure must be on persistent storage, so it is copied from disk to main memory. Then the data block of / is transferred to main memory. Now search for home inside the data block of / and obtain its inode number, which is .
  • Transfers so far: inode / (disk→memory) + data block / (disk→memory) = 2.
  1. Transfer the inode structure for (home) from disk to main memory. Then transfer the data block associated with the home directory to main memory. Search for spiderman in that data block and obtain its inode number (call it ).
  • Adding: inode + data block home = +2 (total 4).
  1. Transfer the inode structure for spiderman to main memory, then its data block to main memory. Search for hello.txt in that data block and obtain its inode number, which is .
  • Adding: inode spiderman + data block spiderman = +2 (total 6).
  1. Transfer the inode structure for (hello.txt) into main memory, and then transfer its data block — the one that holds the actual file bytes — into main memory. From there the CPU can access it and cat displays the content on screen.
  • Adding: inode + data block hello.txt = +2 (total 8).

The pattern spelled out for each directory is: fetch its inode fetch its data block look up next component name get next inode number. The file's final data block fetch is the one that actually yields the text to print. The same sequence applies if the path were /home/ubuntu/sp/file_name.txt: / to home to ubuntu to sp to file_name.txt, with inode + data block pairs at each hop — here depth gives inodes (root + 4 dirs/file) and data blocks, total 10 transfers before the first byte of user data is seen.

The block size reminder heard is that a data block is typically K, that is bytes, so each directory fetch brings a block-sized chunk of entry tables into memory. Formally:

and the earlier directory offset still steps by bytes within that block.

Sense-check: transfers for a 3-directory path is not “slow” — each transfer is one disk I/O, and caching removes most repeats. But the count explains why the first cat after boot costs more than the second: the buffer cache already holds the inodes and blocks the second time.

Visual intuition: draw a ladder with two rails — left rail is inode structures, right rail is data blocks. For each level of the path you climb one rung on the left (inode) and one on the right (block). The last rung on the right finally holds the file's text. Without caching you climb both rails at every level; with caching many rungs are already warm in memory.

9.5.3 Counting Disk Operations

How to count.

  • Define a disk operation as one transfer of an inode or a data block from disk to main memory.
  • For a path of depth directories before the file, the first access costs:

where the “+1” is the final file itself — even if you quote “per directory plus file,” the file also needs its inode and its data block. For cat /home/spiderman/hello.txt (home, spiderman) plus root handling gives the 8 counted above once root's pair is included. For /home/ubuntu/sp/file_name.txt gives 8 in the simplified per-directory count, or 10 including root's pair — examiners accept either breakdown if you label what you include.

Each transfer from disk to main memory is a disk operation. That is why even a simple cat can touch many disk structures before the first byte of user data is seen. Caching can reduce later repeats, but the first walk must materialize each inode and directory block.

Real-world: on a modern ext4 host the block size may be , or rather than classic , and the inode number is 32-bit, but the “two per level” pattern stays identical — inode then block, lookup, next inode. Tools like stat and debugfs let you inspect the same inode numbers (stat /home shows Inode: 13 in the demo's vintage numbering) without running the full walk by hand.

Assumptions & scope.

  • Assumes: cold cache — no inode or block is already in memory. In practice the buffer cache keeps recently used inodes/blocks, so repeated cat on the same path may need zero disk reads.
  • Assumes: one data block per directory suffices to hold its 16-byte entry table for the lookup. Huge directories that span many blocks would need more than one block fetch per level.
  • Scope: counting inode + data block per hop is a disk-operation estimate, not a timing estimate — seek and rotational costs are not modelled here.

Pitfall — forgetting root or the file's own pair. Students often count only intermediate directories and omit root's inode+block or the final file's inode+block. Name them explicitly: “root inode, root block, home inode, home block, …, file inode, file block.”

9.5.4 A Second Set of Paths and an Exercise

Two shorter examples contrast absolute and relative starts:

  • Absolute: /etc/passwd — starts from / as in the main example.
  • Relative: current directory is /home/sys_pgm and the path given is exercises/sys_pgm1/test1/mydetails.pxt. The exercise left to the class is to walk the namei loop starting from the current directory inode rather than root and process components exercises, sys_pgm1, test1, mydetails.pxt in order, applying the same working-inode replacement at each match.

Exercise walk — relative path exercises/sys_pgm1/test1/mydetails.pxt from /home/sys_pgm.

Let (the wd saved in the U area).

  1. exercises — read data block of , find exercises, release , .
  2. sys_pgm1 — read data block of , find sys_pgm1, release, .
  3. test1 — same: find test1, hop.
  4. mydetails.pxt — find mydetails.pxt (= name plus extension as one component) in test1's table, final is the file's inode. Then fetch its inode structure and its data block — the 1K block at 1024 bytes — to get the bytes.

Counting: 4 components → 4 levels of inode+block if warm-cache-free, plus the final file's own pair already included as the last level. Compare with the absolute /etc/passwd which starts at root , finds etc → its inode, then passwd-like number: only 2 hops, so far fewer transfers.

Exam note: Expect to apply the same left-to-right working-inode walk to a relative path example and to state how many inode and data block fetches occur. Label each hop as “inode → data block → lookup name → inode .”

Recap — 9.5 in one line: For cat /home/spiderman/hello.txt the kernel does disk→memory for root inode + root data block → lookup home () → home inode + home block → lookup spiderman → spiderman inode + block → lookup hello.txt () → file inode + file data block ( bytes), so transfers per level (inode then -byte block, offsets still inside directories) before cat can print.

Bridge: The inode-and-block cost you just counted is exactly what df summarizes from the other side — how many blocks and inodes the file system has, uses, and leaves free.

9.6 Disk Usage Inspection — The df Family

9.6.1 What df Shows at Base

Hook — you counted individual block fetches; now step back: how full is the whole disk? That summary view is df.

df — introduced as "disk file system" and in practice disk free — lists the file system view: file system name, total blocks, used blocks, available blocks, use percentage, and what it is mounted on. A plain df prints those columns for each mounted file system.

A typical df header reads Filesystem 1K-blocks Used Available Use% Mounted on. Each “1K-block” is the same byte unit you met in the transfer walk, so the numbers tie back: a file that needed a 1K data block contributes one to “Used,” and the free count drops by one.

The base table, column by column.

  • Filesystem — device name, e.g., /dev/sda1.
  • 1K-blocks — total blocks on that file system, in units of 1024 bytes.
  • Used — blocks that hold inodes’ data blocks, directory blocks, and file blocks.
  • Available — blocks free for new files.
  • Use%. A value like means mostly free; means a full file system where writes will fail.
  • Mounted on — the directory where the file system appears in the single tree, e.g., / for root, /home for home.

No flag means “one line per mounted file system” in the raw 1K-block unit — useful for scripts that parse numbers but hard for a human to eye-scan.

9.6.2 Options That Change the View

  • df -a : include all file systems, even dummy ones. In the demo the output of df and df -a looked much the same on that system, but the flag concept is that -a forces display of entries that an ordinary df may hide — pseudo and dummy file systems with zero blocks that still appear in /proc/mounts.
  • df -i : switch from blocks to inode information. Instead of size, it shows total inodes, used inodes, free inodes for each file system — the handle space rather than the byte space.
  • df -h : human readable sizes. The raw df prints large block counts; df -h prints sizes like 98G, 9.4G, 84G and a use percentage such as 11%. The demo highlighted /dev/sda1 with , , , mounted on / with 11% use, plus other lines at 100% use. The math is the same blocks, just scaled: K-blocks.
  • df -T : add file system type column. The demo showed types such as ext4, ext3, ntfs, fat, fat32 for normal read/write, squashfs for a compressed file system that stores a whole image in squeezed form, and tmpfs for a temporary file system that keeps data in local memory for fast read/write and transfer.

Worked snippets — what you type and what changes.

  • df → columns Filesystem 1K-blocks Used Available Use% Mounted on. Example line (raw): /dev/sda1 102584320 9856000 87500000 11% /.
  • df -i → same rows but columns Inodes IUsed IFree IUse%. Example: /dev/sda1 6553600 120000 6433600 2% / — two percent of inodes used, so you can still create many files even though 11% of blocks are used.
  • df -h → sizes in K, M, G rather than raw block counts. Example: /dev/sda1 98G 9.4G 84G 11% / — the exact demo numbers, easier to quote in viva.
  • df -T → adds Type column, e.g., /dev/sda1 ext4 98G 9.4G 84G 11% /. The extra column tells you which driver family backs the blocks.
  • df -h / or df /home → single line for the file system holding that path — use when a question asks “how full is /?”.

Each snippet was run live, and the back-and-forth corrected the case of -t versus -T when a try with the wrong case did not give the expected filter.

Real-world: tmpfs is often used for /tmp or caches where speed matters and persistence is not needed; squashfs is common for live images. ext4 is the typical read/write default on Linux; squashfs is compressed read-only, so its “Used” is the squeezed image size, not a dynamic count.

Visual intuition: picture two gauges side by side. df -h is the fuel gauge in gigabytes — 98G tank, 9.4G used, 84G left. df -i is the ticket-roll gauge — total tickets (inodes) 6.5M, used 0.12M, free 6.43M. A file system can run out of tickets before it runs out of fuel if millions of tiny files are created — that is why -i exists.

Assumptions & scope.

  • Assumes: the 1K-blocks unit unless -h is added. Scripts that parse df should use the default or df -k explicitly; df -h is for humans.
  • Scope: df summarizes the file system that contains a path, not the directory itself. df /home and df /home/ubuntu/sp can print the same line if both live on /dev/sda1.
  • Case matters: lowercase -t is a filter (include only this type), uppercase -T is a column (show the type). Confusing them is the classic viva trap.

Pitfall — dummy vs real. df -a can add zero-block pseudo entries; students sometimes think the count changed. On the demo host df and df -a looked alike — that only means no extra dummy was mounted there, not that -a does nothing.

9.6.3 Targeted and Filtered Queries

A path can be given, for example df -h / or df /home, to show only the file system that holds that mount. That answers “which device backs this directory?” without listing all mounts.

To filter by type:

  • df -t ext4 : include only file systems of type ext4. On an ext4-only host this shows the real disks; squashfs and tmpfs rows disappear.
  • df -x ext3 : exclude file systems of type ext3. This hides a type you are not interested in and shows everything else.
  • Lowercase -t means include type, uppercase -T means show type column — the case matters. Trying df -t ext3 on a system with no ext3 prints that no file system matched, while df -x ext3 falls back to showing the rest.

Combinations like df -T -t ext4 or df -T -x ext3 are allowed; the same include/exclude logic applies while also printing the type column. So df -T -t ext4 means “show only ext4 and add the Type column,” which on the demo host highlighted the /dev/sda1 ext4 line alone.

Exam note: A question may ask to give the exact flag that yields a human-readable size view, an inode view, or a type-filtered view, or to justify output lines such as the 11% versus 100% entries. The 100% entries in the demo were the squashfs/tmpfs loop rows — they are sized exactly to their content, so used equals total by design, not because the disk is actually full.

9.6.4 Worked Command Snippets

Live-typed patterns to memorize.

  • df → columns Filesystem 1K-blocks Used Available Use% Mounted on.
  • df -i → same rows but columns Inodes IUsed IFree IUse% — inode view, not byte view.
  • df -h → sizes in K, M, G rather than raw block counts. Recall 98G, 9.4G, 84G for /dev/sda1 at 11%.
  • df -T → adds Type column, e.g., /dev/sda1 ext4, plus squashfs (compressed) and tmpfs (memory) rows.
  • df -h / → single line for the file system holding /.
  • df -t ext3no file systems processed when none match; df -x ext3 → all others. As demoed, df -t ext4 kept ext4, df -x ext3 hid ext3.
  • df -T -t ext4 → filtered plus typed; df -T -x ext3 → excluded plus typed.

Each snippet was run live, and the back-and-forth corrected the case of -t versus -T when a try with the wrong case did not give the expected filter. A handy memory aid: little t = tiny filter, big T = big Table adds a column.

Recap — 9.6 in one line: df reports file system use as 1K-blocks Used Available Use%, and flags reshape it — -a adds dummy file systems, -i swaps blocks for Inodes IUsed IFree, -h scales to 98G/9.4G/84G 11% for humans, -T adds the Type (ext4, ext3, ntfs, fat, squashfs, tmpfs), -t/-x filter by type while a path argument like df -h / narrows to one mount.

Bridge: df tells you how much space and how many inodes are left; the next layer tells you what kind of device that space sits on and whether the kernel buffers it.

9.7 Unix Input/Output — Character versus Block Devices

9.7.1 Memory and Device Setting

Before device talk, the memory ladder was named: main memory is RAM (random access memory), secondary memory is the hard disk, tertiary examples are tapes, and cache sits above RAM in levels L0, L1, L2 with no separate common name. That ladder matters because where bytes live decides how they move.

The ladder, bottom to top.

  • Tertiary: tapes — cheapest per byte, slowest, often offline.
  • Secondary: hard disks — your sda and partitions; block devices whose bytes are moved in byte chunks.
  • Primary: RAM — main memory where the kernel copies inode structures and data blocks before the CPU can use them; some views expose RAM itself as a character device.
  • Cache: L1, L2 (and sometimes L0) — tiny, fastest, no separate “disk-like” name; they hold recently used bytes so repeated cat fetches avoid disk.

The device classification that follows is about the interface the kernel exposes for each rung, not just the hardware itself.

Visual intuition: picture a pyramid. The base is wide tapes, the middle is the disk, the top is narrow RAM, and the tip is cache. Bytes flow upward on read (disk block → RAM → cache → CPU) and downward on write. Character devices cut across this pyramid by streaming bytes without staging a whole block.

9.7.2 How to Tell the Type: ls -li on /dev

A student asked how to know whether a device is block or character oriented.

Q: Is there a simple command that tells which devices are block devices and which are character devices?

A: Use ls -li /dev. The first character in the long listing tells the file type: - is a regular file, d is a directory, c is a character device, and b is a block device. Entries that start with c are character devices and those with b are block devices among the /dev files. That same flag -i also prints the inode number, so you see both the type and the handle in one view.

Example fragment from a live ls -li /dev (inodes illustrative):

1234 crw-rw-rw- 1 root root 1, 5  ... /dev/zero   (c = character)
5678 brw-rw---- 1 root disk 8, 0 ... /dev/sda    (b = block)
9012 crw--w---- 1 root tty  4, 0 ... /dev/tty0   (c = character)

The first column c vs b is the answer; the 1, 5 or 8, 0 after it are the major, minor numbers you meet in the next section.

Because /dev is itself a directory, the 16-byte entry logic still applies — each device name occupies a row whose 2-byte inode number points to a special inode that, instead of block pointers, stores major/minor device numbers. So ls -li is literally reading the same directory table you studied in section 9.2, plus dereferencing the device inode.

Exam tip: if a question shows crw- vs brw- and asks “character or block?”, answer from the first letter alone: c → character, stream, no kernel block buffering; b → block, structured, kernel buffers.

9.7.3 Character Devices — Stream of Bytes, No Buffering

Character devices treat data as a stream of bytes. Examples given are main memory (RAM itself as a char device view), line printers, and terminals. The interface between the two ends is unstructured or raw, and there is no buffering provided at that interface. Whatever bytes are produced at one end should be consumed at the other with internal protocol handling to avoid overflow, but the kernel does not stage the bytes in a separate block buffer.

Think of it like a pipe with no tank: bytes flow as they come, and the sender and receiver must pace each other. If the receiver is slow, the sender must wait or bytes are lost — there is no pallet to park a whole block.

Character device traits.

  • Granularity: one byte at a time — a stream, not a block.
  • Structure: unstructured / raw — no record boundaries imposed by the kernel; a line printer receives raw bytes, a terminal sends keystrokes as they occur.
  • Buffering: none provided by the kernel at the device interface for block staging. Flow control is by protocol between ends (e.g., terminal handshake), not by a kernel block buffer.
  • Examples: RAM viewed as /dev/mem or /dev/zero (character view), line printer (/dev/lp0), terminal (/dev/tty).

The “no buffering” phrase means no block buffer that holds a full -byte pallet. Tiny per-character queues may exist in hardware or driver, but the semantics presented is stream with no kernel block staging.

Where the analogy breaks: real terminals do have small driver queues and line disciplines, but from the programmer's view read() on a character device returns whatever bytes are available now, not a full block.

9.7.4 Block Devices — Blocks, Structure and Kernel Buffering

Block devices move data in blocks rather than single bytes. Examples are disks and tape drives. The interface is structured: a block is read or a block is written. Because a whole block is moved at once — typically bytes was quoted — the other end may not be ready to accept or process it immediately. For that reason buffering is required, and that buffering is provided by the kernel.

Think of it like pallets versus letters: character moves letters one by one, block moves a whole pallet that must be staged until a forklift is free. The pallet is the -byte block; the staging area is the kernel's buffer cache. Without the buffer, a block read would have to wait for the consumer to be instantly ready, which at block granularity is wasteful.

Block device traits, side-by-side with character.

Dimension Character (stream) Block (structured)
Unit moved single byte, stream whole block, e.g., bytes
Interface unstructured / raw structured — “read block N / write block N”
Kernel buffering none at block interface kernel provides buffer cache
Examples RAM (char view), line printer, terminal disks (sda), tape drives
Random access generally sequential random — can seek to any block number

When to pick which: If you need random access and efficiency for large data, you want block — file systems sit on block devices for this reason. If you need low-latency byte-by-byte interaction (typing, printing), you want character — the stream matches the device's natural timing.

Visual intuition: picture the disk as a wall of 1024-byte pallets. read( block 5 ) lifts one pallet into the kernel buffer. If the process is not ready, the pallet waits on the staging floor (buffer cache) rather than being resent. Character's “letter” needs no staging floor — it is handed directly.

Exam note: A contrast question may ask for two differences and an example each, plus who provides buffering: kernel for block, no buffering for character. A safe two-point answer: (1) stream vs block, (2) kernel buffering is required for block and provided by kernel, no buffering for character — with examples line printer / terminal vs disk / tape.

Assumptions & scope.

  • Assumes: classic byte block quoted in lecture; modern disks often use blocks but the principle is the same.
  • Scope: buffering discussed is kernel block buffering for the structured interface. Character devices may still have driver-level queues, but those are not the block buffer meant here — do not claim “character has zero bytes buffered anywhere.”
  • Pitfall: tapes were listed as block devices in lecture — even though tapes feel stream-like, their transfer here is described as block-structured, so classify them as block in exam answers tied to this lecture.

Common traps.

  • Saying block devices need no buffering because blocks are efficient — the lecture says the opposite: because a whole -byte block is moved at once, buffering is needed and the kernel provides it.
  • Mixing up the -i in ls -li (shows inode number) with the -i in df -i (shows inode counts). Same letter, different command.

Recap — 9.7 in one line: The RAM → disk → tape → cache ladder sets the stage; ls -li /dev reveals c (character, stream, no kernel block buffering, e.g., RAM/line printer/terminal) versus b (block, structured -byte blocks, kernel buffering, e.g., disks/tapes) via the first letter, with the c/b plus inode view echoing the 16-byte directory and inode ideas from earlier.

Bridge: Once you can tell c from b, the next step is to read the numbers that appear beside those letters — major and minor — and the tools lsblk and fdisk that expose them for disks and partitions.

9.8 Device Identification — Major and Minor Numbers, lsblk and fdisk

9.8.1 Major as Driver, Minor as Instance

Every hardware item in Linux is seen as a device, and a device works through its device driver installed in the system. The driver is the interface that makes the hardware operate.

Two numbers, two questions.

  • Major device number indicates the type of the device and, by that, which driver is needed. When a device file is opened, Linux looks at its major number and forwards the call to the driver registered for that number. It is the “which kind” key.
  • Minor device number indicates the specific instance among possibly many of that kind. There can be more than one hard disk, more than one RAM region, more than one CPU. The minor number picks one instance from the list for that major. It is the “which one” key.

So major selects the driver family, minor selects the instance within the family. If major is the department, minor is the desk number inside that department.

A helpful rephrase from class: major is “what kind of device” and minor is “which one of that kind.” Opening /dev/sda with major routes to the SCSI/SATA disk driver; minor picks the whole disk, minor picks its first partition sda1, minor might pick a logical partition. The same majors appear under /dev as the MAJ:MIN pair you see in lsblk.

Visual intuition: picture a switchboard. The major number chooses the bank of operators (disk bank, loop bank, terminal bank). The minor number chooses the operator within that bank. The kernel looks up the major table once, then dispatches to the minor entry — two-level routing, not one flat number.

Why not just use the file name? Because names can vary (/dev/sda vs /dev/disk/by-uuid/...) but the major/minor pair is the kernel's stable dispatch key. The inode for the device-file stores this pair instead of block pointers — that is why ls -li /dev shows both inode number and major:minor together.

9.8.2 lsblk — List Block Devices

lsblk stands for list block and reports only block devices. Its columns were walked one by one:

  • NAME — device name such as sda, sda1, loop0.
  • MAJ:MIN — major and minor numbers, e.g., 8:0 for sda and 8:1 for sda1, 7:0 onward for loop devices.
  • RM — removable flag as 0 or 1, where 0 means non-removable and 1 means removable.
  • SIZE — size of the device, e.g., 100G for the main disk.
  • RO — read-only flag as 0 or 1, where 1 means read-only and 0 means read and write.
  • TYPE — type such as disk, part (partition), loop.
  • MOUNTPOINT — where it is mounted, if at all, such as /.

Worked reading — lsblk rows from the demo, decoded.

In the demo:

  • sda and sda1 shared major 8, which marks the disk driver family, with minors 0 and 1 marking whole disk and first partition. So 8:0 = disk driver, instance 0 (whole disk sda); 8:1 = same driver, instance 1 (first partition sda1).
  • Loop devices shared major 7 across about twenty-three instances such as loop0 through loop22, all showing RO 1 for read-only, RM 0 for non-removable, and type loop. So 7:07:22 = loop driver, instances 0–22 — the squashfs images you saw under df -T.
  • sda1 and sr0 showed RO 0 — readable and writable — while many loop entries showed RO 1 — read-only because they back compressed images.
  • One entry alone showed RM 1 to mark a removable disk drive — typically a USB stick or card reader.
  • Sizes: sda as 100G in lsblk aligns with sda1 as 98G100GB in df -h and fdisk -l — the small gap is partition overhead and rounding.

These numbers make the earlier abstract major/minor idea concrete: 8 routes to the disk driver, 1 after the colon picks the instance; 7 routes to the loop driver for the many squashfs mounts.

How to read a row in order: pick MAJ:MIN first (driver + instance), then RM/RO flags, then TYPE and MOUNTPOINT to see if it is mounted. Example mental voice: “sda1 8:1 0 100G 0 part / — disk driver instance 1, non-removable, read-write partition mounted as root.”

Counting sense-check: major 7 with 23 loop entries shows the minor space is not limited to 0/1 — minors enumerate every instance for that major.

Visual intuition: picture lsblk as a roster. Leftmost NAME is the human label, middle MAJ:MIN is the kernel routing pair, flags RM/RO are sticky notes (“removable?”, “read only?”), TYPE is the role, MOUNTPOINT is where it hangs in the tree. The demo's wall of loop0…loop22 with identical major 7 looks like a block of identical uniforms — same driver, many instances.

Scope — lsblk vs ls.

  • lsblk = only block devices, structured table with MAJ:MIN, RM, RO, TYPE. It will not show character devices — use ls -li /dev for those (c rows).
  • Scope: lsblk without flags shows the current topology; add -f for file system types if needed, but the lecture demo used the default columns listed above.

9.8.3 fdisk — Partition Tables and Details

fdisk is the partition tool known to Linux administrators for creating and editing partitions, but here it was used to inspect.

  • fdisk -l lists all disks with details. Without sudo it returned permission denied, which was the cue that a privileged view is needed. With sudo fdisk -l it printed for each disk: size such as 100GB, number of sectors, total bytes, sector size 512 bytes, I/O size 512 bytes, disk label type DOS, disk identifier such as a hex mark, and for sda1 fields like boot flag yes, start sector 2048, number of sectors, size, Id, and type such as Linux or the operating system installed.
  • sudo fdisk /dev/sda opens the interactive view for that disk. Options walked in the help output were:
  • m help,
  • a toggle bootable flag,
  • d delete a partition,
  • F list free unpartitioned space — which in the demo was 0 because the whole GB was already partitioned,
  • L list known partition types — including FAT12, XENIX, FAT16, FAT32, AIX, NTFS, ZFS and many others,
  • p print the partition table — which repeated the fdisk -l detail for sda alone,
  • v verify the partition table and i print information about a partition (device, boot, sectors and so on).

Worked inspection — fdisk outputs decoded.

The sudo fdisk -l run for sda and the p run inside sudo fdisk /dev/sda echoed the same line conceptually:

  • Disk sda: 100GB, sectors = million sectors, sector size bytes, I/O size bytes, label type DOS, identifier like 0xabc123.
  • Partition sda1: boot yes (the bootable flag is set), start sector 2048, number of sectors covering almost the whole disk, size ≈ 100G (or 98G as seen in df -h — same disk, different accounting), Id e.g., 83 for Linux, Type Linux.
  • Free space (F): 0 — no unpartitioned gap, because sda1 starts at 2048 and runs to the end. The 2048-sector offset is intentional alignment: sector is the MBR, sectors are reserved for alignment, so the first usable sector is . At bytes per sector, that reserves bytes MB before the partition.
  • Type list (L): includes FAT12, XENIX, FAT16, FAT32, AIX, NTFS, ZFS and many more — you scroll this list to set Id when creating a partition, but the exam only asks that L lists types and p prints the table.

Permission lesson: bare fdisk -lpermission denied. With sudo fdisk -l → full detail. That split is exactly the teaching moment: partition tables are privileged state, so inspection needs elevated rights.

The actual p run for sda echoed the earlier line: sda1 starts at 2048, occupies the bulk of the disk, shows as bootable, and carries its Id and system type. The number appears in both fdisk -l and lsblk narratives because the first partition is aligned to 1 MB — a modern default that avoids misaligned I/O on 4K-sector drives.

Real-world: In practice most day-to-day inspection uses lsblk and df; fdisk -l is the lower-level view an administrator pulls when planning partitions. You would run lsblk to see “what is mounted where,” df -h to see “how full,” and sudo fdisk -l to see “how the disk is sliced at sector granularity” including the -byte sector and start.

Pitfalls & exam traps.

  • Permission: fdisk -l without sudo → permission denied — always add sudo for the full table. That verbatim error is a teaching moment, not a typo.
  • Case matters again: fdisk -l (lowercase L) lists tables; inside fdisk /dev/sda the commands are single letters where case matters — p prints, F shows free space, L lists partition types, m shows help. Do not mix l and L.
  • Start 2048 is not magic: it is MB alignment padding. If a question asks why the first partition starts at 2048, answer “1 MB alignment / DOS label reserved area,” not “random offset.”
  • Sector size is two places: both “sector size 512” (logical) and “I/O size 512” appear; quote bytes for both as seen in the demo.

Recap — 9.8 in one line: Every device file routes by major (driver family — for disks sda/sda1, for loop loop0…loop22) and minor (instance — for whole disk, for sda1, for loops), visible as MAJ:MIN in lsblk with RM/RO/TYPE/MOUNTPOINT and at sector level as -byte sectors, start (≈1 MB), DOS label, 100GB size, p/F/L views, and permission denied without sudo in fdisk.

Bridge: With major/minor you can now read any /dev entry the way you read the 16-byte directory entry — a small integer key that the kernel expands into a full structure, just as an inode number expands into inode plus blocks.

Exam Guidance Summary

All exam-facing advice is gathered here, with inline markers also placed where the topics appear:

  • Syllabus for the mid-semester exam is exactly the files and directories material covered up to this session, including directory entry layout, inode links, path-to-inode translation via the namei algorithm, and the df / device identification commands. No new heavy theory beyond that was added in this session. The session was the last class before the mid-semester, with no class next week.
  • Question style is almost entirely commands. Expect to justify a specific output given a command, or to give the command plus option that produces a given output, with variations on flags. One or two very simple theory questions at most may appear, but the bulk is practical command use. Example patterns: “what does df -h show for /dev/sda1 at 98G/9.4G/84G 11%?”, “which flag of df shows inode counts?”, “what does ls -li /dev show for c vs b?”.
  • Study source is the slides and the options listed in the slides. There is no need to learn every flag a command supports; the flags shown in class — such as df -a, df -i, df -h, df -T, df -t/-x include/exclude, ls -li /dev, lsblk, fdisk -l and fdisk /dev/sda interactive F, L, p — are sufficient, even though that list is already long. For df remember 11% vs 100% interpretation; for lsblk remember major 8 for sda/sda1 and major 7 for loop, plus RM/RO and TYPE; for fdisk remember sector size 512 bytes, start sector 2048, disk size 100GB, DOS label, and the need for sudo.
  • Past paper — an old question paper with practice questions is or will be posted on the Taksila portal. That paper shows the pattern of asking for command and option and of interpreting listings such as the df table or lsblk rows. Practise by covering the output and predicting which flag produced it.
  • Specific concept spotlights carried from sections:
  • 14-character truncation risk (9.2): when first characters match the truncated names collide; the older file might be replaced — expect a theory justification.
  • Name + extension is one component (9.4): last component of /home/ubuntu/sp/file_name.txt is file_name.txt, not txt — expect a short question on component counting.
  • Working inode walk (9.4): left-to-right for absolute else , then 16 = 2 + 14 style lookup per component — expect to walk a relative path like exercises/sys_pgm1/test1/mydetails.pxt from /home/sys_pgm.
  • Disk operation counting (9.5): each level needs inode plus -byte data block; root , home , hello style numbers — expect to count transfers for a given path.
  • df flags (9.6): -a (all including dummy), -i (inode Inodes IUsed IFree), -h (human 98G/9.4G/84G 11%), -T (type ext4/squashfs/tmpfs), -t/-x (include/exclude type) plus case trap -t vs -T.
  • lsblk reading (9.8): major 8 disk vs 7 loop, minors 0/1/…/22, RM 0/1, RO 0/1, TYPE disk/part/loop, SIZE 100G; fdisk needs sudo, sector 512, start 2048.
  • Presentation hints heard earlier still apply: write assumptions, show work in tables where possible, and keep handwriting and formatting clear for grading, because a table makes the 16 = 2 + 14 split and the inode walk easier to award marks for. For namei, a three-column table — step, component , working inode — scores well.
  • Timing — this was the last class before the mid-semester, with no class next week. Wishes for the exams were extended to all courses.

Exam note: Do not spend time learning every fdisk partition type by heart; knowing that L lists types (including FAT12, XENIX, FAT16, FAT32, AIX, NTFS, ZFS) and that p and F show table and free space ( in demo because 100GB was fully partitioned) is the level tested. Focus on the flag meanings above and the 16 = 2 + 14 → offset geometry, which is a reliable two-mark derivation.

Key Industry Applications

Consolidated real-world connections for quick review:

  • Real-world: Everything-is-a-file underlies the way Linux exposes devices as files under /dev, listed with ls -li (c for character, b for block, - for regular, d for directory). The same open-read-write-close path handles /etc/passwd and /dev/tty — the uniformity keeps drivers small and tools reusable.
  • Real-world: The 14-byte fixed entry explains legacy systems and name-collision bugs on older Unix installations; modern ext4, xfs, ntfs and others use variable names to avoid truncation. When you see a vintage tar archive complain about truncated names, it is this limit at work; today’s 255-character limit is the variable-length successor.
  • Real-world: The namei walk powers every open and cat on absolute paths such as /etc/passwd and on relative paths such as exercises/sys_pgm1/test1/mydetails.pxt. Container runtimes and web servers do millions of such walks; understanding W = \text{root vs wd} and the left-to-right stepping explains why symlink attacks must be handled by path reprocessing.
  • Real-world: squashfs appears in live USB images and container layers as a compressed read-only file system; tmpfs powers /tmp and fast staging where memory speed matters. The df -T row that shows squashfs at 100% is not an alarm — it is a fixed image that is full by design, just like a container layer.
  • Real-world: lsblk major 8 for disks and major 7 for loops is the standard driver numbering seen on any Linux host; RM and RO flags let scripts distinguish removable or read-only mounts. Fleet monitoring parses 8:0 vs 7:0…7:22 to separate real storage from loop-backed snaps; RO 1 marks the squashfs images that should never be written.
  • Real-world: fdisk with sector size bytes and start sector is the usual alignment seen on a DOS label 100GB disk with a single sda1 partition. The MB gap is the modern alignment rule that avoids 4K-sector penalties — every cloud VM image you launch inherits it.
  • Real-world: The inode-plus--byte-block counting explains cold-start latency: the first cat after boot touches root → home → file inode+block pairs; the second cat hits the buffer cache and avoids disk. Capacity planning uses df -i (inodes free) alongside df -h (blocks free) because a mail spool can exhaust inodes with tiny files while blocks look healthy.

These links stay within the lecture’s scope — each mirrors a flag, number, or walk you already traced: 16 = 2 + 14 at byte offsets , working inode , block , 98G/9.4G/84G 11% and 100% loop rows, MAJ:MIN 8:0/8:1 vs 7:0…7:22, sector and start .

SP Lecture 9 notes · Files, Directories and Device Management

Systems Programming· postgraduate· 2026-08-20

Sections Breakdown

1Files and Directories — The Unix View

Unix treats every object as a byte-sequence file with open/read/write/close and directories as tables that map names to inode numbers.

2Unix Directory Entry Structure

Classic Unix directory is a table of 16-byte entries (2 bytes inode number + 14 bytes name) at offsets 0,16,32...; names beyond 14 chars are truncated and can collide; . / .. are fixed entries.

3The Link Between Name and Content — Inodes

The 2-byte inode number in the directory entry is the only name→content bridge (called a link); the number points to the larger inode structure which fans out to data blocks.

4Path Name to Inode Translation — The namei Algorithm

namei converts a path string to an inode by initializing working inode W to root (absolute) or wd (relative) and walking components left-to-right with lookup, release and hop; symlink triggers path replacement.

5Disk Access Walkthrough — From cat Command to Data Blocks

First cat on an absolute path does disk→memory for each level's inode plus 1K=1024-byte data block, looking up each component (root 2, home 13, hello 53) before file bytes arrive; caching removes repeats.

6Disk Usage Inspection — The df Family

df reports filesystem use in 1K-blocks with columns Filesystem/Used/Available/Use%; flags -a/-i/-h/-T/-t/-x reshape to dummy, inode, human (98G/9.4G/84G 11%), type (ext4/squashfs/tmpfs) views.

7Unix Input/Output — Character versus Block Devices

Memory ladder RAM/disk/tape/cache sets stage; ls -li /dev shows c (character stream, no kernel block buffering: RAM/printer/terminal) vs b (block structured 1K=1024 bytes, kernel buffered: disks/tapes) via first letter.

8Device Identification — Major and Minor Numbers, lsblk and fdisk

Major selects driver family (8 disk sda/sda1, 7 loop loop0..22) and minor selects instance within; lsblk shows MAJ:MIN RM RO TYPE MOUNTPOINT; fdisk shows 512-byte sectors, start 2048, 100GB DOS label, needing sudo.

9Exam Guidance Summary

Mid-sem exam covers files/directories up to this lecture; almost all questions are command/flag prediction (df/lsblk/fdisk/namei) with old paper on Taksila portal.

10Key Industry Applications

Real-world ties: everything-is-a-file to /dev, 16=2+14 to legacy bugs, namei to every open/cat, df types to live images/tmpfs, lsblk majors to fleet parsing, fdisk alignment to cloud images.

Postgraduate students in Systems Programming

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Files and Directories — The Unix View

Must-know: Everything-is-a-file lets the same four calls handle regular files, directories and devices; a directory holds only name→inode maps, not file bytes.

⚠️ Top pitfall: Thinking a directory contains file bytes instead of just the name-to-inode link; mixing fixed-length classic limit with modern variable names.

Self-check: Why can you list /dev and /home with the same ls logic?

Connects to: 9.2, 9.3

Unix Directory Entry Structure

Must-know: 16 = 2 + 14 and offset_k = 16*k; 14-char truncation causes collision when first 14 chars match; . is current, .. is parent, / is root.

and

⚠️ Top pitfall: Thinking long names are stored fully; under old 16-byte layout only first 14 chars kept and two files with same prefix collide.

Self-check: What byte offset is entry k and what happens to a 20-char name in the classic table?

Connects to: 9.3, 9.4

The Link Between Name and Content — Inodes

Must-know: File name is a link (bridge) to a 2-byte inode number; the inode structure holds mode, owner, times, size, link count and block pointers to data blocks.

⚠️ Top pitfall: Confusing inode number (2-byte table entry) with inode structure (larger record with pointers); or mixing this minimal link with hard/soft link.

Self-check: What is stored in the 2-byte field vs what is in the inode structure?

Connects to: 9.2, 9.4

Path Name to Inode Translation — The namei Algorithm

Must-know: namei: W = root if p starts with / else wd; while c in p: read dir of W, match c to 14-byte name, get N, release W, W←N; symlink replaces prefix and reprocesses.

⚠️ Top pitfall: Treating file extension as separate component; thinking walk is right-to-left; forgetting release step order.

Self-check: For /home/ubuntu/sp/file_name.txt what is the last component and what is W after matching home?

Connects to: 9.5, 9.2

Disk Access Walkthrough — From cat Command to Data Blocks

Must-know: Each directory level costs inode + data block (1K=1024 bytes) fetches; cat /home/spiderman/hello.txt with root 2, home 13, hello 53 needs 8 transfers (4 levels ×2) including root and file.

and

⚠️ Top pitfall: Forgetting root's pair or the file's own inode+block when counting; thinking cached second cat costs same as cold first cat.

Self-check: How many inode and block fetches for cat /home/spiderman/hello.txt with no cache?

Connects to: 9.4, 9.6

Disk Usage Inspection — The df Family

Must-know: df shows 1K-blocks; -a adds dummy, -i shows Inodes, -h shows 98G/9.4G/84G 11% human, -T adds Type (ext4/squashfs/tmpfs), -t/-x filter by type, path narrows to one mount.

⚠️ Top pitfall: Mixing -t (filter include) with -T (show type column); thinking 100% squashfs means disk full by error rather than sized image.

Self-check: Which df flag shows human sizes and what does df -i show instead of blocks?

Connects to: 9.5, 9.8

Unix Input/Output — Character versus Block Devices

Must-know: ls -li /dev: first char c=character (stream, no kernel block buffering) vs b=block (structured blocks, kernel buffers); char examples RAM/printer/terminal, block examples disk/tape.

⚠️ Top pitfall: Claiming character has zero buffering anywhere or that block needs no buffering; tapes are block in this lecture's classification.

Self-check: How to tell block vs character with ls and what buffering rule applies to each?

Connects to: 9.8, 9.1

Device Identification — Major and Minor Numbers, lsblk and fdisk

Must-know: Major 8 = disk driver (sda 8:0, sda1 8:1), major 7 = loop (loop0 7:0 .. 22); lsblk RM/RO/TYPE; fdisk -l needs sudo, sector 512, start 2048, F free 0, L list types, p print.

⚠️ Top pitfall: Forgetting sudo for fdisk -l (permission denied); mixing fdisk -l vs inside L vs p/F; mistaking start 2048 as random not 1MB alignment.

Self-check: What do 8:1 and 7:5 mean in lsblk and why does fdisk without sudo fail?

Connects to: 9.7, 9.6

Exam Guidance Summary

Must-know: Syllabus is files/directories through this lecture; question style is command-output justification; study slides flags and Taksila old paper.

⚠️ Top pitfall: Learning every fdisk type instead of just L lists, p prints, F shows free 0.

Self-check: Which df flag combo gives human size plus type for ext4 only?

Connects to: 9.2, 9.4, 9.6

Key Industry Applications

Must-know: Each lecture concept maps to daily ops: /dev listing, ext4 vs squashfs vs tmpfs, namei for containers, lsblk major parsing, fdisk 1MB alignment.

⚠️ Top pitfall: Treating 100% squashfs as error vs sized image.

Self-check: Why does a second cat cost less than the first?

Connects to: 9.1, 9.5, 9.8

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.