实现文件和文件夹

实现文件和文件夹

  • 文件和文件夹在存储介质中的实现方式在很大程度上取决于该介质的物理特性和功能。

  • 例如,在磁带机、CD/DVD/Blu-Ray或一次写入介质上,文件和文件夹是连续存储的,没有碎片。关于文件系统的所有信息都可以保存在TOC(目录)中。

  • 对于文件生命周期有限的文件系统(如闪存介质、硬盘、SSD等),必须以更复杂的方式维护文件和文件夹的布局。

  • 在这些更高级的方法中,有链表和i节点。

  • 为了管理自由空间,像位图和链表这样的对象是可能的。

链表

图像

图像

链表

  • 优点:

    • 没有文件的外部碎片。

    • 易于实施

    • 顺序访问非常简单。

  • 缺点:

    • 具有最后一个块的内部碎片,除非最后一个块被完全使用。

    • 文件中的随机访问是困难的,因为对于N个块,必须读取K-1个块才能找到块K。

    • 单个数据块中可用的存储空间不是2的幂。大多数程序将数据作为2的幂大小的缓冲区发送到文件系统。

文件分配表(FAT)

图像

图像

文件分配表(FAT)

  • 基于FAT的文件系统通过将链表移动到名为FAT的集中表中来改进链表文件系统。

  • 在FS挂载时,FAT被加载到主存中。即使仍然需要遍历,随机访问也是快速的。

  • 优点:

    • 实现FAT FS很简单。管理可用空间和磁盘布局非常简单。

    • FAT可以加载到内存中,以实现快速而简单的操作。

    • 因为块不包含指针,所以整个块都可以用于数据。

  • 缺点:

    • 对于大型文件系统,FAT可能会变得很大,并消耗大量内存。

索引节点

  • 索引节点是UNIX文件系统的基本结构

  • 信息节点具有以下属性:

    • 文件所有权-用户、组

    • 文件模式-每个用户、组和其他人的rwx位

    • 上次访问和修改的时间戳

    • 文件大小(以字节为单位

    • 设备ID

    • 指向存储设备上存储文件或文件夹内容的块的指针

MINIX-inode

图像

图像

MINIX-inode

  • 前7个“区域”指向文件的前7个块。

  • “间接分区”指向包含其他分区列表的另一个块。

  • 这样做的优点是,可以利用可用的初始数据块集开始快速读取文件。

  • 此外,间接区通过像树一样遍历间接块来允许相对快速的随机访问。

  • 指向“双间接”区域的指针是区域列表的列表。

  • 前几个区域可以寻址7KB。间接区最大可寻址64MB。双间接分区可以寻址超过4 GB的地址。

索引节点

  • 使用间接、双重间接、甚至三重间接块的策略是非常成功的实施策略

  • Linux中的ext2/ext3/ext4也使用这种方法。

块缓存

  • 为了提高文件系统的性能,并使磁盘调度算法更易于实现,大多数操作系统都实现了某种类型的块缓存。

  • 块高速缓存允许预读和后写。它还允许更低的延迟I/O操作。

  • 例如,对于块缓存,WRITE()系统调用只需要在返回之前完成对缓存的修改。操作系统可以在后台线程中在磁盘上完成操作。

  • 如果没有此缓存,系统调用将无法返回,直到将写入提交到磁盘。

块缓存

  • 在Minix中,块缓存使用LRU策略实现。高速缓存维护从最近使用到最近最少使用的缓冲区的链表

    图像

    图像

块缓存

  • 任何数据块缓存的重要参数包括:

    • 物理内存中的缓存大小

    • 将缓存中的“脏”项目提交到磁盘之前的延迟

  • 缓存越大,文件系统的性能可能越好,但这可能是以程序的可用内存为代价的。

  • 将项目写入磁盘之前的延迟越大,操作系统可以做出的磁盘分配和调度决策就越好。

  • 写入磁盘之前的延迟越短,在出现故障时就越能保证将修改持久保存到磁盘。

文件夹和路径遍历

  • 在除最简单的文件系统之外的所有文件系统中,都有文件夹和路径的概念。

  • 在UNIX操作系统中,文件夹条目保存在文件类型设置为TYPE DIRECTORY的inode中。

  • 然后,inode的内容是文件名列表和指向这些文件和/或文件夹的inode的指针。

  • 解析路径涉及访问根文件夹,并递归访问每个文件夹,直到到达文件或发现该文件夹无效。

路径遍历

  • 路径遍历的一个示例。当遍历路径时,该路径可能会交叉到不同的文件系统。

    图像

    图像

虚拟文件系统/VFS

  • 除了文件和文件夹之外,还有其他东西需要由文件系统处理,如命名管道、域套接字、符号和硬链接。

  • 许多操作系统体系结构包括虚拟文件系统或VFS,而不是在每个文件系统实现中实现这些语义。

  • VFS位于操作系统内核和文件系统实现之间。

虚拟文件系统/VFS

  • VFS可以通过生成这些实现可以适应的契约来帮助适应这两种外来文件系统(如VFAT)。

  • VFS还可以通过提供公共结构和处理共享行为来帮助减少FS实现之间的代码重复:

    • 路径遍历

    • 正在处理命名管道、域套接字等...

    • 管理文件句柄和文件锁定

    • 数据块缓存的结构和功能。

    • 用于访问存储设备的结构和功能

虚拟文件系统和堆栈

  • 在某些VFS实现中,可以将文件系统堆叠在彼此的顶部。

  • Linux中的一个很好的例子是UMSDOS:基本的VFAT文件系统不支持用户、组、安全性或扩展属性。通过在VFAT上创建特殊文件并将其隐藏,UMSDOS可以将VFAT改编为类似于Unix的文件系统

  • 这方面的另一个很好的例子是UnION FS。它允许透明地覆盖两个文件系统。

虚拟文件系统和用户模式

  • 因为VFS为要实现的文件系统提供了契约,所以实现唯一的文件系统更简单。很好的例子包括:

  • Proc-进程和内核元数据,通常挂载在‘/proc’下

  • SysFS-将块和字符设备文件公开到用户模式,通常挂载在‘/dev’下

  • FUSE-提供基础设施,用于将来往于VFS的调用重定向至用户模式程序,或从用户模式程序重定向至VFS。

用户模式文件系统

  • 用户模式文件系统在流行的操作系统中的出现(它们在不太流行的操作系统中存在了一段时间)导致了大量新的文件系统开发。

  • 最流行的两个系统是用于Linux/MacOSX和其他类Unix系统的FUSE,以及用于Windows系统的Dokan。

  • 这些框架非常成功,很大程度上是因为它们使系统开发的任务变得容易得多。

用户模式文件系统

  • 在单一内核中进行开发可能会非常具有挑战性。崩溃可能会导致整个系统停机,停止和重新启动组件可能是不可能的,而且调试通常仅限于日志。

  • 对于用户模式开发,在大多数情况下都可以使用调试器。

  • 由于这些优势,像FUSE和DKAN这样的系统变得非常流行。

  • 传统上只是内核模式的系统的其他领域已经转移到用户模式系统,以简化开发并改进体系结构。在Windows中,显示管理器和大部分驱动程序框架已转移到用户模式。

直通熔丝文件系统示例

void ExampleFS::AbsPath(
    char dest[PATH_MAX], const char *path) {
  strcpy(dest, _root);
  strncat(dest, path, PATH_MAX);
}
void ExampleFS::setRootDir(const char *path) {
  printf("setting FS root to: %s\n", path);
  _root = path;
}
int ExampleFS::Getattr(
    const char *path, struct stat *statbuf) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  printf("getattr(%s)\n", fullPath);
  return RETURN_ERRNO(lstat(fullPath, statbuf));
}

