6.824:Lab2-3 FileSystem

Lab2-3 File Server

Introduction(Lab2)

在这个实验中,将使用FUSE的接口完成一个文件系统

  • CREATE/MKNOD, LOOKUP, 和 READDIR
  • SETTATTR,WRITE,READ

这些操作是完成一个文件系统的最根本的几个操作之一。使用下图的架构。

我们将提供YFS和extent服务器模块的基本框架,如图[1]所示。

image

图[1] 整体系统结构

YFS模块时实现文件系统基本核心逻辑的模块,这一模块最后被编译为一个yfs_client支持本地挂载的文件系统。这一模块的代码框架由两部分组成:

  • FUSE 接口,在这个fuse.cc中,这个代码将FUSE操作从内核模块变为了YFS客户端调用。实验已经提供给了FUSE内核的注册操作,需要做的是,修改fuse.cc使其能够正确地调用yfs_client提供的成员方法,再通过FUSE接口返回。
  • YFS 客户端,在yfs_client.{cc,h}。YFS clients使用extent和lock服务实现了分布式文件系统的主要逻辑。例如当创建一个新的文件时,你的yfs_client就要在目录数据中增加一个目录项(item)在extent服务器中,这一模块是调用方,即为客户端,而不是想文件服务器,活着时锁服务器一样的服务器。(fuse->yfs_client->extent client-> extent server),如图[2]所示。 image 如图[2] 整体客户端结构,exe代表操作系统上运行的可执行文件,比如touch,ls等

extent服务器存储着文件系统所有的数据,像是在二进制文件中的硬盘的作用。在随后的实验中,你将在多个主机上运行YFSclient,他们都使用一个公共的extent服务,这也就意味着所有的主机使用着和共享着相同的数据。extent服务器代码框架包含有两个方面:

  • Extent客户端 在extent_client.{cc.h}封装了与extent服务器通行的RPC。
  • Extent服务器 在extent_server.{cc.h}和extent_smain,extent服务器管理了一个简单的K-V存储,它使用extent_protocol::extentid_t作为Keys,V包含两个方面,使用string作为存储的数据类型,使用一个结构体包换这个数据的属性(attr)。服务器包含四个方法:put(k,v), v_s get(k), v_a getattr(k), remove(k).

Getting Started

