Shark File System Lab
Author: ZT
In the given test cases, each thread operates on a file using a single int fd
. This fd
can be used to obtain an sfs_mem_filedesc_t
object through openFileDescTable[fd]
. Similarly, a v-node can be obtained through fileEntry
, and a block can be accessed via superBlock.file[i]
.
First, we need to implement a few functions that are not yet implemented: sfs_getpos
, sfs_seek
, and sfs_rename
. These three functions are relatively easy to implement. Next, let's explain the mutex locking and unlocking mechanism for multi-threaded reading and writing in the file system.
Then, complete the global lock global_lock
to ensure that an event is atomic during multi-threaded read and write operations. It is not required to isolate files, nor to separate read and write operations, or to prevent interference between multiple read operations. Only thread-safe functionality is required, which only needs a global lock.
static pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
Afterwards, add the global lock at the beginning and release it at the end for the following functions: sfs_open
, sfs_close
, sfs_read
, sfs_write
, sfs_getpos
, sfs_seek
, sfs_remove
, sfs_rename
, and sfs_list
.
Once thread safety is ensured, you can introduce file descriptor (fd) locks. The fd locks can guarantee the atomicity of each file operation but cannot ensure simultaneous writes by multiple threads. Therefore, node locks should also be added. Details are as follows:
sfs_open
: Use the global lock at the entry and exit of the function.
sfs_close
: Use the global lock at the entry of the function. To destroy the fd lock, you need to ensure that the current fd lock is not being used by other threads.
// To destory lock, we need to acquire lock...
pthread_mutex_lock(&tFile->fd_lock);
pthread_mutex_unlock(&tFile->fd_lock);
pthread_mutex_destroy(&tFile->fd_lock);
- Similarly, to destroy the node lock, you need to acquire the node lock.
fileEntry->refCount--;
if (fileEntry->refCount > 0){
pthread_mutex_unlock(&global_lock);
return;
}else if(fileEntry->refCount == 0){
pthread_mutex_lock(&fileEntry->node_lock);
pthread_mutex_unlock(&fileEntry->node_lock);
pthread_mutex_destroy(&fileEntry->node_lock);
}
- Finally, release the global lock.
sfs_read
: First, lock the global lock, then try to acquire the fd lock. If successful, release the global lock to allow other threads' events to continue. After completing the read operation, release the fd lock.
sfs_write
: First, lock the global lock, then try to acquire the fd lock. Unlike the read operation, you also need to acquire the node lock. Then, release the global lock. In this situation, other write operations on this file are blocked because they need to acquire the node lock. Similarly, other attempts to read the file using the fd are also blocked.
- When the function needs to end, release the node lock first, then release the fd lock.
pthread_mutex_unlock(&tFile->fileEntry->node_lock);
pthread_mutex_unlock(&tFile->fd_lock);
- Note⚠️: When writing exceeds the block, you need to lock and unlock the
free_block_lock
at the beginning and end of theallocateBlocks
function to manage the allocation of blocks. Similarly, when releasing blocks using thefreeBlocks
function, you also need the globalfree_block_lock
.
- The
sfs_getpos
andsfs_seek
functions are similar to thesfs_read
function: first acquire the global lock, then lock the fd lock.
- The
sfs_remove
,sfs_rename
, andsfs_list
functions only require the global lock.