(If you are curious how an physical hard disk works, take a look at this Scientific American diagram. We'll focus on the higher levels.)
Given the following file system, diagram the data stored on the disk.
The filesystem:
This is on a 4MB disk with 1KB blocks (4096 blocks total). The disk
has only one partition, used for the cse451fs v1.0 with structure
shown above; the directory entries for
/ -+- etc/ -+- passwd
| `- fstab
`- bin/ -+- sh
`- date
.
and
..
are not shown. The file sizes are as follows:
|
From the description
of cse451fs, we know the on-disk data structures will have the
general layout:
But how large will each portion of the disk be? The boot and
superblock are defined to be 1 block each. Reading
mkfs.cse451fs.c
(in the project file distribution)
reveals that there will be 1 inode for every 3 blocks on the disk,
meaning we will have 4096/3 = 1365 inodes. This requires
1365/CSE451_INODES_PER_BLOCK
= 85 inode blocks (inodes
are 64 bytes each). (If we had a larger disk, the number of inodes
might be limited by the size of the inode map in the superblock - see
below.) This leaves 4096 - 2 - 85 = 4009 blocks for the data map and
data blocks.
Again from mkfs.cse451fs.c
, we see that the
numDataMapBlocks = 4009 / (BLOCK_SIZE*8) + 1 = 1 (rounds down). This
means we have 4008 data blocks. We can now draw the picture with
sizes (not to scale; click on a segment to examine it):
Boot Block
On many architectures, including i386, the first block of a bootable
disk has to contain the bootloader executable. Since it's not part
of the filesystem, we won't discuss it here, though the curious may
see the bootloader description for
more information.
Superblock
The superblock has the master information about the filesystem. On
cse451fs, the block also contains the inode map after the filesystem
meta-data. Zooming in on it, it would look like:
From cse451fs.h
, the superblock record
has the following format:
The superblock field values for our little disk would be:
struct cse451_super_block {
__u16 s_nNumInodes; // inode map is tail of superblock
__u16 s_nDataMapStart; // block # of first data map block
__u32 s_nDataMapBlocks; // data map size, in blocks
__u32 s_nInodeStart; // block # of first inode block
__u32 s_nNumInodeBlocks; // number of blocks of inodes
__u32 s_nDataBlocksStart; // block # of first data block
__u32 s_nDataBlocks; // number of blocks of data
__u32 s_nBusyInodes; // number of inodes in use
__u16 s_magic; // magic number
char s_imap[0]; // name for inode map
};
|
Following the superblock record (but on the same disk block) is the
inode map. The inode map tracks which inodes are currently in use.
Looking at the filesystem, we see 3 directories and 4 files, for a
total of 7 inodes required. A possible inode map is thus the low 7
bits in the 33rd byte of the superblock set to 1, with the remaining
7937 bits in the inode map set to 0.
Data Map
The data map tracks which data blocks are currently in use. Like the
inode map, it is a bit array. Our data block requirments are as
follows:
|
Again, one possible data map would be the first 20 bits set, and the
remaining bits all 0s.
Inode Blocks
There are 85 inode blocks allocated, but we've only got 7
inodes. Assuming the inode map described above is used, that means the first 7 inodes
(which fit on a single block, since 7*64<1024) are valid.
From cse451fs.h
, the inodes have the following format:
Values would be as follows. The order I've choosen to assign inodes to
dirs and files is arbitrary except for the first inode, which must
always be "/". I've abbreviated CSE451_DIRENTRYSIZE to ENTRYSIZE
and made assumptions about permissions and ownership. Also note that
the dir/file names are included just for reference; they are not
stored in the inodes.
struct cse451_inode {
__u16 i_mode;
__u16 i_nlinks;
__u16 i_uid;
__u16 i_gid;
__u32 i_filesize;
__u32 i_datablocks[CSE451_NUMDATAPTRS];
};
|
On disk, these would be layed out as a sequence of
cse451_inode
s.
Since data block 88 represents a directory, it contains an array of
struct cse451_dir_entry
s. Again from
cse451fs.h
, the format is (CSE451_MAXDIRNAMELENGTH is
30):
The contents of block 88 would look like (the values in doublequotes
are c strings, null-terminated. Note that despite the short length of
the names, each element is still the full 32 bytes):
struct cse451_dir_entry {
__u16 inode;
char name[CSE451_MAXDIRNAMELENGTH];
};
|
Once again, the ordering of entries is somewhat arbitrary (the . and .. entries will always be first). The 0/0 entry terminates the entry array. After this terminator, the data in the block is undefined.
The other two directory data blocks would be very similar. The "." entries would point to the inode for the directory, and the ".." to the inode for their parent (in this case, inode 1). Note that the directory listing does not distinguish between files and subdirs; that's left to the meta-data stored in the inode.