首先给电脑的操作系统安装FUSE库(详见实验概述

框架代码仅仅实现了GETATTR和STATFS这两个接口,所以刚开始挂载的文件系统是不能够正常使用的。实验将完善文件系统中更多的FUSE的操作

  • 第一部分:应当完成CREATE/MKNOD,LOOKUP,和READDIR。代码应当通过test-lab-2-a.pl这一脚本。这个脚本主要的测试项是创建空文件,在目录中查询文件名,列出目录中的文件内容。
  • 第二部分:需要实现SETATTR,READ和WRITE,你的代码应当通过test-lab-2-b.pl这一脚本。这个脚本的主要测试读,写追加文件。还测试文件是否能够在一个客户端创建,在另一个客户端读取。 确保你在Linux操作的系统的用户在FUSE的用户组中,这样才能够操作FUSE格式的文件系统。

PART1:CREATE/MKNOD, LOOKUP, and READDIR

your job

在第一部分的工作是实现一个extent服务(存储服务),然后实现CREATE NKNOD,LOOKUP和READDIR 的FUSE文件操作。必须使用extent服务器来存储文件系统的内容,这样才能够完成part2中共享数据以及后续的实验。当然可以将extent的存储放在内存中(我就只使用了c++ 的map),只是一旦关闭extent服务器,所有的数据就都丢失了。

在一些系统中,FUSE使用MKNOD创建文件,在另一些系统中,使用CREATE。这两个接口有一些小区别,但是为了方便,我们提供了一个createhelper()接口,MKNOD或者CREATE接口都wrap了这个接口。实验所要做的工作就是实现createhelper()方法。

Step One

  • 实现extent服务器:需要再extent_server.cc中实现这个服务。一共有四个操作put(k,v), get(k), getattr(k)和remove(k)。put和getRPC被用作更新和获取数据(extent的content)。getattrRPC获取数据的“属性”,属性包含了数据最后修改时间(mtime),最后改变属性的时间(ctime)和最后获取数据的时间(atime)。时间以秒作为单位从1970年起计算(unix时间戳)。attribute有点像unix系统的i-node。你可以使用转本的attribue存数,不用像inode一样将元数据和数据都存储在里边。当调用put()时,ctime和mtime一样改变。调用get()时,atime会改变。
  • 文件/目录 ID

    YFS和FUSE都需要一个能够辨识唯一的标识符,如果UNIX文件系统中的inode标号。FUSE使用32-bit作为标识符,你的YFS应当使用64-bit的数字作为标识符,其中高32位为0,低32位作为文件/目录的ID,在这个系统里,标识符在yfs_client.h成为inum。

    当创建一个新的文件(使用fuseserver_createhelper)或者文件(fuseserver_mkdir),你不必分配一个inum,简单点地做法就是随机取一个数组,只要不重复就可以但要考虑当文件和目录不断增加时,冲突的概率。(我刚开始想学unix做bitmap,后来做到客户端了。。。所以这里偷了个懒,就使用随机数生成,使用map count作为判断是否冲突。)

      int extent_server::GetInodeNum(int is_file, extent_protocol::extentid_t& rino){
      	extent_lock ex_lc(&pmutex_file_block_map_);
      	extent_protocol::extentid_t ino;
      	struct timeval tv;
      	gettimeofday(&tv,NULL);
      	srand(tv.tv_usec);
      	do{
      		ino = rand() % 0x80000000LLU;
      		if (ino == 1)
      			continue;
      		if(is_file){
      			ino |= 0x80000000LLU;
      		}
      	}while(file_block_.count(ino) > 0);
      	rino = ino;
      	return extent_protocol::OK;
      }
    

    YFS需要通过inum来区分哪一个是文件数据,哪一个是目录数据。这里使用31bit作为inum号进行分配。这里使用yfs_client::isfile来判断,如果第31位为0,为目录,1为文件。

      bool
      yfs_client::isfile(inum inum)
      {
        if(inum & 0x80000000)
          return true;
        return false;
      }
    
  • 目录格式:

    下一个任务就是定义目录的存储结构格式。一个目录数据应当包含目录中名称和这对应inum的映射。当然存储的时候,需要把目录数据转换成一个string存储在extent服务器中。LOOKUP和READDIR读取目录内容,CREATE/MKNOD修改目录内容。以下是我对目录的定义:

      /*
          *dir_block_:map<FILE_NAME, INUM>
          *dir_item_num_ :目录项个数
          *StringToDirForm 将extent的数据 转换成目录结构数据
          *DirFormToString 将目录结构数据 转化成extent数据
      */
      struct DirForm{
      	std::map<std::string, uint64_t> dir_block_;
      	int dir_item_num_;
      	void StringToDirForm(const std::string&);
      	void DirFormToString(std::string&);
      };
      void DirForm::StringToDirForm(const std::string& data){
      	const char* start = data.data();
      	std::size_t pos = 0;
      	dir_item_num_ = *(int *) (start + pos);
      	printf("dir_item_num_ = %d\n", dir_item_num_);
      	pos += sizeof(int);
      	for(int i = 0; i < dir_item_num_ && pos < data.size(); i++) {
      		std::string item_name;
      		item_name.assign((char *) (start + pos), MAX_ITEM_NAME_LEN);
      		pos += MAX_ITEM_NAME_LEN;
      		uint64_t item_num = *(uint64_t *)(start + pos);
      		pos += sizeof(uint64_t);
      		dir_block_.insert(std::make_pair(item_name, item_num));
      	}
      }
    
      void DirForm::DirFormToString(std::string& data){
      	data.clear();
      	dir_item_num_ = dir_block_.size();
      	char a32[4];
      	memcpy(a32, &dir_item_num_, 4);
      	data.append(a32, sizeof(dir_item_num_));
      	typeof(dir_block_.begin()) mit = dir_block_.begin();
      	for(;mit != dir_block_.end(); mit++){
      		std::string tmp_str = mit->first;
      		tmp_str.resize(MAX_ITEM_NAME_LEN);
      		data.append(tmp_str.c_str(), MAX_ITEM_NAME_LEN);
      		char a64[8];
      		memcpy(a64, &(mit->second), 8);
      		data.append(a64, sizeof(mit->second));
      	}
      }
    

    这里就不需要考虑文件所有者,模式或者是权限等。

  • FUSE:

    当一个程序(像ls或者文本编辑器)在你的yfs_client中操作一个文件或者是目录时,在内核中的代码通过FUSE传递给yfs_client。实验已在fuse.cc中实现了这一接口,比如create,read,write等等操作。你应当在fuse.cc中修改相关的接口,fuse接口中的create调用yfs_client中的create。这个基本的操作可以参考已经实现的gettattr函数。

    相关的一些方法可以在fuse_lowlevel.h中进行详细的理解,比如FUSE使用fuse_reply把成功的结果返回给内核,通过fuse_reply_err报告一个错误。

    在通过READDIR读取目录信息的时候,使用了一些trick,我们已经实现了dirbuf_add,reply_buf_limited等方法,你所要做的就是修改FUSE中的READDIR让系统获得这个目录下的目录项。

    尽管当你创建新的文件或是目录时,可以随意的使用inum作为标识符,但是FUSE是将0x00000001作为根目录(root)的。因此你应当确保0x00000001这个不会作为普通文件或者目录的inum,因为它已被占用

  • YFS code:

    文件系统的主要逻辑应当在yfs_client中完成,因为大部分的操作都是fuse调用yfs_client。fuse只是作为一个handle。在yfs_client中,使用get(inum)从extent服务器获取数据。在目录文件中,要能够将string解析成文件目录结构。

  • tips

    如果一个文件已经存在(filename在相同的目录下重复),CREATE操作就应当返回EEXIST给FUSE。

//创建文件或目录
yfs_client::status
fuseserver_createhelper(fuse_ino_t parent, const char *name,
                        mode_t mode, struct fuse_entry_param *e)
{
  // In yfs, timeouts are always set to 0.0, and generations are always set to 0
  e->attr_timeout = 0.0;
  e->entry_timeout = 0.0;
  e->generation = 0;
  // You fill this in for Lab 2
  uint64_t inum;
  int ret = yfs->create(parent, name, inum);
  if (ret != yfs_client::OK) {
	  return ret;
  }
  e->ino = inum;
  getattr(inum, e->attr);
  printf("fuseserver_createhelper create item succ!\n");
  return yfs_client::OK;

}

yfs_client::status yfs_client::create(uint64_t parent, const char *name, uint64_t& ret_ino){
	  yfs_client::status ret;
	  std::string data;
	  std::string str_name = name;
	  str_name.resize(MAX_ITEM_NAME_LEN);
	  printf("parent id: %lu\n", parent);
	  printf("file name is %s\n", str_name.c_str());
	  YfsLock yl(lc, parent);
	  ret = getdata(parent, data);
	  if (ret != yfs_client::OK){
		  return yfs_client::NOENT;
	  }
	  DirForm dir_info;
	  dir_info.StringToDirForm(data);
	  if (dir_info.dir_block_.count(str_name) > 0) {
		  return yfs_client::EXIST;
	  }
	  ret_ino = GetAvailInum(true);
	  printf("fuseserver_createhelper ino:%lu\n", ret_ino);
	  if (ret_ino == 0) {
		  return yfs_client::NOENT;
	  }
	  dir_info.dir_block_[str_name] = ret_ino;
	  dir_info.DirFormToString(data);
	  ret = putdata(parent, data);
	  if (ret != yfs_client::OK) {
		  RestoreInum(ret_ino, true);
		  return ret;
	  }
	  data.clear();
	  ret = putdata(ret_ino, data);
	  if (ret != yfs_client::OK){
		  RestoreInum(ret_ino, true);
		  return ret;
	  }
	  yfs_client::fileinfo info;
	  ret = getfile(ret_ino, info);
	  if (ret != yfs_client::OK){
	  	  RestoreInum(ret_ino, true);
	  	  return ret;
	  }
	  return yfs_client::OK;
}
//LOOKUP操作
void
fuseserver_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
  struct fuse_entry_param e;
  // In yfs, timeouts are always set to 0.0, and generations are always set to 0
  e.attr_timeout = 0.0;
  e.entry_timeout = 0.0;
  e.generation = 0;
  bool found = false;

  // You fill this in for Lab 2
  yfs_client::status ret = yfs->lookup(parent, name, found, e.ino);
  if (ret != yfs_client::OK)
	  goto lookup_end;

  if(found){
	  getattr(e.ino, e.attr);
  }
 lookup_end:

  if (found)
    fuse_reply_entry(req, &e);
  else
    fuse_reply_err(req, ENOENT);
}
yfs_client::status yfs_client::lookup(uint64_t parent, const char *name, bool& found, uint64_t& imun){
	  DirForm dirinfo;
	  std::string data;
	  std::string str_name = name;
	  str_name.resize(MAX_ITEM_NAME_LEN);
	  printf("input name=%s\n", str_name.c_str());
	  std::map<std::string, uint64_t>::iterator mit;
	  YfsLock yl(lc, parent);
	  if(getdata(parent, data) != yfs_client::OK){
		  return yfs_client::NOENT;
	  }

	  dirinfo.StringToDirForm(data);
	  printf("fuseserver_lookup dirinfo.dir_block_ size is %lu\n", dirinfo.dir_block_.size());
	  mit = dirinfo.dir_block_.begin();
	  /*
	  for(;mit != dirinfo.dir_block_.end(); mit++) {
		  printf("name:%s,in:%llu\n",mit->first.c_str(), mit->second);

	  }
	  */
	  if (dirinfo.dir_block_.count(str_name) > 0){
		  imun = dirinfo.dir_block_[str_name];
		  found = true;
	  }
	  return yfs_client::OK;
}
\\READDIR
void
fuseserver_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
                   off_t off, struct fuse_file_info *fi)
{
  yfs_client::inum inum = ino; // req->in.h.nodeid;
  struct dirbuf b;

  printf("fuseserver_readdir\n");

  if(!yfs->isdir(inum)){
    fuse_reply_err(req, ENOTDIR);
    return;
  }

  memset(&b, 0, sizeof(b));


  // You fill this in for Lab 2
  DirForm dirinfo;
  yfs_client::status ret = yfs->readdir(dirinfo, ino);
  if (ret != yfs_client::OK){
	  printf("readdir:yfs->readdir return %d", ret);
  }
  typeof(dirinfo.dir_block_.begin()) mit = dirinfo.dir_block_.begin();
  for(;mit != dirinfo.dir_block_.end(); mit++){
	  dirbuf_add(&b, mit->first.c_str(), mit->second);
  }
  reply_buf_limited(req, b.p, b.size, off, size);
  free(b.p);
}
yfs_client::status yfs_client::readdir(DirForm& dirinfo, uint64_t inum){
	;
	std::string data;
	YfsLock yl(lc, inum);
	yfs_client::status ret = getdata(inum, data);
	if (ret != yfs_client::OK){
		return ret;
	}
	  dirinfo.StringToDirForm(data);
	  return yfs_client::OK;
}