直通熔丝文件系统示例

int ExampleFS::Readlink(
const char* path, char* link, size_t size){
  printf("readlink(path=%s, link=%s, size=%d)\n",
                          path, link, (int)size);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(readlink(fullPath, link, size));
}
int ExampleFS::Mknod(
const char *path, mode_t mode, dev_t dev) {
  printf("mknod(path=%s, mode=%d)\n", path, mode);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  //handles creating FIFOs, regular files, etc...
  return RETURN_ERRNO(mknod(fullPath, mode, dev));
}

直通熔丝文件系统示例

int ExampleFS::Mkdir(const char *path, mode_t mode) {
  printf("**mkdir(path=%s, mode=%d)\n", path, (int)mode);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(mkdir(fullPath, mode));
}
int ExampleFS::Unlink(const char *path) {
  printf("unlink(path=%s\n)", path);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(unlink(fullPath));
}
int ExampleFS::Rmdir(const char *path) {
  printf("rmkdir(path=%s\n)", path);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(rmdir(fullPath));
}

直通熔丝文件系统示例

int ExampleFS::Symlink(const char *path, const char *link) {
  printf("symlink(path=%s, link=%s)\n", path, link);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(symlink(fullPath, link));
}
int ExampleFS::Rename(const char *path, const char *newpath) {
  printf("rename(path=%s, newPath=%s)\n", path, newpath);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(rename(fullPath, newpath));
}

直通熔丝文件系统示例

int ExampleFS::Link(const char *path, const char *newpath) {
  printf("link(path=%s, newPath=%s)\n", path, newpath);
  char fullPath[PATH_MAX];
  char fullNewPath[PATH_MAX];
  AbsPath(fullPath, path);
  AbsPath(fullNewPath, newpath);
  return RETURN_ERRNO(link(fullPath, fullNewPath));
}
int ExampleFS::Chmod(const char *path, mode_t mode) {
  printf("chmod(path=%s, mode=%d)\n", path, mode);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(chmod(fullPath, mode));
}

直通熔丝文件系统示例

