Skip to content

num73/TierFS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

[toc]

Overview

TierFS特点:TierFS是一个学习性质的项目。具有多个加速层,一个持久化层。本质上是在持久化层上面加上一个不需要一致性保证的缓存系统。性能来源是基于DRAM的缓存加速机制。 并发支持:考虑支持多个线程同时对一个文件进行读,不支持多个线程对一个文件同时写。不支持一个线程读正在写的文件。

TierFS在实现上:

  1. 默认加速层足够大,不会出现缓存溢出。

  2. 不考虑崩溃一致性。

  3. 不进行实时的设备压力测试,按固定比例分配CXL-Mem和DIMM-DRAM。

实现难点

  1. 多级缓存缓存的管理。

  2. 后台异步刷新可能会引起的线程同步问题。

第三方依赖

  1. libcapstone,TierFS在拦截系统调用时使用了Intel的syscall_intercept,libcapstone是syscall_intercept的依赖。

  2. glib2, TierFS在用户态使用C语言实现,由于C语言功能太弱,缺少高级语言自带的很多标准库,TierFS在实现上依赖glib2提供的基础的数据结构和并发控制。运行TierFS前需要确保系统正确安装了glib2。

用户态文件系统TierFS

目录管理

复用底层成熟文件系统的目录管理,在持久化层没有新的目录机制。意味着不拦截SYS_mkdirSYS_rmdirSYS_rename 这些系统调用。

元数据类型

用户态文件类型nufs_file_t,每一个nufs_file 表示一个文件,由于一个文件可能会有多个文件描述符fd,所以在TierFS中,文件和文件描述符fd单独管理,TierFS的文件使用GTree *nufs_files;进行维护。当文件第一次打开时创建对应的nufs_file_t,并放到nufs_files中,文件的最后一个文件描述符被关闭时,从nufs_files 中删除对应的nufs_file_t。 在nufs_file_t中保存有文件大小,文件的缓存数据,文件的引用计数cnt。文件相关的锁GMutex mutex;

typedef struct {

    char *name;
    size_t size; // File size
    GMutex mutex;
    int is_idle;
    int cnt;

    // todo 读的时候要保证不进行cache的状态转换。
    volatile int read_hint;
    GCond read_cond;
    nufs_dram_cache_t *updated_write_cache;
    nufs_dram_cache_t *staged_write_cache;

} nufs_file_t;

用户态文件描述符,nufs_fd_t。文件描述符使用数组维护,在TierFS由一个全局的数组nufs_fd_t nufs_fds[NUFS_FD_MAX]; 来维护,方便直接根据fd 查找到对应的nufs_fd_t

typedef struct {
    int fd;
    int is_nufs; // 判断是否是nufs的fd

    int ready_to_close;

    // 后台刷新可能会占用文件描述符, 刷新过程要阻塞close。
    volatile int fd_busy_hint;
    GMutex fd_mutex;
    GCond fd_free_cond;

    nufs_file_t *file;
    off_t offset;
} nufs_fd_t;

写缓存

