Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子:

t@name-VirtualBox:/# lsbin boot cdrom dev etc home lib lib64 lost+found media mnt opt proc root run sbin snap srv sys tmp usr var

它其实是一棵目录树(没有画全):

然而,虽然所有的UNIX系统以及类UNIX系统的文件系统看起来一样,但是它们的实现却是不尽相同。

作为普通用户,了解文件系统的基本操作就够了;作为应用开发人员,了解文件系统的POSIX接口足矣,但是作为一个对操作系统有着浓厚兴趣的爱好者而言,自己可能就是一个新的文件系统的潜在实现者,所以必须一窥究竟,看看如此外观的文件系统到底是如何实现的。

网上已经有了很多关于UNIX/Linux文件系统实现的资源,但是无一例外,都太复杂了,除了整体的源码分析外,几乎就是针对某个特定文件系统的详解了,如此复杂的这些对于初涉该领域的满腔热情者无疑是一盆冷水,很多人因此望而却步。

  • ...
  • mount机制是如何实现的?
  • inode是如何分配的?磁盘inode和内存inode有什么区别?
  • dentry缓存是怎么回事?是如何管理的?
  • pagecache是什么?radix树如何管理文件数据缓存?
  • cache和buffer的区别又是什么?
  • ...

几乎所有的关于Linux文件系统实现的资源都在用不同的语言解释上面的这些问题,这很容易陷入细节的泥潭。

本文以Linux内核为例,用一种稍微不同的方式去描述文件系统的实现。嗯,我会分3个部分来介绍Linux内核的文件系统:

  1. Linux文件系统在不同视角下的样子
  2. 实现一个很小但能跑的文件系统
  3. 接下来要做什么

本文中,我会通过一个实实在在的文件系统实现的例子,试图阐述 实现一个文件系统,哪些是必须的,哪些不是必须的。 这是一个任务驱动的过程,从简单的例子开始。

读过本文之后,相信会对Linux文件系统的实现有一个总体上的宏观把握,然后再去反复推敲上述的细节问题,重读网上的那些经典资源,相信会事半功倍。

Linux文件系统在不同视角下的样子

当然,在给出最简单的tinyfs实现之前,还是会有一个总体的介绍。

如果我们把本文最初描述的那个在几乎所有UNIX/类UNIX系统中长的一模一样的文件系统表面刨开,在Linux内核中,文初的那棵树其实它长下面的样子(其实在大多数类UNIX系统中,它们长得都差不多):

【这张图基于我一张手绘图修改而成】

我们看到,Linux系统的文件目录树就是靠上图中的这一系列的链表穿针引线给串在一起的,就像缝制一件衣服一样,最终的成衣就是我们看到的Linux系统目录树,而缝制这件成衣的线以及指导走线的规则便是VFS本身了。

现在只要记住两个重要链表:

  1. 文件系统链表。
  2. 每一个文件系统的mount挂载点链表。

然后读完本文之后再去结合代码深入分析它们是如何串起整个文件系统的。

VFS之所有可以将机制大相径庭的完全不同的文件系统对外统一成一个样子,完全就是依靠了它的统一的对POSIX文件调用的接口,该接口的结构看上去是下面的样子:

注意上图最下面的那个椭圆,如果要实现一个文件系统,这个椭圆里的东西是关键,它完成了穿针引线的大部分工作。

现在让我们纵向地看一下一个完整的文件系统实现都包括什么,我指的是从POSIX系统调用开始,一直到数据落盘。Linux内核关于文件系统IO,完整的视图如下所示:

注意VFS提供的三类接口:

  • 和POSIX系统调用的接口 即实现open/read/write的操作的接口。
  • 和底层介质的接口 即下接块设备层的接口。
  • 如何管理自身 即何时以及如何操作VFS数据结构inode,dentry,mount等对象。

一个文件系统如果能实现上面三类接口,那它就是个完整的文件系统了。