PART2 SETTATTR,READ,WRITE(LAB2)

Part 2 Your Jobs

在Part中需要实现SETATTR,WRITE,READ等FUSE操作,只要通过了test-lab-2-b.pl Lab2就算是完成了。test-lab-2-b.pl测试了读、写、追加文件,在一个客户端写,查看另一个客户端是否可读。

Part2:Detailed Guidance

  • 实现SETATTR

    操作系统通过FUSE的SETATTR操作告诉你的文件系统设置文件属性。在to_set参数中设置哪一个需要被设置。目前只有一个文件属性需要注意就是文件大小的属性。设置FUSE_SET_ATTR_SIZE,操作系统也许会通过覆盖写,创建一个已存在的文件。那么就不会通调用FUSE的CREATE,而是SETATTR改变文件size属性。

  • 实现READ\WRITE

    READ(fuseserver_read)读一个文件,需要一个读取文件大小的size和从哪里开始的offset作为参数。当size小于文件size时,当读取数据超出了文件范围时,能读多少返回多少可读的数据。在linux系统中使用man 查看read的详细信息。

    对于WRITE(fuseserver_write)来说,需要一个写入数据大小的size和从哪里开始的offset作为、数据这三个参数。当offset值大于文件原本size的时候,大于的部分全部填充‘\0’。

