CS 372 Operating Systems
Lab 4 Programming Assignment:
File Systems
Clarifications
and corrections are in pink.
- Lab 4 distribution (jar file which should
be unpacked by jar xvf lab4.jar.)
- This jar file contains all of the support code, and two test
cases
(called simple0.tr and simple1.tr). As discussed below, you need
to
write the innards of Mkfs.java, VFS.java, VFSSync.java,
BufferCache.java BufferCacheSync.java and all of
SJF.java.
- Test Case of some trace files
- An amusing story to help buttress flagging
motivation.
Due: Tuesday Dec 1, 11:59pm
Lab Purpose
In this lab you
will build a small file system. The primary purpose of the lab is
to
familiarize you with the implementation techniques for file systems,
including on-disk and in-memory data structures.
The second goal of
this lab is to tie together three major topics of the course:
scheduling, managing memory, and file systems. Your
simulated OS will have a scheduler because processes block
when they access the disk. These schedulers will be very similar
to the schedulers you wrote in lab 1. You manage memory because
you must
buffer disk blocks in memory
pages, but you won't be able to buffer all of the disk, and so you must
implement a memory replacement policy. Finally, all this work is in the
context of implementing a file system with a single-level directory
structure.
The third goal is
to give you experience learning and modifying a large software project.
If you work in industry, you will often need to come up to speed
on a large project with lots of code and spotty documentation.
Our documentation, while not perfect, is above average.
When you first approach the lab much of your time will just be
spent getting your head around the code that exists. That takes a
while, so start early.
A final benefit
is
that the
program infrastructure we give you might contain interesting
programming idioms or design patterns that you can use in future
projects. Please look around the code.
Start this lab
early. It is hard.
Trace Files
Before diving
into the design specification for your file system, lets look at how
you use the system. The system reads a trace file which contains
system calls made by simulated processes. The details of how the file
is read
and how events are fed to your simulated OS is handled by the code that
we provide. Your system responds to these user requests,
schedules disk operations and deals with disk interrupts.
You should always make sure your code is compiled. Eclipse takes care
of that for you, but javac *.java is simple and effective from
the command line.
The workflow for the simulator is to create a simulated disk, run a
trace file on the simulated disk, and then check the simulated disk and
the read check file for correctness. The read check file
records the values of every (non-zero) read request, so you can see the
earliest
point at which you read bad data. Here is an example:
java Mkfs
test_disk 128k Make a simulated disk called,
"test_disk", that is 128KB
java
ProcessTrace
Run the trace simple0.tr on test_disk (both values
are defaults)
This is explained in greater depth below. The Mkfs.java we give
you does not work, so your first task is to complete its
implementation. When we evaluate your project, we will diff the
final disk image against the final image produced by our
implementation starting from the same clean file system (we will also
check your Mkfs to make sure it creates a valid file system). Similarly we will
diff the read check files. Any deviation will result in losing points.
Processing the
Trace
The pid of the initial process (called init) is
zero. Like a real operating system, as part of its
initialization, the OS code we gave you will start the init process. A
well formed trace file starts with an action of process 0.
After the first action, you can treat process 0 like any other
process, e.g., it can kill itself.
After your code processes all events in the trace file, the
ProcessTrace class calls the OS's shutdown method. This
method should clean up any loose ends, like flushing all disk buffers
that reside in memory back onto the disk itself.
0 proc_create(100)
100 create(/fileA)
100 open(/fileA, O_RDWR) = 3
This is the
start of simple0.tr. The individual calls will be
explained below, but the init process (pid=0) starts a process, with identifier 100.
Process 100 creates a file (called fileA, located in directory
/). Then process 100 opens the file, with read and write
permission, and file descriptor 3 will refer to the open file. Each entry
in the trace file is prefixed by the process id that performs
the system call. The return value is depicted on the right, by
the "= 3" for the open. See the sample trace files for more
examples.
Supported System
Calls
Your system must
support the following system calls. The prototypes are
conceptual. See the trace file and the code for the concrete
details.
create(String name)
This system call creates a simulated file with the
given name. A process must create a file
before opening it. Unix implements create by overloading the
open system call and setting the O_CREATE flag to true. Having a
separate create call will simplify your code. Specifically,
create might have to allocate an inode, while open never
will. Open does not allocate any data for a file (doing so
would go against the goal of supporting many small files, including
zero-length files). Report an error if the system tries to create a
create a file with the same name as an existent file.
open(String
name, int flags) This system call opens the
(simulated) file called,
"name", with the flag values in the flags parameter. It reports
an
error if the file was not previously created. It returns a small
integer called a file descriptor. File descriptors
are small integers that index a per-process open file
table. In our system, file descriptors are unique across
processes (they index a global table). So if pid 100 is using
file descriptor 3, then no other pid can open a file with descriptor
3. Any attempt to do this should return
Error.BAD_FILE_DESCRIPTOR. After a file that was using file descriptor X is closed,
a newly opened file can use file descriptor X.
Supported
flags are O_RDONLY, O_WRONLY, O_RDWR, and O_APPEND.
O_RDONLY means the file is only open for reading. Any writes to a
file opened O_RDONLY fail and print an error message.
O_WRONLY means the file is only open for writing. Any reads
from an O_WRONLY file fail and print an error message. O_RDWR
means the file is open for reading and writing. O_APPEND means
the file is opened, and the file pointer is set to the end of the
file. O_APPEND
is an option to all of the other modes. It is used with other modes.
i.e.
O_RDONLY|O_APPEND or O_WRONLY|O_APPEND. Report an error
if the system tries to open a file that has not been created.
close(int fd) This system call
closes the (simulated) file referred to by the file descriptor numbered
fd. Any future read or write to this file descriptor (without an
intervening open) will fail with an error. The file descriptor fd
is now free and can be reassigned to another file. On a close you
must flush to disk all the data blocks of this file in the buffer cache, and
the inode block of this file. A real OS does
not flush on close, but doing this will shorten the distance between
your having a problem and discovering it.
read(int fd, int size) This
system call reads from the file referred to by the file descriptor
numbered fd. The read happens at the location of the current file
pointer, and the file pointer's position is updated.
If a read is too long, and would exceed the maximum file size, do as
much of it as you can before returning an error. After all, when
something goes wrong with your computer, wouldn't you want to get some
data rather than no data?
Your implementation of read should call ReadCheck.write with the pid
doing the reading and the data read. Only call ReadCheck.write
if you have a non-zero sized read. You can call it for each
block read or for all the data at once.
write(int fd, int size) = 0xXX This
system call writes to the file referred to by the file descriptor
numbered fd. The write happens at the location of the current
file pointer, and the file pointer's position is updated. The
write call is followed by a single hex byte, which is the data value
written. For instance
write(3, 50) = 0xAA writes 50 bytes
whose value is 0xAA at the current file pointer for the file referred
to by file descriptor 3. Because our trace file does not model
how a process modifies memory between reading and writing it to disk,
we use this mechanism to insure that we write non-zero data to the disk.
In your implementation, you will have to do the work of making
sure the data ends up in the disk block you are writing.
If a write is too long, and would exceed the maximum file size, do as
much of it as you can before printing an error. Return the number of
bytes written (which can be 0).
seek(int fd, int whence, int
offset) This system call sets the file pointer for the
file referred to by the file descriptor numbered fd. Valid values
of whence are SEEK_SET, SEEK_CUR, and SEEK_END. These have their standard
meanings, SEEK_SET sets the file pointer to offset bytes from the start
of the file. SEEK_CUR sets the file pointer to its current value
plus offset bytes. SEEK_END sets the file pointer at the end of the file.
Unix and windows file systems allow you to
seek past the end of a file, creating files with "holes." You do
not need to support this behavior.
proc_create(int
pid) This system call creates a process with an identifier
equal to pid.
proc_kill(int pid) This system call kills the process with
the identifier equal to pid. If the system kills a process that
has I/O pending, the OS must wait for the I/O to complete before
killing the process, because the process is locking pages that are
being modified by the I/O device (the disk in this case). This is
why when you type kill -9 XXX and the process with pid XXX is in state
D (blocked on I/O), the process will not exit, but will wait for the
I/O to complete. If the I/O is a read from a network file system
whose server has crashed, this wait can take a very long time. Who
thought killing a process could be such trouble? In our lab a
process may kill only itself.
Notice that there is no system call to remove (called unlink for
interesting reasons in UNIX) a file. That helps keep your
implementation simpler. Mkfs a
new file system is the only way to get rid of data. Please try to
grok this---read the Beware Crashes section (below).
Error
Output
In your implementation, you should print appropriate error output.
Please see Error.java for the API, your calls will look like this
Error.println(Error.XXXXX, fileName). When the code
finds an error, return -1 from the system call. The standard
convention in UNIX/Linux is to return a negative value on an error.
The real OS returns a particular number indicating the actual
error. We will just use -1. There are 6 kinds of errors.
- FILE_NOT_FOUND : When you try to
open a file that doesn't exists
- FILE_ALREADY_EXISTS : When you
try to create a file that already exists
- FILE_SIZE_LIMIT : When you try
to write beyond the maximum file size. The maximum
file size is
Disk.BYTES_PER_PAGE * datablocks_per_inode
- DISK_FULL : When you try to
write something when disk is full
- INODE_FULL : When you try to
allocate a new inode where there is no empty inode left
- DENTRY_FULL : If the directory
data block is full and cannot add another Dentry to it
- BAD_FILE_DESCRIPTOR : When you
try to write to a file that is opened with O_RDONLY or read from a file
that is opened with O_WRONLY (The name of the error seems weird but
this is what Linux does). Also return it if the trace file specifies
an file descriptor number that is in use as the result of an open.
- ENAMETOOLONG : If you try to create a file whose name is too long, you return this error.
Beware
Crashes
Your
system does data handling, reading information from the simulated disk
image (which resides in a binary file), and copying it into simulated
memory pages. The metadata you store on disk will be "real" in
the sense that you will read the metadata from simulated disk and use those bytes to
create your internal data structures. This is exactly
analogous to the
operation of a real operating system which buffers disk blocks in
memory, but also maintains in-memory copy of disk-based data structures
like inodes. The in-memory data structures are not exactly the same as the
on-disk versions, usually because the in-memory copies have more information,
like additional pointers. Also, as user programs modify the file system,
those modifications are first buffered in memory before they are written to the
disk.
If you make a change to in-memory file system metadata, you must be sure to flush that change to disk if you
replace
the page (or when you close the file, or before the OS completes its
shut down). If
you do not flush your in-memory changes, then you can end up with a
corrupt file system, just like real life. Flushing updates back to disk is
the main activity done by your OS when it shuts down. If you have a bug in
your code and your simulation crashes in the middle of a run, you might leave
the file system in an inconsistent state, or it might be consistent but not what
you want, like 1 out of 3 files were successfully created. So while you are
debugging, we suggest running mkfs to create
a new file system before each test.
BufferCache
flush policy
Your code buffers disk data in memory. The memory cache of
disk data is called the buffer cache. It holds data pages from
disk files and inodes (metadata from the file system). You need some policy to write
this data back to the disk. Here is that policy.
- VFS.close(): (flush
data block before inode block)
- flush all data blocks of that file if it is in the buffer
cache
- flush the inode block where the inode of the file is located
- BufferCache: when the replacement policy
tells you to remove a page, flush the page before you remove it.
- VFS.shutdown(): flush
all the pages to the disk (this method is called right before simulator
terminates)
You might question why we ask you to flush file changes on close.
That is a reasonable question. We specify this mainly to help you
debug your code since after you close a file, your changes will be
visible on the disk right away, and not at some indeterminate point in
the future.
Properties
File
The simulator
reads
a file called system.properties. This file specifies values for
the
following variables (their default values are shown). If you only
override a proper subset of these values, the remainder will have their
default value.
- trace_file "simple0.tr" -
The file name which contains the system call trace.
- sched_class "RR" -
The scheduler being used.
- disk_file "test_disk" - The file
name which contains the simulated disk.
- sync_disks "true" - This
parameter will always be true for this lab. When you send a
memory page to the block driver, it writes
it to the disk and returns.
- mem_percentage 100 - This
controls the amount of memory given to your OS (which gives it to the
BufferCache). At 100, your BufferCache gets enough memory to
buffer 100% of the disk. That means if your disk is 128KB,
then if mem_percentage is 100, your buffer cache can cache 128KB.
The system determines the size of your disk by looking at the size of
your disk file. At 50, the buffer cache gets enough
memory to buffer 50% of the disk (64KB in our example). The
system checks that you have
at least 6 memory pages.
Start the lab by getting your entire system to work with mem_percentage 100.
Don't bother with page replacement at first. When your entire system
works, then you MUST test your implementation with
values of mem_percentage that are low enough to cause replacement from
your BufferCache. Depending on your trace files and simulated
disk size values from 10-50 will probably be appropriate to test low
memory situations.
- read_check "rc" - This is the
file name for the file that stores information on the contents of data
read from the disk. The system generates this data so we can
check that you read back the same data you write to your disk.
Only call ReadCheck if you have actually read data, and only for however many bytes specified by the user. Don't call it on a read that returns 0 bytes. Your code must
call the ReadCheck function.
- datablocks_per_inode
8 -
Each inode can point to this many data blocks.
- inode_block_ratio 8 -
Inodes occur in blocks 3 + N*(inode_block_ratio+1), N in 0, 1, 2, 3,
... See the picture of the disk layout below.
- inodes_per_block 8 - How many
inodes can fit in a block. Because we have big blocks and small
inodes, we don't use math to compute this value, but provide it as a
property.
The last three
options are required to make a new file system. Once a file
system is made, the value for these three options is stored in the
super block. Once a file system is made, it exists as a file and
the size of the file is the size of the simulated disk. Making these
values smaller will help you test out of data and out of inode error conditions.
You can provide an argument to ProcessTrace on the command line.
That value will override the default value for the trace_file.
How The
Simulator Marks Time
You don't need
to know how the simulator manages its sense of time, but you might be
interested. Within the trace file,
the events for a pid happen in order, but events from different pids
happen in the order determined by your scheduler. See the code
for more details.
Major Classes
Classes you
must write
In general, sections of code that you need to write are marked with the following comment.
// Write me
VFS
(virtual file system) You
will most likely spend a good deal of time in your VFS code. The
vfs
layer in an operating system represents an abstract view of the file
system (e.g., things like the path name separator character (/ on Unix,
\ on windows) are not part of the data representation). The
implementation of this class is split between the files
VFS.java and VFSSync.java. You will need to modify and
understand both files. This split is an unfortunate consequence of
object oriented development.
BufferCache The buffer (or page)
cache keeps the contents of disk blocks in memory. You can develop your
code assuming
you have enough memory to buffer the entire disk, but you should test
where you do not have enough memory. When you don't have enough memory
to buffer the whole disk, your trace might read enough blocks to
require your replacement policy to flush buffered blocks back to
disk. The BufferCache is a write-back cache with write allocate.
You should implement
an LRU
replacement algorithm for your buffer cache. You also need to make
appropriate call to Stat.inc() to keep track of the number of cache read,
write, hit, and replacement.The implementation of the class is
split across BufferCache.java and BufferCacheSync.java.
SJF You remember schedulers,
right? Your code comes with an implementation of RR.java.
This implementation works, and is similar to what you implemented
for lab 1. The major addition is the clear_fd routine that closes any file descriptors
a process has left open when it dies. Other differences are:
don't use ProcessTrace.log, use Dbg.println, and IScheduler has a setVFS method.
Please update your SJF code from lab 1 to work with this lab.
You can use code developed by either partner, but not by someone
else in the class (we will check this). Porting the
scheduler should be easy--use RR.java as a guide. If neither
you nor your partner have a
working implementation of SJF this will provide a good review exercise
for you.
Mkfs This is a utility that creates
an empty file system on a "device" in our case, a file.
Coding guidelines
We
provide you a skeleton implementation of VFS, VFSSync, BufferCache, BufferCacheSync, and Mkfs.
We provide RR.java, which should be enough for you to implement
SJF.java. Please do not change
the code outside of the four files you must write.
NOTE: You only need to write the synchronous bits for this lab.
I believe we have removed the asynchronous portions.
We have eliminated about 548 lines of code from our reference
implementation,
so that should give you some idea of how much work you have to
do.
Of course lines of code are an imperfect metric.
In order to provide return types and get the code we hand you to
compile, we often insert dummy variables, e.g.,
Inode inode =
null;
return inode;
You should modify this code to create an appropriate Inode.
There is no asynchronous code for you to implement in this lab.
Test
Cases
Any systems
builder will tell you that 95% of of the time your code doesn't work
right when it is first compiled. Getting code to compile is an
accomplishment, but it is a relatively small step to a solution.
Even getting your code to process input without dying
does not mean your code is correct. The only way to make sure
code is correct is to prove it (which is hard), or write a test case
(which is non-exhaustive). One lesson from this course is that in
order to write code you must write tests for that code.
In this lab we are giving you three totally trivial tests. It
should be clear to you that the functionality tested by our examples is
a small fraction of the functionality of the entire lab. Getting
the right answer for these test cases will probably get you between
0-10% of the credit for the lab. In this lab you fill in a
complicated system with many correctness properties, and you should
test them. We will evaluate your lab using our own test cases. These
tests are long and do many operations. They aren't tricky, but they do
require that your implementation be complete in a way that you can only verify
by writing large tests yourself. We will not read your code to evaluate
it, we will only run our test cases.
Tests often fall into two categories, unit tests and system
tests. Unit tests are focused test cases designed to stress one
piece of functionality, like writing to file that was opened read-only
should print an error. System tests put the system under load and
make sure all requests complete correctly. You should develop
many unit tests for this lab and a few system tests.
You may share trace files on the newsgroup, and you may share md5 sums
of the final disk. YOU MAY NOT SHARE DISK
IMAGES OR SIGNIFICANT PORTIONS OF DISK IMAGES. If you have
a question as to whether your post is appropriate send it to Witchel
and the TAs before you post and we will judge it for you. In
addition, EACH
GROUP MAY ONLY POST 3 TRACE FILES AND MD5 CHECKSUMS. We
will reduce your grade if you post more. The idea here is that
you can ask your fellow students for help, and your fellow students can
also be altruistic and provide interesting test cases. But one
smart team will not produce exhaustive tests for the whole class.
You need to understand the lab to understand what a test case
should
do, and posting too many checksums (or the files themselves) undermines
student's motivation to understand the test case.Classes that support the file system
implementation
Dentry A directory entry.
BitMapBlock A block that holds a
bit map. The
bitmap could be for free blocks for inodes.
SuperBlock The superblock holds the
file system
parameters.
File Your vfs maintains an array of
open
Files. These should not be confused with java.io.File.
Inode The in-memory version of the
on-disk data
structure.
Most of these classes must be created from the data you read from
disk. Here are two examples.
new
DirectoryBlock(bufferCache.readDirDataBlock(devNum).get_bytes());
new
Inode(getSuperBlock(devNum), page.get_bytes(), ino);
Other
classes
OS
What do these things do again?
It manages the boot process, creates coordinates the other major
components.
OSEvent This encapsulates the
data in a callback, primarily interrupts and system calls. You
can also use them to pass parameters. They aren't too important in
synchronous mode, but you must instantiate them (using VFS.createEvent) when you
call a method that takes OSEvent as a parameter, i.e. do not pass null to it,
since some other classes, e.g. BlockDriver will use
it. VFS.createEvent takes a syscall number, which will be
ignored, but you might as well set it to something sensible, like
OSEvent.SYSCALL_WRITE in VFSSync.write.
ProcessTrace This controls the reading of the trace file
and the sequencing of events.
DiskDumper This allows you
to print out parts of your simulated disk. The Unix command od -x
is also useful for this.
Dbg This is a utility class
that allows flexible printing of messages to the console to aid your
debugging. It is initialized in OS.OS.
See the code for more documentation and ideas.
Disk Format
Here is a
picture of the disk layout you will use. Block 0 is the
superblock, block 1 is the used block bitmap, and block 2 is the inode
bitmap. The format and use of
these blocks is described
below. Block 3 is an inode
block that contains the inode for the directory (there is only a single
directory for the file system). Because inode blocks can contain
multiple inodes, this block might contain inodes used for something
other than the directory. Block 4 is just a data block, but is
used as the data block for the directory. The directory only has
a single data block. While it might seem odd to specify the
directory's data block, you will find that this simplifies your life
when the disk interface becomes asynchronous.
Block 3 is an inode block, and there are data blocks
until the (3+InodeBlockRatio+1)th block (block 12 by default
(which is the 13th block)) which is an inode. After
that inode, we have data blocks until the
(3+2*(InodeBlockRatio+1)+1)th block,
which is an inode (block number 21), etc.