我们恰好可以从设计并实现一个最基本的这样的文件系统开始。一个基本的文件系统,其着重点在于上图中红色的部分,而其它部分则不是必不可少的,但是却是让该文件系统变得优秀(而不仅仅是可用)所必须的。

实现一个很小但能跑的文件系统

为什么要实现这么一个文件系统,难道没人已经做了这个工作吗?做这个工作的意义何在?

原因如下:

  • 我没有找到现成的比较完整且炫酷的作品。 当然有人写这种文件系统练手,但是看下来要么就是使用了libfs.c里封装好的接口,要么就是没有自己设计文件系统的底层存储格式。
  • 下班的班车在路上堵了一个多小时,无聊撸会儿代码。

然而确实,我没有找到简单的 最小文件系统 实现,也许你会说Linux内核自带的ramfs难道不就是一个现成的吗?的确算一个,但它有两个问题导致你无法领略实现一个文件系统的全过程,注意,我说的可是全过程:

  1. ramfs无法让你自己设计底层模拟介质的格式,不完整。
  2. ramfs调用了大量的fs/libfs.c中的内核库例程,不纯碎。

为了 追求完整, 如果你把如何组织一块内核作为ramfs的底层介质这部分代码全部看完,如果你把libfs.c里的库实现全部看完,我想ramfs也就不算一个 足够简单 的文件系统实例了。

看到了么?要想代码简单,你就不得不使用libfs.c里的现成的例程,这将损失你实现一个文件系统的完整性体验,反之,要想完整实现一个文件系统,你可能不得不自己写大量的代码,这却并不简单。

如何既完备,又足够简单呢?

对于我这种编程水平渣渣的内核爱好者而言,如何在堵车的一个多小时内完成一个可以编译通过的文件系统(我承认完全能跑是我回到家后又调试了一个多小时才完成的...),这对于我而言,是一个挑战,但我要试一试,没想到就成功了。所以才有了今天的分享。

我从最底层的介质结构的设计开始。

我并没有真实的硬件介质,也并不打算编写专门的 格式化程序 去格式化一块内存区域,所以我直接用大数组定义一块内存,它便是我的模拟介质了,我的tinyfs的文件格式如下:

// tinyfs.h#define MAXLEN 8#define MAX_FILES 32#define MAX_BLOCKSIZE 512// 定义每一个目录项的格式struct dir_entry { char filename[MAXLEN]; uint8_t idx;};// 定义每一个文件的格式。struct file_blk { uint8_t busy; mode_t mode; uint8_t idx; union { uint8_t file_size; uint8_t dir_children; }; char data[0];};// OK,下面的block数组所占据的连续内存就是我的tinyfs的介质,每一个元素代表一个文件。// struct file_blk block[MAX_FILES+1];

这个文件系统的格式非常的Low:

  1. 最多容纳512个文件(包括目录在内)。
  2. 每个文件包括元数据在内最多32个字节。
  3. 文件名最多8个字节。

之所以这么Low是因为它只是一个开始, 当这个文件系统实现并且能跑之后,你会发现它因为Low而带来的不足和一些代价,而弥补这些不足正好是优化的动机,带着你逐步实现一个更加不Low的文件系统,在实现的过程中,你会窥见并掌握Linux内核文件系统的全貌和细节。 完美的学习过程,OK!

下面是代码:

