CSS 430
Final Project: File System
Due Date: See the syllabus
In this project, you will build a Unix-like file system on the ThreadOS. Through the use of the file system, user programs will now be able to access persistent data on disk by way of stream-oriented files rather than the more painful direct access to disk blocks with rawread() and rawrite().
Given the size of this project, you are encouraged to form a team of two or three students. In working with your partner(s), the workload should divided equally across all members. This project requires much more time to be completed than the previous four assignments. Schedule and pace your design and coding work accordingly. If you are very confident of working independently you may do so.
Use ThreadOS' original files, (i.e., the original classes) except for Kernel.java. For Kernel.java, use the one you got in the assignment 4, (i.e., Kernel_org.java). For simplicity, you should just re-download all readable files from the ThreadOS directory to get started.
The file system should provide user threads with the system calls that will allow them to format, to open, to read from , to write to, to update the seek pointer of, to close, to delete, and to get the size of their files.
For simplicity, the file system being created will consiste of a single level. The "/" root directory is predefined by the file system and permanently available for user threads to store their files. No other directories are provided by the system and created by users dynamically.
Each user thread needs to keep track of all files it has opened. For this purpose, it should maintain a table of those open files in its TCB. This table is called a user file descriptor table. It has 32 entries. Whenever a thread opens a file, it must allocate to this file a new table entry, termed a file descriptor. Each file descriptor includes the file access mode and the reference to the corresponding file (structure) table entry. The file access mode indicates "read only", "write only", "read/write", or "append". The file (structure) table is a system-maintained table shared among all user threads, each entry of which maintains the seek pointer and the inode number of a file. Depending on the access mode, the seek pointer is set to the first or the tail of the file, and keeps track of a next position to read from and to write to the file. It is entirely possible for one thread to open the same file many times, thus having several entries in the corresponding TCB's user file descriptor table. Although each of these user file descriptor table entries refer to a different file (structure) table entry with its own seek pointer, all of them eventually points to the same inode.
The file system you will implement must provide the following eight system calls.
Unless specified otherwise, each of the above system calls returns -1 when detecting an error.
The first disk block, block 0, is called the superblock. It is used to describe
class Superblock { | |
public int totalBlocks; // the number of disk blocks | |
public int totalInodes; // the number of inodes | |
public int freeList; // the block number of the free list's head | |
public SuperBlock( int diskSize ) { | |
... | |
} | |
... | |
} |
Starting from the blocks after the superblock will be the inode blocks. Each inode describes one file. Our inode is a simplified version of the Unix inode. It includes 12 pointers of the index block. The first 11 of these pointers point to direct blocks. The last pointer points to an indirect block. In addition, each inode must include (1) the length of the corresponding file, (2) the number of file (structure) table entries that point to this inode, and (3) the flag to indicate if it is unused (= 0), used(= 1), or in some other status (= 2, 3, 4, ...). 16 inodes can be stored in one block.
public class Inode { | |
private final static int iNodeSize = 32; // fix to 32 bytes | |
private final static int directSize = 11; // # direct pointers | |
public int length; // file size in bytes | |
public short count; // # file-table entries pointing to this | |
public short flag; // 0 = unused, 1 = used, ... | |
public short direct[] = new short[directSize]; // direct pointers | |
public short indirect; // a indirect pointer | |
Inode( ) { // a default constructor | |
length = 0; | |
count = 0; | |
flag = 1; | |
for ( int i = 0; i < directSize; i++ ) | |
direct[i] = -1; | |
indirect = -1; | |
} | |
Inode( short iNumber ) { // retrieving inode from disk | |
// design it by yourself. | |
} | |
int toDisk( short iNumber ) { // save to disk as the i-th inode | |
// design it by yourself. | |
} | |
} |
You will need a constructor that retrieves an existing inode from the disk into the memory. Given an inode number, termed inumber, this constructor reads the corresponding disk block, locates the corresponding inode information in that block, and initializes a new inode with this information.
The system must avoid any inode inconsistency among different user threads. There are two solutions to maintain the inode consistency:
The "/" root directory maintains each file in a different directory entry that contains its file name (maximum 30 characters; 60 bytes in Java) and the corresponding inode number. The directory receives the maximum number of inodes to be created, (i.e., thus the max. number of files to be created) and keeps track of which inode numbers are in use. Since the directory itself is considered as a file, its contents are maintained by an inode, specifically inode 0. This can be located in the first 32 bytes of the disk block 1.
Upon booting ThreadOS, the file system instantiates the Directory class as the root directory through its constructor, reads the file from the disk that can be found through the inode 0 at 32 bytes of the disk block 1, and initializes the Directory instnace with the file contents. Prior to shutdown, the file system must write back the Directory information onto the disk. The methods bytes2directory( ) and directory2bytes will initialize the Directory instance with a byte array read from the disk and converts the Directory instance into a byte array that will be thereafter written back to the disk.
public class Directory { | |
private static int maxChars = 30; // max characters of each file name | |
// Directory entries | |
private int fsize[]; // each element stores a different file size. | |
private char fnames[][]; // each element stores a different file name. | |
public Directory( int maxInumber ) { // directory constructor | |
fsizes = new int[maxInumber]; // maxInumber = max files | |
for ( int i = 0; i < maxInumber; i++ ) | |
fsize[i] = 0; // all file size initialized to 0 | |
fnames = new char[maxInumber][maxChars]; | |
String root = "/"; // entry(inode) 0 is "/" | |
fsize[0] = root.length( ); // fsize[0] is the size of "/". | |
root.getChars( 0, fsizes[0], fnames[0], 0 ); // fnames[0] includes "/" | |
} | |
public void bytes2directory( byte data[] ) { | |
// assumes data[] received directory information from disk | |
// initializes the Directory instance with this data[] | |
} | |
public byte[] directory2bytes( ) { | |
// converts and return Directory information into a plain byte array | |
// this byte array will be written back to disk | |
// note: only meaningfull directory information should be converted | |
// into bytes. | |
} | |
public short ialloc( String filename ) { | |
// filename is the one of a file to be created. | |
// allocates a new inode number for this filename | |
} | |
public boolean ifree( short iNumber ) { | |
// deallocates this inumber (inode number) | |
// the corresponding file will be deleted. | |
} | |
public short namei( String filename ) { | |
// returns the inumber corresponding to this filename | |
} | |
} |
The file system maintains the file (structure) table shared among all user threads. When a user thread opens a file, it follows the sequence listed below:
The file (structure) table entry should be:
public class FileTableEntry { // Each table entry should have | |
public int seekPtr; // a file seek pointer | |
public final Inode inode; // a reference to its inode | |
public final short iNumber; // this inode number | |
public int count; // # threads sharing this entry | |
public final String mode; // "r", "w", "w+", or "a" | |
public FileTableEntry ( Inode i, short inumber, String m ) { | |
seekPtr = 0; // the seek pointer is set to the file top | |
inode = i; | |
iNumber = inumber; | |
count = 1; // at least on thread is using this entry | |
mode = m; // once access mode is set, it never changes | |
if ( mode.compareTo( "a" ) == 0 ) // if mode is append, | |
seekPtr = inode.length; // seekPtr points to the end of file | |
} | |
} |
The file (structure) table is defined as follows:
public class FileTable { | |
private Vector table; // the actual entity of this file table | |
private Directory dir; // the root directory | |
public FileTable( Directory directory ) { // constructor | |
table = new Vector( ); // instantiate a file (structure) table | |
dir = directory; // receive a reference to the Director | |
} // from the file system | |
// major public methods | |
public synchronized FileTableEntry falloc( String filename, String mode ) { | |
// allocate a new file (structure) table entry for this file name | |
// allocate/retrieve and register the corresponding inode using dir | |
// increment this inode's count | |
// immediately write back this inode to the disk | |
// return a reference to this file (structure) table entry | |
} | |
public synchronized boolean ffree( FileTableEntry e ) { | |
// receive a file table entry reference | |
// save the corresponding inode to the disk | |
// free this file table entry. | |
// return true if this file table entry found in my table | |
} | |
public synchronized boolean fempty( ) { | |
return table.isEmpty( ); // return if table is empty | |
} // should be called before starting a format | |
} |
Each user thread maintains a user file descriptor table in its own TCB. Every time it opens a file, it allocates a new entry table including a reference to the corresponding file (structure) table entry. Whenever a thread spawns a new child thread, it passes a copy of its TCB to this child which thus has a copy of its parent's user file descriptor table. This in turn means the both the parent and the child refer to the same file (structure) table entries and eventually share the same files.
public class TCB { | |
private Thread thread = null; | |
private int tid = 0; | |
private int pid = 0; | |
private boolean terminate = false; | |
// User file descriptor table: | |
// each entry pointing to a file (structure) table entry | |
public FileTableEntry[] ftEnt = null; | |
public TCB( Thread newThread, int myTid, int parentTid ) { | |
thread = newThread; | |
tid = myTid; | |
pid = parentTid; | |
terminated = false; | |
// The following code is added for the file system | |
ftEnt = new FileTableEntry[32]; | |
for ( int i = 0; i < 32; i++ ) | |
ftEnt[i] = null; // all entries initialized to null | |
// fd[0], fd[1], and fd[2] are kept null. | |
} | |
} |
Note that the file descriptors 0, 1, and 2 are reserved for the standard input, output, and error, and thus those entires must remain null.
Design and implement the file system in ThreadOS as specified above. Run Test5.java which is found in the ThreadOS directory. This test program will test the functionality of every feature and system call listed above and check the data consistency of your file system. Some of the things that are tested include:
gradeguide5.txt is the grade guide for the final project.
This website could answer your questions. Please click here before emailing the professor :-).