In this sequence of labs, you'll build a multi-server file system called Yet-Another File System (yfs) in the spirit of Frangipani. At the end of all the labs, your file server architecture will look like this:
You'll write a file server process, labeled yfs above, using the FUSE toolkit. Each client host will run a copy of yfs. yfs will appear to local applications on the same machine by registering via FUSE to receive file system events from the operating system. The yfs extent server will store all the file system data on an extent server on the network, instead of on a local disk. yfs servers on multiple client hosts can share the file system by sharing a single extent server.
This architecture is appealing because (in principle) it shouldn't slow down very much as you add client hosts. Most of the complexity is in the per-client yfs program, so new clients make use of their own CPUs rather than competing with existing clients for the server's CPU. The extent server is shared, but hopefully it's simple and fast enough to handle a large number of clients. In contrast, a conventional NFS server is pretty complex (it has a complete file system implementation) so it's more likely to be a bottleneck when shared by many NFS clients.
To install FUSE on your own Ubuntu/Debian linux box, do apt-get install libfuse2 libfuse-dev fuse-utils. Or, you can compile and install fuse-2.6-3 from source.
Pthread interfaces are somewhat cumbersome to use. You can (optionally) use our custom wrapper objects in pwrapper.h. For example, pMutex and pCond take care of pthread initializations for mutexes and conditional variables automatically for you. Their usage is very straightforward and an example use of pMutex can be found in lock_tester.cc of Lab 1. The class pScopedMutex has an interesting use. In particular, in its constructor function, it locks the pMutex argument. Its destructor function unlocks the corresponding pMutex. Therefore, an easy way to perform subsystem/protocol locking in a function is to declare a pScopedMutex with the subsystem pMutex in the function. When the function returns, the compiler ensures that the pScopedMutex's destructor will be called, thus automatically unlocking the subsystem mutex. (It frees you from the burden of having to explicitly unlock the mutex in multiple places when there are multiple return points in a function.)
printf statements are always your friend when debugging any kind of problem in your programs. However, when programming in C/C++, you should always be familiar with gdb, the GNU debugger. You may find this gdb reference useful. Below introduces a few gdb tips for complete newbies:
If your program is crashing (segmentation fault), type gdb program core where program is the name of the binary executable to examine the core file. If you don't find the core file anywhere, type ulimited -c unlimted befor starting your program again. Once inside gdb, type bt to examine the stack trace when the segmentation fault happened.
While your programming is running, you can attach gdb to it by typing gdb program 1234. Again, program is the name of the binary executable. 1234 is the process number of your running program. Of course, you can choose to run your program with gdb from the beginning. If so, simply type gdb program. Then at the gdb prompt, type run.
While in gdb, you can set breakpoints (use gdb command b) to stop the execution at specific points, examine variable contents (use gdb command p), etc.
To apply a given gdb command to all threads in your program, prepend thread apply all to your command. For example, thread apply all bt dumps the backtrace for all threads.