// tinyfs.c#include #include #include #include #include "tinyfs.h"struct file_blk block[MAX_FILES+1];int curr_count = 0; // 我勒个去,竟然使用了全局变量!// 获得一个尚未使用的文件块,保存新创建的文件或者目录static int get_block(void){ int i; // 就是一个遍历,但实现快速。 for (i = 2; i < MAX_FILES; i++) { if (!block[i].busy) { block[i].busy = 1; return i; } } return -1;}static struct inode_operations tinyfs_inode_ops;// 读取目录的实现static int tinyfs_readdir(struct file *filp, void *dirent, filldir_t filldir){ loff_t pos; struct file_blk *blk; struct dir_entry *entry; int i; pos = filp->f_pos; if (pos) return 0; blk = (struct file_blk *)filp->f_dentry->d_inode->i_private; if (!S_ISDIR(blk->mode)) { return -ENOTDIR; } // 循环获取一个目录的所有文件的文件名 entry = (struct dir_entry *)&blk->data[0]; for (i = 0; i < blk->dir_children; i++) { filldir(dirent, entry[i].filename, MAXLEN, pos, entry[i].idx, DT_UNKNOWN); filp->f_pos += sizeof(struct dir_entry); pos += sizeof(struct dir_entry); } return 0;}// read实现ssize_t tinyfs_read(struct file * filp, char __user * buf, size_t len, loff_t *ppos){ struct file_blk *blk; char *buffer; blk = (struct file_blk *)filp->f_path.dentry->d_inode->i_private; if (*ppos >= blk->file_size) return 0; buffer = (char *)&blk->data[0]; len = min((size_t) blk->file_size, len); if (copy_to_user(buf, buffer, len)) { return -EFAULT; } *ppos += len; return len;}// write实现ssize_t tinyfs_write(struct file * filp, const char __user * buf, size_t len, loff_t * ppos){ struct file_blk *blk; char *buffer; blk = filp->f_path.dentry->d_inode->i_private; buffer = (char *)&blk->data[0]; buffer += *ppos; if (copy_from_user(buffer, buf, len)) { return -EFAULT; } *ppos += len; blk->file_size = *ppos; return len;}const struct file_operations tinyfs_file_operations = { .read = tinyfs_read, .write = tinyfs_write,};const struct file_operations tinyfs_dir_operations = { .owner = THIS_MODULE, .readdir = tinyfs_readdir,};// 创建文件的实现static int tinyfs_do_create(struct inode *dir, struct dentry *dentry, umode_t mode){ struct inode *inode; struct super_block *sb; struct dir_entry *entry; struct file_blk *blk, *pblk; int idx; sb = dir->i_sb; if (curr_count >= MAX_FILES) { return -ENOSPC; } if (!S_ISDIR(mode) && !S_ISREG(mode)) { return -EINVAL; } inode = new_inode(sb); if (!inode) { return -ENOMEM; } inode->i_sb = sb; inode->i_op = &tinyfs_inode_ops; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; idx = get_block(); // 获取一个空闲的文件块保存新文件 blk = &block[idx]; inode->i_ino = idx; blk->mode = mode; curr_count ++; if (S_ISDIR(mode)) { blk->dir_children = 0; inode->i_fop = &tinyfs_dir_operations; } else if (S_ISREG(mode)) { blk->file_size = 0; inode->i_fop = &tinyfs_file_operations; } inode->i_private = blk; pblk = (struct file_blk *)dir->i_private; entry = (struct dir_entry *)&pblk->data[0]; entry += pblk->dir_children; pblk->dir_children ++; entry->idx = idx; strcpy(entry->filename, dentry->d_name.name); // VFS穿针引线的关键步骤,将VFS的inode链接到链表 inode_init_owner(inode, dir, mode);  d_add(dentry, inode); return 0;}static int tinyfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode){ return tinyfs_do_create(dir, dentry, S_IFDIR | mode);}static int tinyfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl){ return tinyfs_do_create(dir, dentry, mode);}static struct inode *tinyfs_iget(struct super_block *sb, int idx){ struct inode *inode; struct file_blk *blk; inode = new_inode(sb); inode->i_ino = idx; inode->i_sb = sb; inode->i_op = &tinyfs_inode_ops; blk = &block[idx]; if (S_ISDIR(blk->mode)) inode->i_fop = &tinyfs_dir_operations; else if (S_ISREG(blk->mode)) inode->i_fop = &tinyfs_file_operations; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_private = blk; return inode;}struct dentry *tinyfs_lookup(struct inode *parent_inode, struct dentry *child_dentry, unsigned int flags){ struct super_block *sb = parent_inode->i_sb; struct file_blk *blk; struct dir_entry *entry; int i; blk = (struct file_blk *)parent_inode->i_private; entry = (struct dir_entry *)&blk->data[0]; for (i = 0; i < blk->dir_children; i++) { if (!strcmp(entry[i].filename, child_dentry->d_name.name)) { struct inode *inode = tinyfs_iget(sb, entry[i].idx); struct file_blk *inner = (struct file_blk *)inode->i_private; inode_init_owner(inode, parent_inode, inner->mode); d_add(child_dentry, inode); return NULL; } } return NULL;}int tinyfs_rmdir(struct inode *dir, struct dentry *dentry){ struct inode *inode = dentry->d_inode; struct file_blk *blk = (struct file_blk *)inode->i_private; blk->busy = 0; return simple_rmdir(dir, dentry);}int tinyfs_unlink(struct inode *dir, struct dentry *dentry){ int i; struct inode *inode = dentry->d_inode; struct file_blk *blk = (struct file_blk *)inode->i_private; struct file_blk *pblk = (struct file_blk *)dir->i_private; struct dir_entry *entry; // 更新其上层目录 entry = (struct dir_entry *)&pblk->data[0]; for (i = 0; i < pblk->dir_children; i++) { if (!strcmp(entry[i].filename, dentry->d_name.name)) { int j; for (j = i; j < pblk->dir_children - 1; j++) { memcpy(&entry[j], &entry[j+1], sizeof(struct dir_entry)); } pblk->dir_children --; break; } } blk->busy = 0; return simple_unlink(dir, dentry);}static struct inode_operations tinyfs_inode_ops = { .create = tinyfs_create, .lookup = tinyfs_lookup, .mkdir = tinyfs_mkdir, .rmdir = tinyfs_rmdir, .unlink = tinyfs_unlink,};int tinyfs_fill_super(struct super_block *sb, void *data, int silent){ struct inode *root_inode; int mode = S_IFDIR; root_inode = new_inode(sb); root_inode->i_ino = 1; inode_init_owner(root_inode, NULL, mode); root_inode->i_sb = sb; root_inode->i_op = &tinyfs_inode_ops; root_inode->i_fop = &tinyfs_dir_operations; root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = CURRENT_TIME; block[1].mode = mode; block[1].dir_children = 0; block[1].idx = 1; block[1].busy = 1; root_inode->i_private = &block[1]; sb->s_root = d_make_root(root_inode); curr_count ++; return 0;}static struct dentry *tinyfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data){ return mount_nodev(fs_type, flags, data, tinyfs_fill_super);}static void tinyfs_kill_superblock(struct super_block *sb){ kill_anon_super(sb);}struct file_system_type tinyfs_fs_type = { .owner = THIS_MODULE, .name = "tinyfs