int ExampleFS::Chown(const char *path, uid_t uid, gid_t gid) {
  printf("chown(path=%s, uid=%d, gid=%d)\n",
            path, (int)uid, (int)gid);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(chown(fullPath, uid, gid));
}
int ExampleFS::Truncate(const char *path, off_t newSize) {
  printf("truncate(path=%s, newSize=%d\n", path, (int)newSize);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(truncate(fullPath, newSize));
}
int ExampleFS::Utime(const char *path, struct utimbuf *ubuf) {
  printf("utime(path=%s)\n", path);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(utime(fullPath, ubuf));
}

直通熔丝文件系统示例

int ExampleFS::Open(const char *path,
        struct fuse_file_info *fileInfo) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  fileInfo->fh = open(fullPath, fileInfo->flags);
  return 0;
}
int ExampleFS::Read(const char *path, char *buf,
        size_t size, off_t offset, struct fuse_file_info *fileInfo) {
  return RETURN_ERRNO(pread(fileInfo->fh, buf, size, offset));
}
int ExampleFS::Write(const char *path, const char *buf,
        size_t size, off_t offset, struct fuse_file_info *fileInfo) {
  return RETURN_ERRNO(pwrite(fileInfo->fh, buf, size, offset));
}

直通熔丝文件系统示例

int ExampleFS::Statfs(const char *path, struct statvfs *statInfo) {
  printf("statfs(path=%s)\n", path);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(statvfs(fullPath, statInfo));
}
int ExampleFS::Flush(const char *path, struct fuse_file_info *fileInfo) {
  printf("flush(path=%s)\n", path);
  //noop because we don't maintain our own buffers
  return 0;
}
int ExampleFS::Release(const char *path, struct fuse_file_info *fileInfo) {
  printf("release(path=%s)\n", path);
  return 0;
}

直通熔丝文件系统示例

int ExampleFS::Fsync(const char *path, int datasync, struct fuse_file_info *fi) {
  printf("fsync(path=%s, datasync=%d\n", path, datasync);
  if(datasync) {
    //sync data only
    return RETURN_ERRNO(fdatasync(fi->fh));
  } else {
    //sync data + file metadata
    return RETURN_ERRNO(fsync(fi->fh));
  }
}
int ExampleFS::Setxattr(const char *path, const char *name, const char *value, size_t size, int flags) {
  printf("setxattr(path=%s, name=%s, value=%s, size=%d, flags=%d\n",
    path, name, value, (int)size, flags);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(lsetxattr(fullPath, name, value, size, flags));
}

直通熔丝文件系统示例

int ExampleFS::Getxattr(const char *path,
    const char *name, char *value, size_t size) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(getxattr(fullPath, name, value, size));
}
int ExampleFS::Listxattr(const char *path,
    char *list, size_t size) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(llistxattr(fullPath, list, size));
}
int ExampleFS::Removexattr(const char *path, const char *name) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(lremovexattr(fullPath, name));
}

直通熔丝文件系统示例

int ExampleFS::Opendir(const char *path,
    struct fuse_file_info *fileInfo) {
  printf("opendir(path=%s)\n", path);
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  DIR *dir = opendir(fullPath);
  fileInfo->fh = (uint64_t)dir;
  return NULL -- dir ? -errno : 0;
}

直通熔丝文件系统示例

int ExampleFS::Readdir(const char *path, void *buf,
    fuse_fill_dir_t filler, off_t offset,
    struct fuse_file_info *fileInfo) {
  printf("readdir(path=%s, offset=%d)\n", path, (int)offset);
  DIR *dir = (DIR*)fileInfo->fh;
  struct dirent *de = readdir(dir);
  if(NULL -- de) {
    return -errno;
  } else {
    do {
      if(filler(buf, de->d_name, NULL, 0) != 0) {
        return -ENOMEM;
      }
    } while(NULL != (de = readdir(dir)));
  }
  return 0;
}

直通熔丝文件系统示例

int ExampleFS::Releasedir(const char *path,
    struct fuse_file_info *fileInfo) {
  closedir((DIR*)fileInfo->fh);
  return 0;
}
int ExampleFS::Fsyncdir(const char *path, int datasync,
    struct fuse_file_info *fileInfo) {
  return 0;
}
int ExampleFS::Init(struct fuse_conn_info *conn) {
  return 0;
}
int ExampleFS::Truncate(const char *path, off_t offset,
    struct fuse_file_info *fileInfo) {
  char fullPath[PATH_MAX];
  AbsPath(fullPath, path);
  return RETURN_ERRNO(ftruncate(fileInfo->fh, offset));
}

FUSE

  • 正如您在上面的示例中所看到的,FUSE文件系统与对文件和文件夹的Unix系统调用的约定非常匹配。

  • 这些功能中的每一个都有非常好的行为解释,可以在每个功能的手册页面中找到。

  • 通常,FUSE文件系统可以用500-4000行代码来实现。这与内核模式文件系统相当。

  • 一个非常高级的文件系统NTFS已经用FUSE在大约17,500行代码中实现了。

  • 一个非常流行的FUSE文件系统SSHFS已经在大约4,500行代码中实现。

  • 在Linux内核中,ext4大约有35,500行代码,ext2大约有9000行代码。