yfs_client::status yfs_client::read(uint64_t ino, size_t size, off_t off, std::string &buf){
	  std::string data;
	  YfsLock yl(lc, ino);
	  yfs_client::status ret = getdata(ino, data);
	  if (ret != yfs_client::OK) {
		  return yfs_client::NOENT;
	  }
	  if ((uint64_t)off >= data.size()) {
		  buf = "";
	  }else if ((off + size) > data.size()){
		  buf = data.substr(off);
	  }else{
		  buf = data.substr(off, size);
	  }
	  return yfs_client::OK;
}
yfs_client::status yfs_client::write(uint64_t ino, size_t size, off_t off, const char* buf){
	  std::string data;
	  std::string write_data;
	  YfsLock yl(lc, ino);
	  yfs_client::status ret = getdata(ino, data);
	  if (ret != yfs_client::OK) {
		  return ret;
	  }
	  if ((uint64_t)off > data.size()) {
		  data.resize(off);
	  }
	  write_data = data.substr(0, off);
	  if ((off + size) > data.size()){
		  write_data.append(buf, size);
	  } else {
		  std::string tmp_str(buf, size);
		  write_data += tmp_str;
		  write_data += data.substr(off + size);
	  }
	  ret = putdata(ino, write_data);
	  if (ret != yfs_client::OK){
		  return ret;
	  }
	  return ret;
}
yfs_client::status yfs_client::setattr(uint64_t ino, struct stat *attr){
	  std::string data;
	  YfsLock yl(lc, ino);
	  yfs_client::status ret = getdata(ino, data);
	  if (ret != yfs_client::OK) {
		  return ret;
	  }
	  data.resize(attr->st_size);
	  ret = putdata(ino, data);
	  if (ret != yfs_client::OK) {
		  return ret;
	  }
	  return ret;
}