简单文件系统的实现_300来行代码带你实现一个能跑的最小Linux文件系统相关推荐

  1. 300行代码带你实现一个Linux文件系统

    Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子: root@name-VirtualBox:/# ls bin boot cdrom dev et ...

  2. python黑科技:Python大佬用20行代码带你打造一个微信聊天机器人,真神了~

    如何用20行Python代码打造一个微信群聊助手? 1.安装python环境 2.安装python的itchat库 3.安装itchat库 4.Linux 5.申请图灵机器人API和key 6.编写p ...

  3. 5行代码带你实现一个js的打字效果

    有次看电影, 看到屏幕上一个个的文字蹦出来, 感觉像是有人在打字一样, 觉得挺有意思, 于是这里也用js实现了一个. 效果预览 最简单的打字效果 只要五行代码, 这里直接贴出来: const dom ...

  4. 300来行代码实现最小Linux文件系统

    Linux作为一个类UNIX系统,其文件系统保留了原始UNIX文件系统的表象形式,它看起来是这个样子: root@name-VirtualBox:/# ls bin  boot  cdrom  dev ...

  5. 用python画苹果的logo_简单几步,100行代码用Python画一个蝙蝠侠的logo

    转自:菜鸟学Python 简单几步,100行代码用Python画一个蝙蝠侠的logo-1.jpg (35.33 KB, 下载次数: 0) 2020-7-30 12:04 上传 蝙蝠侠作为DC漫画的核心 ...

  6. 神经网络 c++ 源码 可以直接复制运行,提供数据集,操作简单,最少仅需4行代码

    神经网络 c++ 源码 可以直接复制运行,提供数据集,操作简单,最少仅需4行代码 本文的神经网络,让你省去Python那些花里胡哨的变量名,最少仅需4行代码即可完成自己的神经网络** 本文章采用c++ ...

  7. python画画100行代码_简单几步,100行代码用Python画一个蝙蝠侠的logo

    原标题:简单几步,100行代码用Python画一个蝙蝠侠的logo 转自:菜鸟学Python 蝙蝠侠作为DC漫画的核心人物之一,一直都受到广大粉丝的喜爱,而笔者作为DC的铁杆粉丝,自然也是老爷(粉丝对 ...

  8. python爬虫都能干什么用_5 行代码就能写一个 Python 爬虫

    欢迎关注我的公众号:第2大脑,或者博客:高级农民工,阅读体验更好. 摘要:5 行代码就能写一个 Python 爬虫. 如果你是比较早关注我的话,会发现我此前的大部分文章都是在写 Python 爬虫,前 ...

  9. 几行代码就搞定一个文字识别功能,同时还能转换成语音,畅快!

    前几天想把一篇不错的文章保存下来,无奈是图片的,于是想利用python把图片中的文字识别出来 实现的方式还是挺多的,这里介绍下百度的AI开放平台,毕竟大公司,感觉识别的精度会高点,同时相信他们的算法也 ...

最新文章

  1. Windows 7各版本主要区别
  2. spring roo_使用Spring Roo进行快速云开发–第1部分:Google App Engine(GAE)
  3. 使用JacpFX和JavaFX2构建富客户端
  4. 测试linux硬盘读写速度
  5. android消息处理机制原理解析
  6. Atitit 小程序后端服务api艾提拉总结 目录 1. 服务端 1 1.1. 开放接口 1 1.1.1. 登录 1 1.1.2. 用户信息 1 1.1.3. 接口调用凭证 2 1.1.4. 数据分
  7. chrome frame节点 取_Chrome Frame插件解决IE浏览器兼容问题
  8. Word插入高分辨率图片无法显示
  9. alfafile中转站免费_免费、不限速的文件中转站or网盘
  10. windows media player 官方修复工具下载及修复方法
  11. 清华大学最新科研进展汇总(2020-2021年)
  12. Linux中使用cp命令报cp:omitting directory错误
  13. chrome翻译插件之谷歌翻译
  14. PEST分析顺丰服务需求_顺丰内外部环境分析.doc
  15. 指定的文件夹没有包含设备的兼容软件驱动程序...请确认它是为用于基于X64的系统的Windows设计的
  16. 一千度近视眼学计算机,【震惊了】你见过近视8百,1千,3千,5千度?你见过9000度吗?...
  17. 【移动端】手机界面的设计尺寸
  18. 翻译考试用计算机作答,2019年CATTI考试改为机考,官方首次披露真题
  19. (二-1)多码之间的进制转换【计算机组成原理】
  20. 浅谈薄膜行业MES解决方案

热门文章

  1. php 折叠菜单,SlashdotMenu 折叠菜单
  2. MATLAB-1:入门基础
  3. String 重载 + 原理分析
  4. OpenCASCADE:MFC示例
  5. wxWidgets:wxSplashScreen类用法
  6. Wilcox:子类化顶级窗口
  7. boost::program_options模块实现一个用户定义的类来解析 特定机制——不是默认使用的 iostream 操作的测试程序
  8. boost::lockfree::stack用法的测试程序
  9. boost::histogram::axis::regular用法的测试程序
  10. boost::hana::value用法的测试程序