Inodes
only have direct pointers. The dataBlocksPerInode member of Superblock
denotes this value. The maximum file size will be
Disk.BYTES_PER_BLOCK * datablocks_per_inode
Superblock
The superblock
has information about the file system, like the number of inodes per
block, the inode_block_ratio, and the datablocks_per_inode, a
pointer to the directory inode (its inumber), and the total number of
blocks in the file system.
Usedblock Bitmap
The usedblock
bitmap block stores a bitmap where each bit represents the state of
each data block on the disk. If the bit is 1, that block is
used. In this case, used means it is not available for allocation
as a data block. Therefore, all blocks which can be used for
inodes are marked used. That means mark all inode blocks as used
in the usedBlockBitmap in
order
not to allocate a data block there
Inode Bitmap
The inode bitmap
block stores a bitmap where bit i
represents whether inode i is
in use. Each inode block contains InodePerBlock inodes. If
InodePerBlock is 4, then inodes 0-3 are stored in block 3 in the above
picture.
Directory
Entries
Your directory
entries are 36 bytes long. They consist of a 32-byte fixed size
name, and a 4-byte inode number that is the inumber for the file's
inode.
Program Output
Your program
generates three kinds of output.
- Final state of the simulated disk file.
- The read check file.
- Errors and statistics that are printed to stdout.
Your goal is to
create a correct implementation that performs as well as
possible.
Correctness is judged by comparing the final state of the simulated
disk file and the read check file to one generated by our reference
implementation, and by matching error messages. Statistics
do NOT have to match for correctness.
To check your answers, you can use the program md5sum. Running md5sum -b
<filename> on a Unix machine will generate a 128-bit hash
(represented by ascii text). It is VERY unlikely
that two different files have the same md5 hash.
Callbacks
This lab will not use callbacks, because your code
will assume that disks respond synchronously,
i.e., you wait for them. This allows the code you write to be
simple, e.g.,
void
process_superblock_sync() {
read_superblock();
update_superblock();
write_superblock();
}
While
simple, this would be a terrible thing to do with a real disk.
Disks take a very long time to
respond relative to instruction execution, so systems overlap the disk
request of one process with computation from another. Many
systems (like Linux and Windows) have kernel threads which call the
scheduler
after making a disk request. The scheduler suspends the process,
and preserves the stack state of the process, usually using assembly
language to switch stack contexts to the next kernel thread that must
run. In these systems, most of the state for the current system
call
is stored on the stack (with some information stored in the process
table, the process' PCB, etc.).
The asynchronous disk part of the lab will use callbacks, but that is not part of this assignment.
Hints
If you crash in the middle of a disk operation, you can leave the disk in an
inconsistent state. You might need to recreate the disk from scratch if you
crash using Mkfs.
When the simulator starts (known as booting up in deference to the expression to
pull oneself up by one's bootstraps), the superblock, 2 bitmap blocks,
directory inode block, directory data block (i.e. block 0 ~ 4) will be
loaded into the BufferCache.
In the buffer cache, pin bit is
set to true.
It means you should not remove these 5 blocks from the buffer cache.
They always reside in memory, allowing you to know that reading these blocks always hits the cache.
See VFS.startup() for the details, though this is more important
for the asynchronous case.
Pages, sectors and blocks are all 4KB.
If one process opens a file, no other process may use that file descriptor. You should check and disallow it.
Try to take advantage of
SuperBlock.getBytes(),
BitMapBlock.getBytes(),
DirectoryBlock.getBytes(),
Inode.getBytes()
MemPage.get_pages()
when you create an array of byte.
The interface for the BufferCache is narrow. The buffer cache reads
disk blocks into pages that it manages. If the VFS reads a newly allocated
block, it could conceivably tell the BufferCache not to bother reading the disk
block because the contents are totally new. This would complicated the
interface, so we don't do it. If you read a block that has never been
allocated, you still read the block from disk.
In the code you will see c_style_identifiers, and
javaStyleIdentifiers. Sometimes I feel like a nut, sometimes I
don't.
Extra Credit
If you
implement any of these you will receive extra credit. The more
you implement, the more credit you get.
- Add an
indirect
block to your inode.
- Implement
unlink.
- Add file
permissions.
- Write fsck.
- Allow
multiple directories, arranged hierarchically.
If you do the
extra credit, you need to submit at least 4 additional test cases (for
each feature) that
test your functionality, called extra00.tr, extra01.tr,...,extra10.tr,
extra11.tr, etc (the first number is the Xth extra feature,
the second number goes from 0 up to 9). Also
include a file called EXTRA.txt which has any notes or assumptions you
made in your implementation.
What to Turn In
Please turn in a
single jar file which has Mkfs.java, VFS.java, VFSSync.java,
BufferCache.java, BufferCacheSync.java, and SJF.java along with your README, ANSWERS.txt (see
below), and
your test cases. We will unpack your jar file, and use the
ORIGINAL source files you were given for the rest of the project.
ANY
MODIFICATIONS you made to files besides the ones you are
supposed to turn in WILL BE LOST when we test your program, so
don't change these files. We will run your
lab like this:
java ProcessTrace
You are required to turn in 3 test cases, in files named
test0.tr, test1.tr, test2.tr (you may include additional files that
conform to this naming scheme). test0 should create lots of small
files, test1 should create at least one large file. test2 (and
subsequent tests) I leave to your creativity.
Don't forget to test your code under low memory situations
(mem_percentage is 20 or so depending on your trace and the size of
your simulated disk).
Please include a file called AUTHORS.txt which includes a sequence of
lines. Each line has a name, followed by a colon (:) followed by
your eid.
Lab Questions
Please put these
questions and your answers in a file called ANSWERS.txt. Answer
these questions for the default values of system.properties.
- What is the maximum file size?
- What is the maximum number of files you can create, assuming each
file has a unique name created from all lowercase letters (26 of them)?
- Why is the
default value of inodes_per_block equal to the default vale for
inode_block_ratio?
Other Details
and Submission Instructions
- Each student must do the assignment independently, or work as a
pair in the pair programming style. In pair programming, two
students must sit at the same computer and both must participate
actively. We require each student who is in a pair programming group to
spend at least 80% of the total development time participating
actively, and at least 40% of that time being the one typing on the
keyboard. (This style does not mean two students can split up
the program, work separately, and then reconvene to put the solution
together.)
If you pair program, follow these additional guidelines.
- Only one person turns in the assignment.
- Your README file also contains, the name of both pairs.
- See the Pair
Programming web page for how to do pair programming and the
benefits of it.
In your README file, include the following:
Name
Slip days used (this project): _______ Slip days used (total): ______
On my honor, name, this programming assignment is my own work.
...if applicable...
Pair Name
Slip days used (this project): _______ Slip days used (total): ______
On our honor, name and name, this programming assignment is our own
work. We spent 80% of our time on this project together, we split the
keyboard time evenly, and we both participated equally in the solution
design.
........
Total hours spent on this project:
Please turn in your project using the following command (in one
line):
/lusr/bin/turnin --submit naga86 lab4 FileSystem.jar