Lab3:MKDIR,UNLINK,and Locking

Introduction

在这个实验中,

  • 在FUSE中增加MKDIR和UNLINK操作
  • 在yfs_client中加入锁操作,以确保在同一个目录下,不同用户之间操作不会有冲突

Your Job

这个任务主要是在FUSE中实现MKDIR和UNLINK,确保在MKDIR创建是,标志文件与目录的inum的那个bit位为0。对于MKDIR,你不用在创建每个目录的时候再创建“.”,“..”因为Linux内核对于YFS是透明的。UNLINK只需要删除对应inum的extent即可,不需要实现UNIX系统那样的文件应用计数。

如果全部通过test-lab-3-a.pl脚本测试,那么就算是完成了part1的内容了。这个测试脚本包含了创建目录,创建删除文件,检查目录extent的mtime和ctime属性。要注意的是测试脚本会检查时间属性的正确性。创建一个文件会同时改变父文件的mtime和ctime。

fuseserver_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
     mode_t mode)
{
  struct fuse_entry_param e;
  // In yfs, timeouts are always set to 0.0, and generations are always set to 0
  e.attr_timeout = 0.0;
  e.entry_timeout = 0.0;
  e.generation = 0;
  // Suppress compiler warning of unused e.
  //(void) e;
  // You fill this in for Lab 3
#if 1
  yfs_client::status ret = yfs->mkdir(parent, name, e.ino);
  if (ret == yfs_client::EXIST) {
	fuse_reply_err(req, EEXIST);
	return;
  }

  if (ret != yfs_client::OK) {
	fuse_reply_err(req, ENOENT);
	return;
  }

  ret = getattr(e.ino, e.attr);

  if (ret != yfs_client::OK) {
	fuse_reply_err(req, ENOENT);
	return;
  }

  fuse_reply_entry(req, &e);
#else
  fuse_reply_err(req, ENOSYS);
#endif
}

yfs_client::status yfs_client::mkdir(uint64_t parent, const char *name, uint64_t& ret_ino) {
	  yfs_client::status ret;
	  std::string data;
	  std::string str_name = name;
	  str_name.resize(MAX_ITEM_NAME_LEN);
	  printf("parent id: %lu\n", parent);
	  printf("file name is %s\n", str_name.c_str());
	  YfsLock yl(lc, parent);
	  ret = getdata(parent, data);
	  if (ret != yfs_client::OK){
		  return yfs_client::NOENT;
	  }
	  DirForm dir_info;
	  dir_info.StringToDirForm(data);
	  if (dir_info.dir_block_.count(str_name) > 0) {
		  return yfs_client::EXIST;
	  }
	  ret_ino = GetAvailInum(false);
	  printf("fuseserver_createhelper ino:%lu\n", ret_ino);
	  if (ret_ino == 0) {
		  return yfs_client::NOENT;
	  }
	  dir_info.dir_block_[str_name] = ret_ino;
	  dir_info.DirFormToString(data);
	  ret = putdata(parent, data);
	  if (ret != yfs_client::OK) {
		  RestoreInum(ret_ino, false);
		  return ret;
	  }
	  data.clear();
	  ret = putdata(ret_ino, data);
	  if (ret != yfs_client::OK){
		  RestoreInum(ret_ino, false);
		  return ret;
	  }
	  yfs_client::fileinfo info;
	  ret = getfile(ret_ino, info);
	  if (ret != yfs_client::OK){
		  RestoreInum(ret_ino, false);
		  return ret;
	  }
	  return yfs_client::OK;
}
void
fuseserver_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{

  // You fill this in for Lab 3
  // Success:	fuse_reply_err(req, 0);
  // Not found:	fuse_reply_err(req, ENOENT);

  yfs_client::status ret = yfs->unlink(parent, name);
  if (ret != yfs_client::OK) {
	fuse_reply_err(req, ENOENT);
	return;
  }

  fuse_reply_err(req, 0);
  return;

  fuse_reply_err(req, ENOSYS);
}