Tier的写缓存使用基于红黑树的区间树( https://github.com/markfasheh/interval-tree )来维护,在Tier中有一个nufs_dram_cache_t 结构体来表示写缓存,其中包括两个字段,区间树的根节点和更新的大小,每个文件nufs_file_t有两个写缓存,分别是updated_write_cache staged_write_cache :

typedef struct {
    // 基于RBtree的区间树,区间都是闭区间,左闭右闭
    struct rb_root root;
    size_t updated_size;

} nufs_dram_cache_t;

typedef struct {

    ....

    nufs_dram_cache_t *updated_write_cache;
    nufs_dram_cache_t *staged_write_cache;
    ....
} nufs_file_t;

TierFS的所有写操作先进入到updated_write_cache写缓存。在updated_write_cache 数据写入足够多的数据并且此时这个文件没有staged_write_cache时,会将updated_write_cache 转化成该文件的staged_write_cache 并在线城池中创建后台持久化的任务。

数据版本&线程池&并发和同步控制

TierFS使用了两个线程池来处理后台任务, 其中persistent_task_thread_pool 处理缓存到持久化设备的持久化任务,close_task_thread_pool 处理对文件描述符的关闭任务,其中persistent_task_thread_pool 的大小可以根据需要配置,但是为了保证所有的close操作顺序执行close_task_thread_pool 的大小必须固定是1

把close操作单独拿出来是因为文件在调用close时,可能会存在一部分数据正在刷新到PMEM,或者还未开始刷新到PMEM的情况。

由于后台刷新的过程可能和文件读写并行进行,后台刷新和状态的过程和读写过程会相互影响,需要做同步控制,TierFS的同步机制使用glib2中的条件变量进行控制。在TierFS中的读写需要满足

  1. 读的过程不能进行cache的状态转换,即读会阻塞write cache到staged cache的转换

  2. 写的过程不能进行状态转换,即写会阻塞write cache到staged cache的转换

  3. 状态转换时不能进行读写,即write cache到staged cache的转换会阻塞读写

需要特别强调的是,由于一个文件可能被打开多次,可能会对应多个fd,为了简单起见,只考虑所有的fd有相同的读写权限,这个时候分两种情况(1)close的fd并不是这个文件最后的fd。(2)这个fd是这个文件对应的最后一个fd。不管是那种情况,将fd 关闭(close)后,fd就不能使用,这个时候如果后台使用这个fd进行的所有刷新任务都会失败。而第(2)种情况,需要将所有缓存中的数据刷新到持久化设备中。为了保证close不会阻塞应用程序,并且不会出现错误,Tier的close使用单独的具有1个线程的线程池完成,使用1个线程的线程池可以保证所有的close都是异步并且顺序执行的。在对close的同步控制上满足

  1. 有后台任务正在使用某个fd进行刷新时不能进行这个fd的close。

  2. 如果这个fd是该文件的最后一个fd,需要创建后台刷新任务,等所有任务顺利执行后再进行close。

  3. 所有的close异步并且顺序执行。

GThreadPool *persistent_task_thread_pool;

GThreadPool *close_task_thread_pool;

void nufs_g_persistent_task_func(gpointer data, gpointer user_data) {
    nufs_persistent_task_info_t *info = (nufs_persistent_task_info_t *)data;
    int fd = info->fd;

    g_mutex_lock(&nufs_fds[fd].fd_mutex);
    nufs_fds[fd].fd_busy_hint = 1;
    g_mutex_unlock(&nufs_fds[fd].fd_mutex);

    nufs_dram_cache_t *cache = info->cache;

    nufs_cache_persist(cache, fd);

    g_mutex_lock(&nufs_fds[fd].fd_mutex);
    nufs_fds[fd].fd_busy_hint = 0;
    g_cond_signal(&nufs_fds[fd].fd_free_cond);
    g_mutex_unlock(&nufs_fds[fd].fd_mutex);

    g_mutex_lock(&nufs_fds[fd].file->mutex);
    while (nufs_fds[fd].file->read_hint) {
        g_cond_wait(&nufs_fds[fd].file->read_cond, &nufs_fds[fd].file->mutex);
    }
    nufs_fds[fd].file->staged_write_cache = NULL;
    g_mutex_unlock(&nufs_fds[fd].file->mutex);

    free_interval_tree(&cache->root);
    free(info);
}

void nufs_g_close_task_func(gpointer data, gpointer user_data) {

    nufs_close_task_info_t *close_info = (nufs_close_task_info_t *)data;
   
    nufs_fd_t *nufs_fd = &nufs_fds[close_info->fd;
    g_mutex_lock(&nufs_fd->fd_mutex);
    while (nufs_fd->fd_busy_hint) {
        g_cond_wait(&nufs_fd->fd_free_cond, &nufs_fd->fd_mutex);
    }
    if (!close_info->is_file_clear) {
        original_close(nufs_fd->fd);
    } else {
        cache_persist(nufs_fd->file->staged_write_cache, nufs_fd->fd);
        nufs_fd->file->staged_write_cache = NULL;
        cache_persist(nufs_fd->file->updated_write_cache, nufs_fd->fd);
        nufs_fd->file->updated_write_cache = NULL;
        original_close(nufs_fd->fd);
        nufs_free_file(close_info->file);
    }

    g_mutex_unlock(&nufs_fd->fd_mutex);
}

fsync: 阻塞式接口,手动把对应fd的所有cache刷新到底层持久设备中。

Hot & Cold Cache

文件系统的Hot/Cold识别和处理可以分成两种,1. 将文件分成Hot/Cold文件,不同的文件放到不同的设别或把Hot文件进行缓存。2. 将一个文件内数据块做Hot和Cold的区分,Hot/Cold块放到不同的设备或把Hot块进行缓存。

TierFS的Hot/Cold为第二种,对同一个文件中的Hot/Cold块进行区分并且缓存Hot块。

为了实现的简单,TierFS只的Hot缓存只关注读过程的缓存,TierFS的Hot/Cold主要机制:

  1. Hot/Cold主要优化读性能,TierFS的Hot/Cold统计和识别只关注读的过程。

  2. 使用简单阈值动态识别Hot/Cold页面。

  3. Hot/Cold独立于两层基于区间树的A-B write缓存,Hot/Cold缓存使用哈希表维护。

  4. 使用TierFS读文件时会统计块的读次数,如果该块的读次数超过一定阈值,将其放入Hot缓存。读文件数据时先走Hot 缓存。

  5. 写文件数据时清理对应Hot缓存。

文件操作

open

  1. 调用底层 original_open 打开文件,获取真实 fd。

  2. 在全局 nufs_files 树中查找文件元数据(nufs_file_t),没有则新建并插入。

  3. 增加文件引用计数。

  4. nufs_fds 表中记录 fd 相关信息(如偏移、是否为 nufs 文件等)。

read

  1. 加锁,设置read_hint,防止并发的写缓存状态转换。

  2. 统计访问页数,若某页访问次数超过阈值,则建立影子页缓存(热点页)。

  3. 优先从影子页读取数据,剩余部分再从 DRAM 缓存和底层存储读取。

  4. 读完后解锁,清除 read_hint

write

  1. 写入 DRAM 缓存(updated_write_cache)。

  2. 若缓存达到阈值且没有正在持久化的缓存,则触发缓存分阶段持久化(异步写回底层存储)。

  3. 写入后更新文件大小。

  4. 写入时会清除对应的影子页,保证一致性。

close

  1. 文件引用计数减一。

  2. 若引用计数为零,移除元数据并异步持久化缓存、释放资源。

  3. 所有关闭操作通过线程池异步执行,避免阻塞主线程。

测试结果:

SSD上的测试,底层使用ext4文件系统,O_DIRECT方式的读写性能:

num73@num73-pclab:~/TierFS/gtest$ ./test1
Creating test file of size 1073741824 bytes...
Create Success!
Sequential read throughput: 28.02 MB/s
Sequential write throughput: 123.32 MB/s
Random read throughput: 21.15 MB/s
Random write throughput: 136.85 MB/s

使用了TierFS中间层的结果:

num73@num73-pclab:~/TierFS/gtest$ ./run_nufs.sh ./test1
NUFS: initialized.
Creating test file of size 1073741824 bytes...
Create Success!
Sequential read throughput: 2866.99 MB/s
Sequential write throughput: 2084.52 MB/s
Random read throughput: 2581.76 MB/s
Random write throughput: 1897.85 MB/s
NUFS: destructing...

对比可以看到提升大很,对比原有ext4文件系统提升了13x-122x

About

TierFS is TieredFS.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors