CS110: Principles of Computer Systems
Winter 2021-2022
Stanford University
Instructors: Nick Troccoli and Jerry Cain
Unix v6 Filesystem design, part 1 (files)
Unix v6 Filesystem design, part 2 (directories)
Interacting with the filesystem from our programs
assign2: implement portions of a filesystem!
A hard disk is sector-addressable: cannot read/write just one byte of data - can only read/write "sectors" of data. (we will work with a sector size of 512; but size is determined by the physical drive).
void readSector(size_t sectorNumber, void *data);
void writeSector(size_t sectorNumber, const void *data);
Let's imagine that the hard disk creators provide software to let us interface with the disk.
This is all we get! We have to layer functions on top of these to ultimately allow us to read, write, lookup, and modify entire files.
Let's imagine that the hard disk creators provide software to let us interface with the disk.
void readSector(size_t sectorNumber, void *data);
void writeSector(size_t sectorNumber, const void *data);
char text[512];
readSector(5, text);
// Now text contains the contents of sector 5
int nums[512 / sizeof(int)];
readSector(6, nums);
// Now nums contains the contents of sector 6
How do we use readSector? Here are some examples:
Let's imagine that the hard disk creators provide software to let us interface with the disk.
void readSector(size_t sectorNumber, void *data);
void writeSector(size_t sectorNumber, const void *data);
char text[512] = "Hello, world!";
writeSector(5, text);
// Now sector 5 contains "Hello, world!" (and \0) followed by garbage values.
int nums[512 / sizeof(int)];
readSector(6, nums);
nums[15] = 22;
writeSector(6, nums);
// Now sector 6 is updated to change its 16th number to be 22.
How do we use writeSector? Here are some examples:
We want to read/write file on disk and have them persist even when the device is off.
This may include operations like:
We will use the Unix Version 6 Filesystem to see an example of filesystem design.
A filesystem generally defines its own unit of data, a "block," that it reads/writes at a time.
Pros of larger block size? Smaller block size?
Example: the block size could be defined as two sectors
The Unix V6 Filesystem defines a block to be 1 sector (so they are interchangeable).
Two types of data we will be working with:
Key insight: both of these must be stored on the hard disk. Otherwise, we will not have it across power-offs! (E.g. without storing metadata we would lose all filenames after shutdown). This means some blocks must store data other than payload data.
Two types of data we will be working with:
Key insight: both of these must be stored on the hard disk. Otherwise, we will not have it across power-offs! (E.g. without storing metadata we would lose all filenames after shutdown). This means some blocks must store data other than payload data.
Two types of data we will be working with:
Design questions to consider:
Design questions to consider:
Problem: how do we know what block numbers store a given file's data?
Two types of data we will be working with:
We need somewhere to store information about each file, such as which block numbers store its payload data. Ideally, this data would be easy to look up as needed.
Problem: how do we know what block numbers store a given file's data?
An inode ("index node") is a grouping of data about a single file. It stores things like:
struct inode {
uint16_t i_mode; // bit vector of file
// type and permissions
uint8_t i_nlink; // number of references
// to file
uint8_t i_uid; // owner
uint8_t i_gid; // group of owner
uint8_t i_size0; // most significant byte
// of size
uint16_t i_size1; // lower two bytes of size
// (size is encoded in a
// three-byte number)
uint16_t i_addr[8]; // device addresses
// constituting file
uint16_t i_atime[2]; // access time
uint16_t i_mtime[2]; // modify time
};
The full definition of an inode has much more; but we focus just on size (i_size0 and i_size1) and block numbers (i_addr[8]). An inode is 32 bytes big in this filesystem.
The filesystem stores inodes on disk together in the inode table for quick access.
Filesystem goes from filename to inode number ("inumber") to file data. (Demo time!)
We need inodes to be a fixed size, and not too large. So how should we store the block numbers? How many should there be?
The inode design here has space for 8 block numbers. But we will see later how we can build on this to support very large files.
Let's say we have an inode with the following information (remember 1 block = 1 sector = 512 bytes):
file size: 600 bytes
block numbers: 56, 122
How many bytes of block 56 store file payload data?
How many bytes of block 122 store file payload data?
Let's say we have an inode with the following information (remember 1 block = 1 sector = 512 bytes):
file size: 2000 bytes
block numbers: 56, 122, 45, 22
Which block number stores the 2000th byte of the file?
Which block number stores the 1500th byte of the file?
Bytes 0-511 reside within block 56, bytes 512-1023 within block 122, bytes 1024-1535 within block 45, and bytes 1536-1999 at the front of block 22.
Let's imagine that the hard disk creators provide software to let us interface with the disk.
void readSector(size_t sectorNumber, void *data);
void writeSector(size_t sectorNumber, const void *data);
typedef struct inode {
uint16_t i_addr[8]; // device addresses
// constituting file
...
} inode;
// Loop over each inode in sector 2
inode inodes[512 / sizeof(inode)];
readSector(2, inodes);
for (size_t i = 0; i < sizeof(inodes) / sizeof(inodes[0]); i++) {
...
}
How do we access inodes? Here are some examples:
Problem: with 8 block numbers per inode, the largest a file can be is 512 * 8 = 4096 bytes (~4KB). That definitely isn't realistic!
Let's say a file's payload is stored across 10 blocks:
45, 42, 15, 67, 125, 665, 467, 231, 162, 136
Assuming that the size of an inode is fixed, where can we put these block numbers?
Solution: let's store them in a block, and then store that block's number in the inode!
Let's say a file's payload is stored across 10 blocks:
451, 42, 15, 67, 125, 665, 467, 231, 162, 136
Solution: let's store them in a block, and then store that block's number in the inode! This approach is called indirect addressing.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
The quick brown fox jumped over the...
Design questions:
Indirect addressing is useful, but means that it takes more steps to get to the data, and we may use more blocks than we need.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
The quick brown fox jumped over the...
Design questions:
Indirect addressing is useful, but means that it takes more steps to get to the data, and we may use more blocks than we need.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
The quick brown fox jumped over the...
The Unix V6 filesystem uses singly-indirect addressing (blocks that store payload block numbers) just for large files.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
The quick brown fox jumped over the...
Let's assume for now that an inode for a large file uses all 8 block numbers for singly-indirect addressing. What is the largest file size this supports? Each block number is 2 bytes big.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
The quick brown fox jumped over the...
8 block numbers in an inode x
256 block numbers per singly-indirect block x
512 bytes per block
= ~1MB
Let's say we have an inode with the following information (remember 1 block = 1 sector = 512 bytes, and block numbers fit i):
file size: 200,000 bytes
block numbers: 56, 122
Which singly-indirect block stores the block number holding the 150,000th byte of the file?
Bytes 0-131,071 reside within blocks whose block numbers are in block 56. Bytes 131,072 (256*512) - 199,999 reside within blocks whose block numbers are in block 122.
Problem: even with singly-indirect addressing, the largest a file can be is 8 * 256 * 512 = 1,048,576 bytes (~1MB). That still isn't realistic!
Solution: let's use doubly-indirect addressing; store a block number for a block that contains singly-indirect block numbers.
Solution: let's use doubly-indirect addressing; store a block number for a block that contains singly-indirect block numbers.
inode
filesize: 5000
blocknums: 450
...
block 450
451,42,15,67,
125,665,467,
231,162,136
block 450
451,42,15,67,
125,665,467,
231,162,136
block 451
55,34,12,44,...
block 55
The quick brown fox jumped over the...
Allows even larger files, but data takes even more steps to access. How do we employ this idea?
The Unix V6 filesystem uses singly-indirect addressing (blocks that store payload block numbers) just for large files. It also uses doubly-indirect addressing (blocks that store singly-indirect block numbers).
In other words; a file can be represented using at most 256 + 7 = 263 singly-indirect blocks. The first seven are stored in the inode. The remaining 256 are stored in a block whose block number is stored in the inode.
An inode for a large file stores 7 singly-indirect block numbers and 1 doubly-indirect block number. What is the largest file size this supports? Each block number is 2 bytes big.
263 singly-indirect block numbers total x
256 block numbers per singly-indirect block x
512 bytes per block
= ~34MB
An inode for a large file stores 7 singly-indirect block numbers and 1 doubly-indirect block number. What is the largest file size this supports? Each block number is 2 bytes big.
OR:
(7 * 256 * 512) + (256 * 256 * 512) ~ 34MB
(singly indirect) + (doubly indirect )
Better! still not sufficient for today's standards, but perhaps in 1975. Moreover, since block numbers are 2 bytes, we can number at most 2^16 - 1 = 65,535 blocks, meaning the entire filesystem can be at most 65,535 * 512 ~ 32MB.
Not all the block numbers may be used. E.g.
Assume we have a large file with inumber 16. How do we find the block containing the start of its payload data? How about the remainder of its payload data?
Assume we have a large file with inumber 16. How do we find the block containing the start of its payload data? How about the remainder of its payload data?
Next time: how can we update our filesystem design to support directories?