yfs_client::status yfs_client::unlink(uint64_t parent, const char *name){
	  yfs_client::status ret;
	  std::string data;
	  std::string str_name = name;
	  str_name.resize(MAX_ITEM_NAME_LEN);
	  YfsLock yl(lc, parent);
	  ret = getdata(parent, data);
	  if (ret != yfs_client::OK){
		  return yfs_client::NOENT;
	  }
	  DirForm dir_info;
	  dir_info.StringToDirForm(data);
	  std::map<std::string, uint64_t>::iterator del_mit = dir_info.dir_block_.find(str_name);
	  //printf("unlink str-name:%s\n, parent is %llu\n", str_name.c_str(), parent);
	  if (del_mit == dir_info.dir_block_.end()) {
		  return yfs_client::NOENT;
	  }
	  uint64_t ino = del_mit->second;
	  //printf("yfs_client::unlink ino:%ullX\n", ino);
	  if (!isfile(ino)) {
		  return yfs_client::NOENT;
	  }
	  /*
	  int ret_restore = RestoreInum(true, ino);

	  if (ret_restore != 0) {
		  return yfs_client::NOENT;
	  }
	  */
	  dir_info.dir_block_.erase(del_mit);
	  dir_info.DirFormToString(data);
	  ret = putdata(parent, data);
	  if (ret != yfs_client::OK) {
		  return ret;
	  }
	  YfsLock yl2(lc, ino);
	  if (ec->remove(ino) != extent_protocol::OK) {
		return yfs_client::NOENT;
	  }

	return yfs_client::OK;
}

Part 2:Locking

下一步就要考虑当有多个yfs_client客户端运行的时候,保证文件操作的原子性了。现在如果yfs_client 的create方法会调用extent服务器中父目录的一些内容,做一些改变,并存储一些新的东西给extent服务器。在两个客户端同时运行的时候,会出点两个客户端同时获取一个extent服务器的旧数据的信息,每一个会分别插入一个新的内容在这个目录文件中,然后写入extent服务器中。那么最终这个extent中的数据是最后一个人新加的东西。当然正确的结果是这两个客户添加的东西都要加上,这是一个潜在的竞争。在并发CREATE UNLIN,并发 MKDIR UNLINK,并发WRITE中都会存在。

所以,可以要使用锁服务来消除竞争。比如yfs_client应当在CREATE的时候请求一个锁,当修改完毕后,在把这个锁释放掉。如果存在并发操作,这个锁会将并发的两个操作分开,保证原子性。左右yfs_client必须在相同的一个锁服务器上请求。逻辑如图[3]所示:

image

图[3]

Your Job

主要任务就时在yfs_client操作的时候加上锁来确保并发的正确性。test-lab-3-b和test-lab-3-c用作测试。如果你在添加锁之前见进行了测试,那么会在并发创建(concurrent create)。如果在添加了锁之后还有这个错误,那就是有bug了。

Detailed Guidance

  • 关于锁

    最极端的例子就是,整个文件系统中只是用一个锁,这样所有的操作都不会是并行的。还有一个比较极端的例子就是在一个目录下使用一个锁,当然这也不是一个好的方法。单一个全局变量的锁就保证了所有的并发,但是一个细粒度的锁就会增加开销,而且容易发生死锁,因为可能在某些操作时,会需要持有多个锁。

    应当给每一个锁一个inum。这样在对文件或者目录做操作的时候就会比较容易,不用再做什么转换。对那个inum操作,申请哪个inum的锁,使用完后直接释放就好。当然在发生了error,也要及时的释放锁。

    在yfs_client中使用lab1中实现的lock服务就好,就像使用extent_client一样方便。

  • 注意以下事件

    这是首次挂载两个YFS,如果在以前的实验中没有注意的话你可能在分配inum时会出现一些问题,即分配了两个相同的inum。有一种避免的方法就是使用随机数生成inum,可以通过pid生成。(我的代码使用的是时间毫秒级,秒级的不行)。

    这次实验也是首次使用“\0”写入文件,如果使用std::string(char*)构造函数创建会失败,因为这个构造函数还把“\0”作为string’的结尾。如果使用这种构造函数进行写读写数据的初始化,会造成问题。使用std::string(bug, size)这一个函数代替。

22 May 2018 by John Brown